1.硬件架构
最为关键的三个部件:CPU,内存,I/O控制芯片。
I/O设备相比于CPU和内存的速度很慢,为了协调I/O设备和总线之间的速度,每个设备都会有一个I/O控制器;
由于CPU核心频率的提升,导致内存跟不上CPU的速度,于是产生了和内存频率一致的系统总线,而CPU采用倍频的方式与系统总线进行通信;
北桥芯片:协调CPU,内存和高速的图形设备,使其能够高速地交换数据;
南桥芯片:磁盘、USB、键盘、鼠标等设备连接在南桥,再由南桥汇总后连接到北桥;
系统总线采用PCI结构,低速设备踩踏ISA总线;
CPU频率限制在4GHz;
多核处理器:处理器厂商将多个处理器“合并在一起打包出售”,这些处理器之间共享较昂贵的缓存部件,只保留多个核心。
2.软件架构
2.1 系统软件分类
1)平台性的,如操作系统内核,驱动程序,运行库,系统工具
2)用于程序开发的,编译器,汇编器,链接器,开发库。
2.2 软件体系分层
应用程序,开发工具
(接口):操作系统应用程序编程接口API
运行库
(接口):系统调用接口
操作系统内核
(接口):硬件规格
硬件
3.操作系统
3.1 功能
1)提供抽象的接口
2)管理硬件资源
Multiprogramming->分时系统->多任务系统
操作系统接管所有硬件资源,本身运行在一个受硬件保护的级别;
所有应用程序都以进程的方式运行在比操作系统权限更低的级别,每个进程有自己独立的地址空间,使得进程间的地址空间相互隔离;
3.2 对CPU的管理
CPU由操作系统进行分配,每个进程根据进程优先级得到CPU;
若运行超出一定时间,操作系统会暂停该进程,将CPU分配给其他待运行的进程,即抢占式分配CPU;
3.3 对I/O控制器的管理
繁琐的硬件细节由操作系统的硬件驱动程序完成,其与操作系统内核运行在特权级。
操作系统开发者为硬件生产厂商提供一系列接口和框架,由他们为硬件开发驱动程序。
硬盘:基本存储单位为扇区(sector),每个扇区一般为512字节,假设硬盘有2个盘片,每个盘面分65536磁道,每个磁道分1024个扇区,则硬盘容量为2*2*65536*1024*512字节。
3.4 对内存的管理
物理地址空间:32位地址空间大小为2^32=4 294 967 296字节。
虚拟地址空间:每个进程有自己独立的虚拟空间,且只能访问自己的地址空间。
分段(segmentation):把一段程序所需要的内存空间大小的虚拟空间映射到某个物理地址空间。该方法解决了地址隔离和程序重定位问题,但未解决内存使用效率低问题,因为若内存不足,换入换出到磁盘的都是整个程序;
分页(paging):把地址空间人为地等分成固定大小的页,以页为单位来存取和交换数据;若虚拟空间的页映射到同一物理页,则可实现内存共享。
页错误:进程需要用到的页不在内存而在磁盘中。此时由操作系统接管进程,将页从磁盘读出并装入内存,再将其与虚拟页建立映射关系。
几乎所有硬件采用MMU(内存管理单元)的部件进行页映射,其集成在CPU中。
CPU----(virtual address)----MMU----(physical address)----物理内存
4.线程
4.1 线程概念
线程:
1)又称轻量级进程LWP,是程序执行流的最小单元;
2)一个标准的线程包括线程ID,当前指令指针(PC),寄存器集合,堆栈;
3)通常一个进程由一到多个线程组成,各线程间共享程序的内存空间(代码段,数据段,堆等)及一些进程级资源(打开文件和信号);
4)多个线程可以互不干扰地并发执行,并共享进程的全局变量和堆数据;
线程访问权限
1)线程私有:局部变量,函数参数,线程局部存储
2)线程间共享:全局变量,堆数据,函数静态变量,程序代码,打开文件
4.2 线程调度
概念:单处理器让多线程程序轮流执行
线程状态:
1)运行(running):此时线程正在执行
2)就绪(ready):此时线程可以立刻执行,但CPU已经被占用
3)等待(waiting):此时线程正在等待某一事件发生(通常是I/O或同步),无法执行
IO密集型线程:该线程频繁进入等待状态,即放弃可占用的时间份额(通常优先级更高)
CPU密集型线程:该线程频繁进行大量计算,将时间片耗尽
为避免线程饿死,调度系统会逐步提升等待了过长时间的低优先级线程
线程优先级改变方式
1)用户指定优先级
2)根据进入等待状态的频繁程度提升或降低优先级
3)长时间得不到执行而被提升优先级
linux内核中不存在真正意义上的线程,而将所有的执行实体(线程或进程)都称为任务,每个任务类似于一个单线程的进程,不同任务间可以选择共享内存空间,即共享了同一内存空间的多个任务构成一个进程,这些任务成立进程里的线程。
linux可以使用以下方式创建任务:
1)fork:复制当前进程
2)exec:使用新的可执行映像覆盖当前可执行映像
3)clone:创建子进程并从指定位置开始执行
fork只能产生本任务的镜像,需要使用exec配合启动新任务;因此,fork产生一个心任务后,新任务调用exec来执行新的可执行文件;使用clone来产生新线程。
4.3 线程安全
单指令的操作是原子的(Atomic),即单条指令是不会被打断的。
同步:指在一个线程访问数据未结束时,其他线程不得对同一个数据进行访问
锁:同步最常见的方法,每个线程在访问数据或资源前先试图获取锁(Acquire),并在访问结束后释放锁(Release);在锁已经被占用时试图获取锁,线程会等待,直到锁重新可用。
二元信号量(Binary Semaphore):最简单的一种锁,只有两种状态:占用/非占用,适合只能被唯一一个线程独占访问的资源;同一信号量可以由一个线程获取后由另一个线程释放
多元信号量,即信号量:允许多个线程并发访问的资源,一个初始值为N的信号量允许N的线程并发访问
互斥量(Mutex):资源仅同时允许一个线程访问,要求哪个线程获取互斥量,则该线程负责释放锁
临界区(Critical Section):比互斥量更严格的同步手段
1)进入临界区:获取锁
2)离开临界区:释放锁
3)与信号量和互斥量区别:临界区作用范围仅限本进程,而信号量和互斥量在系统的任何进程都可见;谁获取谁释放
读写锁(Read-Write Lock):上述同步手段对于读取频繁,而偶尔写入的场景较低效
读写锁获取方式:共享式(Shared)和独占式(Exclusive)
读写锁状态 | 以共享方式获取 | 以独占方式获取 |
自由 | 成功 | 成功 |
共享 | 成功 | 等待 |
独占 | 等待 | 等待 |
条件变量(Condition Variable):类似于栅栏,线程可以等待条件变量,也可以唤醒条件变量
使用条件变量可以让许多线程一起等待某个事件发生,当事件发生时,所有线程可以一起恢复执行
函数可重入(Reentrant):表示该函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行,包括两种情况
1)多个线程同时执行该函数
2)函数自身调用自身
函数可重入,表明该函数被重入后没有不良后果;可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用
特点:
1)不使用任何(局部)静态或全局的非const变量
2)不返回任何(局部)静态或全局的非const变量的指针
3)仅依赖于调用方提供的参数
4)不依赖任何单个资源的锁
5)不调用任何不可重入的函数
volatile关键字作用
1)阻止编译器为提高速度将一个变量缓存到寄存器内而不写回
2)阻止编译器调整操作volatile变量的指令顺序
4.4 用户线程与内核线程的映射
用户态多线程库的实现方式
1)一对一模型
一个用户使用的线程唯一对应一个内核使用的线程(反之,一个内核里的线程在用户态不一定有对应的线程存在)
缺点:由于许多操作系统限制了内核线程的数量,因此一对一线程会让用户的线程数量受限制;许多操作系统内核线程调度时,上下文切换开销较大,导致用户线程执行效率下降
2)多对一模型
将多个用户线程映射到一个内核线程,线程间切换由用户态代码实现
缺点:若一个用户线程阻塞,则所有线程都无法执行,此时内核的线程也阻塞
优点:高效的上下文切换和无限制的线程数量
3)多对多模型
将多个用户线程映射到少数但不止一个内核线程