进程与线程
进程是操作系统的核心概念,是对正在运行程序的一个抽象。
1 进程
1.1 进程模型
进程(process)就是一个正在执行的程序的实例,包括程序计数器、寄存器和变量的当前值。从概念上说,每个进程拥有它自己的虚拟CPU,而真正的CPU在各进程间来回切换。
1.2 进程的创建
4种主要事件会导致进程的创建:
- 系统初始化
- 正在运行的程序执行了创建进程的系统调用
- 用户请求创建一个新进程
- 一个批处理作业的初始化
操作系统初始化时会创建若干个进程,有些是前台进程,有些是后台进程,这些进程与特定用户没有关系,但具有某些专门的功能,称为守护进程(daemon)。
进程创建后,父进程和子进程有各自不同的地址空间。不可写的内存区是共享的,若要子进程共享父进程的所有内存,通常使用写时复制(copy-on–write)共享,即两者之一想要修改部分内存,则这块内存首先被复制,以确保修改发生在私有内存区域。可写的内存是不可以共享的。
1.3 进程的终止
- 正常退出(自愿)
- 出错退出(自愿)
- 严重错误(非自愿)
- 被其他进程杀死(非自愿)。
1.4 进程的层次结构
进程只有一个父进程,但可以有多个子进程。在UNIX中,进程和它的所有子进程以及后裔共同组成一个进程组。所有进程都属于以init为根的一棵树。在Windows中没有进程层次的概念,所有进程地位相同。
1.5 进程的状态
运行态:该时刻进程实际占用CPU。
就绪态:可运行,但因其他进程正在运行而暂时停止。
阻塞态:除非某种外部事件发生,否则进程不能运行。
进程的状态间转换有四种可能:
- 进程因为等待输入而由运行态进入阻塞态
- 当出现有效输入,进程由阻塞态进入就绪态
- 当调度程序选择此进程,由就绪态进入运行态
- 当调度程序选择另一进程,由运行态进入就绪态
1.6 进程的实现
操作系统维护着一张进程表(一个结构数组),每个进程占一个进程表项(或者称为进程控制块),该表项包括程序计数器、堆栈指针、内存分配状况、所打开文件的状态、账号和调度信息及其他进程状态转换的必要信息,以保证该进程随后能再次启动。
中断发生后操作系统底层的工作步骤:
- 硬件压入堆栈程序计数器。
- 硬件从中断向量装入新的程序计数器。
- 汇编语言过程保护寄存器值。
- 汇编语言过程设置新的堆栈。
- C中断服务例程运行。
- 调度程序决定下一个将运行的进程。
- C过程返回至汇编代码。
- 汇编语言过程开始新的当前进程。
2 线程
线程(thread)是操作系统能够进行运算调度的最小单元,被包含在进程之中。
2.1 线程的使用
使用线程的必要性:
- 在进程中同时发生着多种活动,其中某些活动随着时间退役会被阻塞,使用多线程可将其分解为可以准并行运行的多个顺序线程。
- 线程比进程更轻量级,更容易创建,也更容易撤销。多个线程可以共享公共内存,线程之间是没有保护的,这是进程做不到的。
- 若存在大量计算和I/O处理,拥有多个线程允许这些活动彼此重叠进行,从而加快应用程序的执行速度。
- 在多CPU系统中,多线程是有益的,真正的并行有了实现的可能。
2.2 线程模型
与进程一样,线程可以处于若干种状态的任何一个:运行、阻塞、就绪或终止。每个线程有自己的堆栈,所有进程都是平等的。线程通常是有益的,但也让程序设计变得复杂。
2.3 在用户空间中实现线程
实现线程包由两种主要方法:在用户空间中和在内核中。
把整个线程包放在用户空间中,内核对线程包一无所知。优点是用户级线程包可以在不支持线程的操作系统上实现。在用户空间管理线程时,每个进程需要有专用的线程表(thread table),用来跟踪该进程中的线程。用户级线程还允许每个进程有自己的调度算法。
2.4 在内核中实现线程
内核中有用来记录系统中所有线程的线程表,当某个线程希望创建一个新线程或撤销一个已有线程时,它进行一个系统调用,这个系统调用通过对线程表的更新完成线程创建或撤销工作。
2.5 弹出式线程
一个消息的到达导致系统创建一个处理该消息的线程,这种线程称为弹出式线程。
3 进程间通信
进程间通信(Inter Process Communication, IPC)有三个问题:一个进程如何把信息传递给另一个;确保多个进程在关键活动中不会交叉;正确的顺序。这些问题同样适用于线程。
3.1 竞争条件
两个或多个进程读写某些共享数据,最后的结果取决于进程运行的精确时序,称为竞争条件(race condition)。
3.2 临界区
避免竞争条件需要的是互斥(mutual exclusion),即以某种手段确保当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作。把对共享内存进行访问的程序片段称作临界区(critical section),若能适当安排,使两个进程不可能同时处于临界区,就能避免竞争条件。
保证使用共享数据的并发进程能够正确并高效地进行需满足4个条件:
- 任何两个进程不能同时处于其临界区。
- 不应对CPU的速度和数量做任何假设。
- 临界区外运行的进程不得阻塞其他进程。
- 不得使进程无限期等待进入临界区。
3.3 忙等待的互斥
3.4 睡眠与唤醒
生产者-消费者问题:也叫有界缓冲区问题。两个进程共享一个公共的固定大小的缓冲区,其中一个是生产者,将信息放入缓冲区,另一个是消费者,从缓冲区中取出信息。
3.5 信号量
使用一个整型变量来累计唤醒次数,称作信号量(semaphore),一个信号量的取值可以为0(没有保存下来的唤醒操作)或者为正值(有一个或多个唤醒操作)。对信号量的操作必须二维单一的、不可分割的原子操作,原子性对于解决同步问题和避免竞争条件是绝对必要的。原子操作是指一组相关联的操作要么都不间断地执行,要么都不执行。
3.6 互斥量
若不需要信号量的计数能力,可使用信号量的简化版本,称为互斥量(mutex)。互斥量仅适用于管理共享资源或一小段代码,在线程中非常有用。
互斥量是一个可以处于两态之一的变量:解锁和加锁。
3.7 管程
3.8 消息传递
3.9 屏障
3.6 互斥量
若不需要信号量的计数能力,可使用信号量的简化版本,称为互斥量(mutex)。互斥量仅适用于管理共享资源或一小段代码,在线程中非常有用。
互斥量是一个可以处于两态之一的变量:解锁和加锁。
3.7 管程
3.8 消息传递
3.9 屏障