2.1.1 进程的概念和特征
1. 进程的概念
典型的定义有:
- 进程是程序的一次执行过程。
- 进程是一个程序及其数据在处理机上顺序执行时所发生的活动。
- 进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
进程映像
- 相应地,由程序段、相关数据段和PCB三部分构成了进程实体(又称进程映像)。值得注意的是,进程映像是静态的,进程则是动态的。
2. 进程的特征
进程的特征包括动态性、并发性、独立性、异步性。
2.12 进程的状态与转换
1. 进程的状态
进程有五种状态:运行态、就绪态、阻塞态、创建态、终止态。
2. 进程状态的转换
- 就绪态→运行态:处于就绪态的进程被调度后,获得处理机资源(分派处理机时间片), 于是进程由就绪态转换为运行态。
- 运行态→就绪态:处于运行态的进程在时间片用完后,不得不让出处理机,从而进程由运行态转换为就绪态。此外,在可剥夺的操作系统中,当有更高优先级的进程就绪时,调度程序将正在执行的进程转换为就绪态,让更高优先级的进程执行。
- 运行态→阻塞态:进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行态转换为阻塞态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。
- 阻塞态→就绪态:进程等待的事件到来时,如IO操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞态转换为就绪态。
2.1.3 进程的组成
进程由以下三部分组成:
1. 进程控制块
- 为了使参与并发执行的每个程序( 含数据)都能独立地运行,必须为之配置一个专门的数据结构,称为进程控制块(Process Control Block,PCB)。
系统利用PCB来描述进程的基本情况和运行状态,进而控制和管理进程。所谓创建进程,实质上是创建进程实体中的PCB;而撤销进程,实质上是撤销进程的PCB。
PCB是进程存在的唯一标志。
2. 程序段
程序段就是能被进程调度程序调度到CPU执行的程序代码段。注意,程序可被多个进程共享,即多个进程可以运行同一个程序。
3. 数据段
一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行时产生的中间或最终结果。
2.1.4 进程控制
在操作系统中,一般把进程控制用的程序段称为原语,原语的特点是执行期间不允许中断,它是一个不可分割的基本单位。
1. 进程的创建(被动)
[!NOTE] 子进程与父进程
允许一个进程创建另一个进程,此时创建者称为父进程,被创建的进程称为子进程。子进程可以继承父进程所拥有的资源。当子进程被撤销时,应将其从父进程那里获得的资源归还给父进程。此外,在撤销父进程时,通常也会同时撤销其所有的子进程。
在操作系统中,终端用户登录系统、作业调度、系统提供服务、用户程序的应用请求等都会引起进程的创建。
进程创建的过程
操作系统创建一个新进程的过程如下( 创建原语):
- 为新进程分配一个唯一的进程标识号,并申请一个空白PCB (PCB 是有限的)。若PCB申请失败,则创建失败。
- 为进程分配其运行所需的资源,如内存、文件、IO设备和CPU时间等(在PCB中体现)。这些资源或从操作系统获得,或仅从其父进程获得。如果资源不足(如内存),则并不是创建失败,而是处于创建态,等待内存资源。
- 初始化PCB, 主要包括初始化标志信息、初始化处理机状态信息和初始化处理机控制信息,以及设置进程的优先级等。
- 若进程就绪队列能够接纳新进程,则将新进程插入就绪队列,等待被调度运行。
2. 进程的终止(被动)
引起进程终止的事件
引起进程终止的事件主要有:
- 正常结束,表示进程的任务已完成并准备退出运行。
- 异常结束,表示进程在运行时,发生了某种异常事件,使程序无法继续运行,如存储区越界、保护错、非法指令、特权指令错、运行超时、算术运算错、/0故障等。
- 外界干预,指进程应外界的请求而终止运行,如操作员或操作系统干预、父进程请求和父进程终止。
进程终止的过程
操作系统终止进程的过程如下(终止原语):
- 根据被终止进程的标识符,检索出该进程的PCB,从中读出该进程的状态。
- 若被终止进程处于运行状态,立即终止该进程的执行,将处理机资源分配给其他进程。
- 若该进程还有子孙进程,则应将其所有子孙进程终止。
- 将该进程所拥有的全部资源,或归还给其父进程,或归还给操作系统。
- 将该PCB从所在队列(链表)中删除。
3. 进程的阻塞和唤醒(主动)
1. 进程的阻塞
正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新任务可做等,进程便通过调用阻塞原语(Block),使自己由运行态变为阻塞态。可见,阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞态。
进程阻塞的过程
阻塞原语(Block)的执行过程如下:
- 找到将要被阻塞进程的标识号对应的PCB。
- 若该进程为运行态,则保护其现场,将其状态转为阻塞态,停止运行。
- 把该PCB插入相应事件的等待队列,将处理机资源调度给其他就绪进程。
2. 进程的唤醒
当被阻塞进程所期待的事件出现时,如它所期待的I/O操作已完成或其所期待的数据已到达,由有关进程(比如,释放该I/O设备的进程,或提供数据的进程)调用唤醒原语(Wakeup),将等待该事件的进程唤醒。
进程唤醒的过程
唤醒原语(Wakeup)的执行过程如下:
- 在该事件的等待队列中找到相应进程的PCB。
- 将其从等待队列中移出,并置其状态为就绪态。
- 把该PCB插入就绪队列,等待调度程序调度。
Block原语和Wakeup原语是一对作用刚好相反的原语,必须成对使用。
2.1.5 进程的通信
进程通信是指进程之间的信息交换。PV操作是低级通信方式,高级通信方式是指以较高的效率传输大量数据的通信方式。
高级通信方式
高级通信方法主要有以下三类。
1. 共享存储
在通信的进程之间存在块可直接访问的共享空间,通过对这片共享空间进行写/读操作实现进程之间的信息交换。
如图所示。在对共享空间进行写/读操作时,需要使用同步互斥工具(如P操作、V操作),对共享空间的写/读进行控制。
共享存储分类
共享存储又分为两种:
- 低级方式的共享是基于数据结构的共享。
- 高级方式的共享则是基于存储区的共享。
操作系统只负责为通信进程提供可共享使用的存储空间和同步互斥工具,而数据交换则由用户自己安排读/写指令完成。
注意,进程空间一般都是独立的,进程运行期间一般不能访问其他进程的空间,想让两个进程共享空间,必须通过特殊的系统调用实现,而进程内的线程是自然共享进程空间的。
共享存储形象理解
简单理解就是,甲和乙中间有一个大布袋,甲和乙交换物品是通过大布袋进行的,甲把物品放在大布袋里,乙拿走。但乙不能直接到甲的手中拿东西,甲也不能直接到乙的手中拿东西。
2. 消息传递
在消息传递系统中,进程间的数据交换以格式化的消息(Message)为单位。若通信的进程之间不存在可直接访问的共享空间,则必须利用操作系统提供的消息传递方法实现进程通信。
进程通过系统提供的发送消息和接收消息两个原语进行数据交换。这种方式隐藏了通信实现细节,使通信过程对用户透明,简化了通信程序的设计,是当前应用最广泛的进程间通信机制。
在微内核操作系统中,微内核与服务器之间的通信就采用了消息传递机制。
消息传递的分类
- 直接通信方式。发送进程直接把消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息,如图2.3所示。
- 间接通信方式。发送进程把消息发送到某个中间实体,接收进程从中间实体取得消息。这种中间实体一般称为信箱。该通信方式广泛应用于计算机网络中。
3. 管道通信
管道通信允许两个进程按生产者-消费者方式进行通信(见图2.4),生产者向管道的一端写,消费者从管道的另一端读。
数据在管道中是先进先出的。
管道通信的过程(半双工)
- 只要管道非空,读进程就能从管道中读出数据,若数据被读空,则读进程阻塞,直到写进程往管道中写入新的数据,再将读进程唤醒。
- 只要管道不满,写进程就能往管道中写入数据,若管道写满,则写进程阻塞,直到读进程读出数据,再将写进程唤醒。
为了协调双方的通信,管道机制必须提供三方面的协调能力:互斥、同步和确定对方的存在。
管道克服文件通信问题
在Linux中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现如下:
- 限制管道的大小。管道文件是一个固定大小的缓冲区,在Linux中该缓冲区的大小为4KB,这使得它的大小不像普通文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。
- 读进程也可能工作得比写进程快。当所有管道内的数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。
管道的进程继承
管道只能由创建进程所访问,当父进程创建一个管道后, 由于管道是一种特殊文件,子进程会继承父进程的打开文件,因此子进程也继承父进程的管道,并使用它来与父进程进行通信。
注意:从管道读数据是一次性操作,数据一旦被读取,就释放空间以便写更多数据。普通管道只允许单向通信,若要实现父子进程双向通信,则需要定义两个管道。
2.16 线程和多线程模型
1. 线程的基本概念
引入进程和线程的目的
- 引入进程的目的是更好地使多道程序并发执行,提高资源利用率和系统在吐量。
- 而引入线程的目的则是减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
线程定义
线程最直接的理解就是“轻量级进程”,它是一个基本的CPU执行单元,也是程序执行流的最小单元,由线程ID、程序计数器、寄存器集合和堆栈组成。
线程的资源
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
一个线程可以创建和撤销另一个线程, 同一进程中的多个线程之间可以并发执行。线程也有就绪、阻塞和运行三种基本状态。
2. 线程与进程的比较
- 调度
- 传统中进程是资源和独立调度的基本单位。
- 引入线程后,线程是独立调度的基本单位,进程是资源的基本单位。不同进程的线程切换会引起进程切换。
- 并发性。引入线程后,进程可以并发执行,多个线程之间也可以并发执行,提高了系统的吞吐量。
- 拥有资源。进程是资源分配的基本单位。
- 独立性。每个进程都拥有独立的地址空间和资源,除了共享全局变量,不允许其他进程访问。
- 系统开销。同一进程的线程切换要比进程切换开销小的多。
- 支持多处理机系统。对于多线程进程,可以将进程中的多个线程分配到多个处理机上执行。
3. 线程的属性
- 不拥有系统资源,拥有唯一标识符和线程控制块。
- 不同的线程可以执行相同的程序,同一个服务程序被不同用户调用时,操作系统将其创建为不同线程。
- 同一进程的线程共享该进程拥有的全部资源。
- 线程是处理机的独立调度单位。
- 线程也有生命周期,阻塞,就绪,运行等状态。
4. 线程的状态和转换
- 执行状态:线程已获得处理机而正在运行。
- 就绪状态:线程已具备各种执行条件,只需再获得CPU便可立即执行。
- 阻塞状态:线程在执行中因某事件受阻而处于暂停状态。
线程这三种基本状态之间的转换和进程基本状态之间的转换是一样的。
5. 线程的组织与控制
1) 线程控制块
与进程类似,系统也为每个线程配置一个线程控制块TCB,用于记录控制和管理线程的信息。
线程控制块通常包括:
- 线程标识符。
- 一组寄存器,包括程序计数器、状态寄存器和通用寄存器。
- 线程运行状态,用于描述线程正处于何种状态。
- 优先级。
- 线程专有存储区,线程切换时用于保存现场等。
- 堆栈指针,用于过程调用时保存局部变量及返回地址等。
同进程中的所有线程都完全共享进程的地址空间和全局变量。各个线程都可以访问进程地址空间的每个单元,所以一个线程可以读、写或甚至清除另一个线程的堆栈。
2) 线程的创建
线程也是具有生命期的,它由创建而产生,由调度而执行,由终止而消亡。相应地,在操作系统中就有用于创建线程和终止线程的函数(或系统调用)。
线程创建过程
用户程序启动时,通常仅有一个称为“初始化线程”的线程正在执行,其主要功能是用于创建新线程。在创建新线程时,需要利用一个线程创建函数,并提供相应的参数,如指向线程主程序的入口指针、堆栈的大小、线程优先级等。线程创建函数执行完后,将返回一个线程标识符。
3) 线程的终止
当一个线程完成自己的任务后,或线程在运行中出现异常而要被强制终止时,由终止线程调用相应的函数执行终止操作。
6. 线程的实现方式
线程的实现可以分为两类:用户级线程(User-I evel Thread, ULT) 和内核级线程( Kermel-I evel Thread, KLT)。内核级线程又称内核支持的线程。
1) 用户级线程(ULT)
在用户级线程中,有关线程管理(创建、撤销和切换等)的所有工作都由应用程序在用户空间中完成,内核意识不到线程的存在。应用程序可以通过使用线程库设计成多线程程序。
用户级线程的优点
这种实现方式的优点如下:
- 线程切换不需要转换到内核空间,节省了模式切换的开销。
- 调度算法可以是进程专用的,不同的进程可根据自身的需要,对自己的线程选择不同的调度算法。
- 用户级线程的实现与操作系统平台无关,对线程管理的代码是属于用户程序的一部分。
用户级线程的缺点
这种实现方式的缺点如下:
- 系统调用的阻塞问题,当线程执行一个系统调用时,不仅该线程被阻塞,而且进程内的所有线程都被阻塞。
- 不能发挥多处理机的优势,内核每次分配给一个进程的仅有一个CPU,因此进程中仅有一个线程能执行。
2) 内核级线程(KLT)
在操作系统中,无论是系统进程还是用户进程,都是在操作系统内核的支持下运行的,与内核紧密相关。内核级线程同样也是在内核的支持下运行的,线程管理的所有工作也是在内核空间内实现的。
内核空间也为每个内核级线程设置一个线程控制块,内核根据该控制块感知某线程的存在,并对其加以控制。
内核级线程的优点
这种实现方式的优点如下:
- 能发挥多处理机的优势,内核能同时调度同进程中的多个线程并行执行。
- 如果进程中的一个线程被阻塞,内核可以调度该进程中的其他线程占用处理机,也可运行其他进程中的线程。
- 内核支持线程具有很小的数据结构和堆栈,线程切换比较快、开销小。
- 内核本身也可采用多线程技术,可以提高系统的执行速度和效率。
内核级线程的缺点
这种实现方式的缺点如下:
- 同一进程中的线程切换,需要从用户态转到核心态进行,系统开销较大。这是因为用户进程的线程在用户态运行,而线程调度和管理是在内核实现的。
3) 组合方式
在组合实现方式中,内核支持多个内核级线程的建立、调度和管理,同时允许用户程序建立、调度和管理用户级线程。一些内核级线程对应多个用户级线程,这是用户级线程通过时分多路复用内核级线程实现的。同进程中的多个线程可以同时在多处理机上并行执行,且在阻塞一个线程时不需要将整个进程阻塞,所以组合方式能结合KLT和ULT的优点,并且克服各自的不足。
线程库
线程库是为程序员提供创建和管理线程的API。
实现线程库的主要的方法有如下两种:
- 在用户空间中提供一个没有内核支持的库。这种库的所有代码和数据结构都位于用户空间中。这意味着,调用库内的一个函数只导致用户空间中的一个本地函数的调用。
- 实现由操作系统直接支持的内核级的一个库。对于这种情况,库内的代码和数据结构位于内核空间。调用库中的一个API函数通常会导致对内核的系统调用。
7. 多线程模型
根据用户级线程和内核级线程的连接方式的不同,从而形成了下面三种不同的多线程模型:
1) 多对一模型
将多个用户级线程映射到一个内核级线程,如图所示。 这些用户线程一般属于一个进程,线程的调度和管理在用户空间完成。仅当用户线程需要访问内核时,才将其映射到一个内核级线程上,但是每次只允许一个线程进行映射。
优点:
- 线程管理是在用户空间进行的,因而效率比较高。
缺点:
- 如果一个线程在访问内核时发生阻塞,则整个进程都会被阻塞;
- 在任何时刻,只有一个线程能够访问内核,多个线程不能同时在多个处理机上运行。
2) 一对一模型。
将每个用户级线程映射到一个内核级线程,如图所示。
优点:
- 当一个线程被阻塞后,允许调度另一个线程运行,所以并发能力较强。
缺点:
- 每创建一个用户线程,相应地就需要创建一一个内核线程,开销较大。
3) 多对多模型
将n个用户线程映射到m个内核级线程上,要求n≥m, 如图所示。
!
特点:
- 既克服了多对一模型并发度不高的缺点,又克服了一对一模型的一个用户进程占用太多内核级线程而开销太大的缺点。此外,还拥有上述两种模型各自的优点。