《现代操作系统》读书笔记—进程与线程篇
#进程
##进程模型
计算机上的所有可运行的软件,通常包括操作系统,被组织成若干顺序进程(squential process),简称进程(process).一个进程就是一个正在运行的实例,包括程序计数器、寄存器和变量的当前值。从概念上说,每个程序拥有它自己的CPU.然而实际上是CPU在多个进程间切换.
在UNIX系统中,可以使用fork()系统调用创建系统调用.
进程的状态
进程一般来说有三种状态
- 运行态
- 就绪态
- 阻塞态
进程状态转换图:
进程状态切换的原因:
- 就绪状态→执行状态:一个进程被进程调度程序选中。
- 执行状态→阻塞状态:请求并等待某个事件发生。
- 执行状态→就绪状态:时间片用完或在抢占式调度中有更高优先级的进程变为就绪状态。
- 阻塞状态→就绪状态:进程因为等待的某个条件发生而被唤醒。
进程的实现
为了实现进程模型,操作系统维护着一张表格(一个结构数组),即进程表(process table),每个进程占用一个进程表项.进程表项也叫PCB(process control block),该表项包含了进程状态的重要信息,包括程序计数器,堆栈指针,内存分配状况,所打开的文件状态,账号和调度信息,以及其他在进程由运行态转换到就绪态或阻塞态时必须保存的信息,从而保证该进程一会能够再次启动,就好像从来没有被切换过一样.
进程切换的过程如下图:
线程
线程的使用
进程的两个基本属性:
- 进程是一个拥有资源的独立单元;
- 进程同时又是一个可以被处理器独立调度和分配的单元。
为了使多个程序更好地并发执行,并尽量减少操作系统的开销,操作系统设计者引入了线程,让线程去完成第二个基本属性的任务,而进程只完成第一个基本属性的任务。
线程的定义
线程是进程内一个相对独立的、可调度的执行单元。线程自己基本上不拥有资源,只拥有一点在运行时必不可少的资源(如程序计数器、一组寄存器和栈),但它可以与同属一个进程的其他线程共享进程拥有的全部资源。多线程是指一个进程中有多个线程,这些线程共享该进程资源。但是各线程自己堆栈数据不对其他线程共享。
经典的线程模型
- 多对一模型:多对一模型将多个用户级线程映射到一个内核级线程上。只要一个用户级线程阻塞,就会导致整个进程阻塞。
- 一对一模型:一对一模型将内核级线程与用户级线程一一对应。这样做的好处是当一个线程阻塞时,不影响其他线程的运行。
- 多对多模型:多对多模型将多个用户级线程映射到多个内核级线程,采用这样的模型可以打破前两种模型对用户级线程的限制,不仅可以使多个用户级线程真正意义上并行执行,而且不会限制用户级线程的数量。
线程的实现
- 内核级线程:指依赖于内核,由操作系统内核完成创建和撤销工作的线程。
- 用户级线程:指不依赖于操作系统核心,由应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制的线程。
- 混合实现 : 多对多模型实现,多个用户线程被分配到多个内核线程上.
用户级线程的优点:
- 切换代价小.具体体现为
- 切换都是在本地进行,不需要进入内核,只有一套栈,而内核有两套栈.所以速度会快很多.
- 不需要trap,不需要上下文切换,也不需要对内存高速缓存进行刷新.
- 允许每个进程都有自己的调度算法(进程主动使用yield()放弃)
用户级线程的缺点:
- 如何实现阻塞系统调用,因为这会停止所有的用户态指令.
- 如果发生缺页中断,由于操作系统不知道有其他线程存在,会阻塞到这个线程完成缺页中断,而不是去调度其他用户级线程
线程与进程的比较
- 调度:在传统的操作系统中,拥有资源和独立调度的基本单位都是进程。而在引入线程的操作系统中,线程是独立调度的基本单位,进程是拥有资源的基本单位。在同一进程中,线程的切换不会引起进程切换。在不同进程中进行线程切换,如从一个进程内的线程切换到另一个进程中的线程中,将会引起进程切换。
- 拥有资源:进程是拥有资源的基本单位,而线程不拥有系统资源(也有一点必不可少的资源,并非什么资源都没有),但线程可以访问其隶属进程的系统资源。
- 并发性:在引入线程的操作系统中,不仅进程之间可以并发执行,而且同一进程内的多个线程之间也可以并发执行。
- 系统开销:由于创建进程或撤销进程时,系统都要为之分配或回收资源,系统开销较大;而线程切换时,只需保存和设置少量寄存器内容,因此开销很小。
内核线程切换的五段论
进程间通信
临界区
- 临界资源:同时仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如打印机、绘图机等。
- 临界区:每个进程中访问临界资源的一段代码。
互斥的概念与要求
为了禁止两个进程同时进入临界区,软件算法或同步机构都应遵循以下准则:
- 空闲让进:当没有进程处于临界区时,可以允许一个请求进入临界区的进程立即进入自己的临界区。
- 忙则等待:当已有进程进入其临界区时,其他试图进入临界区的进程必须等待。
- 有限等待:对要求访问临界资源的进程,应保证能在有限的时间内进入自己的临界区。
- 让权等待:当一个进程因为某些原因不能进入自己的临界区时,应释放处理器给其他进程。
忙等待的互斥
几种互斥的方案
- 屏蔽中断
在单处理器的系统中,最简单的方法是在每个线程刚刚进入临界区的时候将中断屏蔽,并在离开临界区的时候将中断重新开启.
这个方案并不好,把屏蔽中断的权利交给用户进程是不明智的.如果一个恶意的进程屏蔽中断之后不再打开中断,那就歇逼了.
而且,如果系统是多处理器系统,屏蔽中断只能对执行的那个cpu有效. - 锁变量
锁变量是一种软件方法.设想有一个共享变量,其初始值为0.当一个进程想进入其临界区时,它首先测试这把锁.如果锁的值为0,则该进程将其设置为1并进入临界区.如果是1,就等到变成0再进入.
这种方案还是有问题,原因在于对锁变量的访问不是原子的. - 严格轮换法
严格轮换法的代码示例如下:
while(TRUE){
while(turn != 0);
critical_region();
turn = 1;
noncritical_region();
}
while(TRUE){
while(turn != 1);
critical_region();
turn = 0;
noncritical_region();
}
由代码可以看出,由于turn同一个时刻只能有一个值,所以这种方案可以实现互斥.
但是显然的是,这种方案违反了空闲让进的原则.
- Peterson解法
Peterson解法的代码实现如下:
#define FALSE 0
#define TRUE 1
#define N 2 //进程数量
int turn; //现在轮到谁
int interested[N] ; // 所有值初始化为0(FALSE)
void enter_region(int process){
int other; // 其他进程号
other = 1 - process; // 另一方进程
interested[process] = TRUE; // 表明所感兴趣的
turn = process; // 设置标志
while(turn == process && interested[other] == TRUE); //空语句
}
void leave_region(int process){ // 进程 : 谁离开
interested[process] = FALSE; //表示离开临界区
}
Peterson解法的正确性:
当A进程进入的时候,由于B不想进入,所以很快返回.
当A在临界区,B调用enter_region方法,会挂在while中.
当两个进程同时进入方法体,由于turn只有一个值,所以没有问题.
- 最常用的方法-TSL和XCHG指令
TSL指令的功能是这样的:将一个内存字lock读取到寄存器RX中,然后在该内存地址上存一个非零值.读字和写字操作保证是不可分割的,即该指令结束之前其他处理器均不允许访问改内存字.执行TSL将总线锁住,防止其他CPU在本指令结束前访问内存.
这个方法和锁变量法的思维基本一致,但是借助了硬件实现了原子操作.
enter_region:
TSL REGISTER,LOCK