1 进程
1.1 进程特性
进程是程序的一次运行过程,它是多任务操作系统的核心概念。进程具有以下几个基本特性:
- 动态性:进程由“创建”而产生,由“撤销”而消亡,因“调度”而运行,因“等待”而停顿。进程从创建到消失的全过程称为进程的生命周期。
- 并发性:在同一时间段内可以有多个进程在系统中活动。它们宏观上是在并发运行,微观上是在交替运行。
- 独立性:进程是可以独立运行的基本单位,是操作系统分配资源和调度管理的基本对象。每个进程都独立的拥有各种必要的资源,独立的占有CPU运行。
- 异步性:每个进程都独立的执行,各自按照不可预知的速度向前推进。进程间的协调运行由操作系统负责。
1.2 进程的基本状态
由于进程的个数总是多于CPU的个数,因此它们需要轮流的占用CPU,在进程的生命周期内,它会在多个状态之间不断的切换。
进程有3个基本的状态:
- 就绪态:进程被创建后就处于就绪态。当被进程调度程序选中后,即进入运行态开始运行。
- 运行态:当进程的时间片耗尽或被有更高优先级的进程就绪时,调度程序将抢占当前进程,被抢占的进程交出CPU,转入就绪态等待下一次被调度。处于运行态的进程如果需要等待某个资源或某个事件发生,则会退出CPU,转入睡眠态,当所等待的资源就绪后,进程被唤醒,进入就绪态准备被调度。处于运行态的进程如果接收到了暂停执行的信号,也会放弃CPU,转入暂停态,当收到恢复执行的信号后,进入就绪态准备被调度。处于运行态的进程执行结束后,会进入僵死态,待父进程进行相应处理后撤销它的PCB,该进程就不复存在了。
- 等待态:进程因某种资源不能满足,或希望的某事件尚未发生,或接收到了暂停的信号,都会进入等待状态。系统中常常会有多个进程处于等待状态,它们按等待的事件分类,排成等待队列。
1.3 进程控制块
进程由程序、数据和进程控制块3个基本部分组成。程序是进程执行的可执行代码,数据是进程所处理的对象,进程控制块用于记录进程的各种信息。
进程控制块(Process Control Block,PCB)是进程的代表,PCB存在则进程就存在,PCB消失则进程也就结束了。操作系统通过PCB来管理进程,了解它的活动情况,通过它对进程实施控制和调度。
进程控制块中主要包含以下信息:
- 进程描述信息:进程标识号(PID)、归属关系(属主和属组标识号)、家族关系(父进程、子进程和兄弟进程)等;
- 进程控制与调度信息:当前状态、调度策略、优先级、时间片等;
- 资源信息:进程的内存地址空间、打开的文件信息、要处理的信号等;
- 现场信息:切换时保存的CPU寄存器内容、进程运行环境等。
1.4 进程地址空间
为了容纳进程的映像,每个进程都需要有一个自己的内存空间。在32位系统中,每个进程通常被限制在4GB的虚拟内存空间中。这4G的虚拟内存被划分为用户态可以访问的用户空间(3G)和核心态可以访问的核心空间(1G)。内核空间的代码和数据由所有进程共享,但每个进程都单独拥有一个内核栈。用户空间的代码和数据由进程独享。
1.5 进程组织结构
操作系统中通常同时存在着成百上千个进程,系统需要采用适当的数据结构来记录和管理这些进程。常用的组织方式有:
- 进程链表:系统将所有的PCB链成一个双向循环链表,PCB通过它的tasks字段链入进程链表。表头指针在0号进程(内核进程)的PCB中,遍历该链表即可顺序的找到每个进程的PCB;
- PID散列表:为了加快查找速度,内核中设置了若干个散列表,其中PID散列表用于将PID映射到进程的PCB,PID散列表是一个链式散列表,所有的PCB都通过pid_chain和pid_list字段链入到这个散列表中,用PID查找散列表就可以快速的找到它的PCB。
- 进程树链表:系统中的所有进程都形成了一棵树,每个进程都是树中的一个节点,树的根是1号进程(init进程/systemd进程),它是所有进程的祖先进程。进程的PCB中有父进程指针parent、子进程指针children和兄弟进程指针sibling,它们共同构造出了进程树的结构。
- 可执行队列:系统将所有处于可执行状态的PCB组织成了可执行队列,处于可执行队列的进程通过PCB中的run_list字段链入适当的队列中,进程切换时,调度程序从可执行队列中选择一个让其运行。
- 等待队列:系统将睡眠的进程分类管理,每类对应一个特定的事件,用一个等待队列链接起来。当某一事件发生时,内核会唤醒相应的等待队列中满足条件的进程,将唤醒的进程节点从队列中删除,并将其PCB加入到可执行队列中去。
2 进程调度
引起进程调度的情况:
- 当前进程放弃CPU,转入睡眠态或僵死态;
- 当前进程的时间片消耗完毕;
- 有更高优先级的进程加入到了可执行队列中。
进程调度的步骤:
(1)选择进程:按照一定的调度算法,从就绪进程中选择一个进程;
(2)切换进程:为换下的进程保留现场信息,为选中的进程恢复现场信息,使其运行。
进程调度算法:
- 先进先出法:按照进程进入可执行队列的先后次序来调度,先进入队列的进程先被调度。这是最简单的调度策略,但缺点是对一些紧迫任务的响应时间可能过长;
- 短进程优先法:优先调度较短的进程运行,以提高系统的吞吐量,但对于长进程不利;
- 时间片轮转法:进程按规定的时间片轮流使用CPU,时间片的选择应该适当,过短会引起频繁的调度,过长会导致响应过慢;
- 优先级调度法:为每个进程设置优先级,调度时优先选择优先级高的进程运行,使得紧迫的任务可以优先得到处理。Linux中又分为静态优先级和动态优先级,静态优先级是预先指定的,动态优先级是随着进程运行时间的长短而动态改变的,两种优先级结合调度,既可以保证对高优先级进程的响应,也不会过度忽略低优先级的进程。
3 进程间通信(IPC)
Linux支持以下几种IPC机制:
- 信号量(semaphore):信号量分为内核信号量和IPC信号量。IPC信号量是用户态进程使用的同步与互斥机制。
- 信号(signal):信号是进程间可相互发送的控制信息,传输的信息量少,用于通知进程有某个事件发生。每个进程都具有接收和处理信号的能力。系统中预定义了一组信号用来控制进程的活动,用户也可以自定义信号来通告进程某个约定事件的发生。
- 管道(pipe):管道是连接两个进程的单向数据传输通道(需要双向通信时,要建立两个管道),一个进程向管道中写入数据,另一个进程从管道中读取数据,从而实现两个进程间同步传递字节流。
- 消息队列(message queue):消息队列是由消息(结构化的数据)链接而成的链式队列,有写权限的进程可以向队列中添加消息,有读权限的进程可以从队列中读取消息,与管道不同的是,消息通信是异步的。
- 共享内存(shared memory):共享内存通信方式就是在内存中开辟一段存储区,将这个区映射到多个进程的地址空间中,使得多个进程可以直接读写这个存储区从而达到数据共享的目的。使用共享内存通信效率高,访问速度快,且可以传递大量数据,但它没有内置的同步机制,需要配合信号量实现进程的同步。
4 进程的互斥与同步
进程之间的制约关系有两种:
- 进程的同步:相关进程为协作完成同一任务而引起的直接制约关系。即进程间为合作完成一个任务而互相等待、协调运行步调。
- 进程的互斥:即进程间因竞争系统资源而引起的间接制约关系。即禁止多个进程同时进入各自的访问同一临界资源的临界区,以保证对临界资源的排他性使用。
Linux中实现进程互斥与同步的主要手段有如下三点:
- 原子操作:原子操作是用特定汇编语言实现的操作,它的操作过程受硬件的支持,具有不可分割性。主要用于实现资源的计数。
- 锁:通过给资源加锁或解锁的方式来实现互斥访问资源,主要用于数据的保护。
- 信号量:信号量是一个具有P操作和V操作的整型变量,用来表示某个临界资源的可用数目,主要用于实现进程间的同步。
死锁问题
死锁(deadlock)是指系统中若干个进程相互无知地等待对方所占有的资源,而又不释放自己所占有的资源,从而无限地处于等待状态的一种僵持局面,其现象是若干个进程均停滞不前,且无法自行恢复。