1进程的概念
1.1程序的顺序执行
一个程序通常由若干个程序段组成,这些程序段必须按照某种先后次序执行,只有在前一个程序段执行完成后,后面的程序段才能执行,这类计算过程就是程序的顺序执行过程。
顺序执行的特征:
- 顺序性。处理机严格按照程序所规定的顺序执行;
- 封闭性。程序一旦开始运行,其执行结果不受外界因素的影响。因为程序运行时独占系统的各种资源,故这些资源的状态(除初始状态外)只有本程序才能改变。
- 可再现性。只要程序执行时的初始条件和执行环境相同,当程序重复执行时,都将获得相同的结果。
1.2程序的并发执行
程序的并发执行是指若干个程序(或程序段)同时在系统中运行,这些程序(或程序段)的执行在时间上是重叠的,即一个程序(或程序段)的执行尚未结束,另一个程序(或程序段)的执行已经开始。
并发执行的特征:
- 间断性。程序并发执行时,由于需要共享资源或为完成同一项任务而相互合作,致使并发程序之间形成了相互制约的关系。这些相互制约的关系将导致并发程序具有“执行-暂停-执行”这种间断性的活动规律。
- 失去封闭性。程序并发执行时,共享系统中的各种资源,这些资源的状态将由多个程序来改变,这将致使程序的运行失去封闭性。
- 不可再现性。程序并发执行时,由于失去了封闭性,也将失去运行结果的可再现性。也就是说,对于同一个程序而言,即使其运行的初始条件相同,但当其重复执行时其运行结果可能不同。
1.3进程的定义与特征
在多道程序的环境下,程序的并发执行代替了程序的顺序执行,程序的活动不再处于封闭系统中,从而出现了许多新的特性。在这种情况下,程序这个静态概念已经不能如实反映程序活动的动态特征。为此人们引入了一个新的概念—-进程。
- 进程是程序在处理器上的一次执行过程。
- 进程是可以和别的计算并行执行的计算
- 进程是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的一个独立单位。
- 进程是一个具有一定功能的程序,是关于某个数据集合的一次运行活动。
进程的基本特征:
- 动态性。进程是程序的一次执行过程,因而是动态的。动态性还表现在它因创建而产生,由调度而执行,因得不到资源而暂停执行,最后由撤销而消亡。
- 并发性。引入进程的目的就是为了使程序能与其他程序并发执行,以提高资源利用率。
- 独立性。进程是一个能独立运行的基本单位,也是系统进行资源分配和调度的独立单位。
- 异步性。进程以各自独立的,不可预知的速度向前推进。
- 结构特征。为了描述和记录进程的运行变化过程,并使之能正确运行,系统应为每个进程配置一个进程控制块。这样,从结构上看,每个进程都由程序段,数据段,进程控制块三部分组成。
1.4进程状态及其变化
进程执行时的动态特性决定了进程具有多种状态。事实上,运行中的进程至少具有以下3种基本状态。
(1)就绪状态。进程已经获得了除处理机以外的所有资源,一旦获得处理机就可以立即执行。
(2)执行状态。又称运行状态。当一个进程获得必要的资源并正在处理机上运行时,此进程所处的状态为执行状态。
(3)阻塞状态。又称等待状态或睡眠状态。正在执行的进程,由于发生某事件而暂时无法继续执行(如等待输入/输出完成),此时进程所处的状态为阻塞状态。
进程并非固定处于某一种状态,其状态会随着自身的运行和外界条件的变化而发生变化。
1.5进程控制块
为了管理和控制进程的运行,系统为每个进程定义了一个数据结构——进程控制块(Process Control Block,PCB),用于记录进程的属性信息。
当创建一个进程时,系统为该进程建立一个PCB;
当进程执行时,系统通过其PCB了解进程的现行状态信息,以便对其进行控制和管理;
当进程结束时,系统收回其PCB,该进程随之消亡。
由此可见,系统根据PCB感知进程的存在,PCB是进程存在的唯一标志。
不同的操作系统的PCB所包含的内容会有差异,但通常应该包含以下内容:
- 进程标识符。每个进程都必须有唯一的标识符,以区别于系统内部的其他进程。在进程创建时,由系统为进程分配唯一的进程标识符。
- 进程当前状态。说明进程的当前状态,以作为进程调度程序分配CPU的依据。
- 进程队列指针。用于记录PCB队列中下一个PCB的地址。为了查找方便,系统中的PCB可能组织成多个队列,如就绪队列、阻塞队列等。
- 程序和数据地址。指出进程的程序和数据所在的内(外)存地址。
- 进程优先级。优先级用于描述进程使用CPU的紧迫程度,是进程调度的一个依据。优先级高的进程通常优先获得CPU。
- CPU现场保护区。当进程因某种原因释放CPU时,要将此时的CPU现场信息(如指令寄存器、状态寄存器、通用寄存器等)保存在该区域中,以便进程重新获得CPU时能恢复原来的CPU现场信息而继续执行。
- 通信信息。记录进程在执行过程中与其他进程所发生的信息交换情况。
- 家族关系。指明本进程的父进程、子进程等家族关系信息(如本进程的子进程与父进程的标识符)
- 占有资源清单。列出进程所需资源及当前已获得的资源。
在一个系统中,通常存在许多进程,为了对它们进行有效管理,应该用适当的方法将PCB组织起来。目前常用链表或表格将PCB组织起来。
2进程控制
进程控制的职责是对系统中的所有进程实施有效的管理。其功能包括:
- 进程的创建
- 进程的撤销
- 进程的阻塞与唤醒
这些功能一般由操作系统内核实现。
2.1核心态与用户态
操作系统内核是基于硬件的第一次软件扩充。在现代操作系统设计中,往往把一些与硬件紧密相关的模块、运行频率较高的模块以及一些公用的基本操作安排在靠近硬件的软件层次中,并使它们常驻内存,以提高操作系统的运行效率。
进程控制功能通过执行各种原语来实现。
原语是由若干条机器指令构成的、用以完成特定功能的一段程序。原语在执行期间不可分割,所以原语操作具有原子性。
为了防止操作系统及其关键数据(如PCB等)受到用户程序有意或无意的破坏,通常将处理机的执行状态分为两种:
- 核心态。又称系统态,是操作系统管理程序执行时机器所处的状态。它具有较高的权限,能执行一切指令,访问所有的寄存器和存储区。
- 用户态。用户程序执行时机器所处的状态。它具有较低的权限,只能执行规定的指令,访问指定的寄存器和存储区。
2.2进程创建
一个进程可以创建一个新的进程(子进程)。子进程又可以创建子进程,这样就能够构成一个进程间的家族关系。
创建原语的主要功能是为被创建进程形成一个PCB。其主要操作过程是:先向系统申请一个空闲PCB,并为子进程分配必要的资源,然后将子进程的PCB初始化,并将此PCB插入就绪队列,最后返回一个进程标志号。
2.3进程撤消
一个进程完成其任务后,应予以撤消,以便及时释放它所占用的各类资源。撤消进程可采用两种撤消策略:一种策略是只撤消指定进程,另一种策略是撤消指定进程及其子孙进程。
撤消原语的主要功能是收回被撤消进程所占用的所有资源,并撤消它的进程控制块。其主要操作过程是:先从PCB集合中找到被撤消进程的PCB,若被撤消进程正处于执行状态,则立即停止该进程的执行,并设置重新调度标志,以便进程撤消后将处理机分配给其他进程。对后一种撤消策略,若被撤消进程有子孙进程,还应将该进程的子孙进程予以撤消。对于被撤消进程所占有的资源,或者归还给父进程,或者归还给系统。最后撤消其进程控制块。
2.4进程阻塞与唤醒
阻塞原语的作用是将进程由执行状态转为阻塞状态,而唤醒原语的作用则是将进程由阻塞状态变为就绪状态。
当一个进程期待的某一事件尚未出现时,该进程调用阻塞原语将自己阻塞起来。阻塞原语的主要操作过程如下:在阻塞一个进程时,由于该进程正处于执行状态,故应中断处理机,保存该进程的CPU现场,停止运行该进程,然后将该进程插入到相应时间的等待队列,再从就绪队列中选择另外一个进程投入运行。
对处于阻塞状态的进程,当该进程期待的事件出现时,由发现者进程调用唤醒原语将阻塞的进程唤醒,使其进入就绪状态。唤醒原语的主要操作如下:将被唤醒进程从相应的等待队列中移出,将状态改为就绪并插入就绪队列。
由此可见,一个进程由执行变为阻塞,是这个进程自己调用阻塞原语。由阻塞变为就绪,是另一个发现者进程调用唤醒原语实现。一般这个发现者进程与被唤醒进程是合作的并发进程。
3线程
3.1线程的概念
在操作系统中引入进程的目的是为了使多道程序并发执行,以改善资源利用率及提高系统吞吐量。
而引入线程的目的是,为了减少程序并发执行时所付出的时空开销,使操作系统具有更好的并发性。
线程的几种描述:
- 线程是进程内的一个执行单元。
- 线程是进程内的一个可调度实体。
- 线程是程序(或进程)中相对独立的一个控制流序列。
- 线程是执行的上下文,其含义是执行的现场数据和其他调度所需的信息。
- 线程是进程内一个相对独立的、可调度的执行单元。
线程自己基本上不拥有资源,只拥有在运行时必不可少的一点资源(如程序计数器、一组寄存器和栈),但它可以与同属一个进程的其他线程共享进程拥有的全部资源。
3.2线程的实现
最自然的方式是由操作系统内核提供线程的控制机制,在只有进程概念的操作系统中可由用户程序利用函数库提供线程的控制机制,还有一种做法是同时在操作系统内核和用户程序两个层次上提供线程控制机制。
内核级线程是指依赖于内核,由操作系统内核完成创建和撤消的线程。在支持内核级线程的操作系统中,内核维护进程和线程的上下文信息,并完成线程切换工作。
用户级线程是指不依赖于操作系统内核,由应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制的线程。由于用户级线程的维护由应用进程完成,不需要操作系统内核了解用户级线程的存在,因此可用于不支持内核级线程的多进程操作系统,甚至是单用户操作系统。
有些操作系统提供了上述两种方法的组合实现。在这种系统中,内核支持多线程的建立、调度与管理;同时,系统中又提供使用线程库的便利,允许用户应用程序建立、调度和管理用户级的线程。由于同时提供内核线程控制机制和用户线程库,因此可以很好地将内核级线程和用户级线程的优点结合起来。
4.互斥与同步的基本概念
并发执行的进程之间存在着不同的相互制约关系,为了协调进程之间的相互制约关系,就需要实现进程的同步。
4.1临界资源
有A、B两个进程共享一台打印机,若让它们任意使用,则可能发生两个进程输出结果交织的情况。解决的办法是进程A要使用打印机时应先提出申请,一旦系统把打印机分配给进程A,打印机就一直被进程A占有,进程B若要使用该打印机,就必须等待,直到进程A用完并释放打印机后,系统才能将该打印机分配给进程B使用。
虽然计算机系统中的多个进程可以共享系统中的各种资源,但是其中许多资源一次只能为一个进程所使用,我们把一次仅允许一个进程使用的资源称为临界资源。
在每个进程中,访问临界资源的那段程序称为临界区。
4.2互斥
在操作系统中,当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一个进程才允许去访问此临界资源,我们称进程之间的这种相互制约关系为互斥。
可以采用软件方法或同步机构来协调。但是,不论是软件方法或是同步机构都应该遵循下述准则:
(1)空闲让进。当没有进程处于临界区时,可以允许一个请求进入临界区的进程立即进入临界区。
(2)忙则等待。当已有进程进入临界区时,其他试图进入临界区的进程必须等待。
(3)有限等待。对要求访问临界资源的进程,应保证能在有限时间内进入临界区。
(4)让权等待。当进程不能进入临界区时,应释放处理机。
4.3同步概念
一般来说,一个进程相对于另一进程的运行速度是不确定的,但是相互合作的几个进程需要在某些确定点上协调它们的工作。所谓进程同步是指多个相互合作的进程,在一些关键点上可能需要互相等待或互相交换信息,这种相互制约关系成为进程同步。
例如,系统中有两个合作的进程,它们共用一个单缓冲去。这两个进程一个为计算进程,完成对数据的计算工作;另一个为打印进程,负责打印计算结果。当计算进程对数据的计算尚未完成时,计算的结果没有送入缓冲区,打印进程不能执行打印操作。一旦计算进程把计算结果送入缓冲区后,就应给打印进程发送一个信号,打印进程收到信号后,便可以从缓冲区中取出计算结果进行打印。在打印进程尚未把缓冲区中的计算结果取出打印之前,计算进程也不能把下一次的计算结果送入缓冲区。只有在打印进程取出缓冲区中的内容,给计算进程发出一个信号后,计算进程才能将下一次的计算结果送入缓冲区。计算进程和打印进程之间就是用这种发信号的方式实现同步的。
5.利用硬件方法解决互斥问题
用软件方法解决互斥问题既困难又复杂,目前已很少使用。
5.1 中断屏蔽方法
当一个进程正在使用处理机执行它的临界区代码时,要防止其他进程再进入其临界区访问的最简单方法是**禁止一切中断发生**,或称之为屏蔽中断、关中断。因为CPU只在发生中断时引起进程切换,这样屏蔽中断就能保证当前运行进程将临界区代码顺利地执行完,从而保证了互斥的正确实现,然后再开中断。
...
关中断;
临界区;
开中断;
...
采用开/关中断的方法实现进程互斥既简单又有效,但同时也存在一些不足,如这种方法限制了处理机交替执行程序的能力,因此执行的效率会明显下降,对于内核来说,当它在执行更新变量或列表的几条指令期间将中断关闭是很方便的,但将关中断的权利交给用户进程是很不明智的,若一个进程关中断后不再开中断,则系统很可能会因此终止。
5.2硬件指令方法
许多计算机中都提供了专门的硬件指令,完成对一个字或字节中内容进行检查和修改或交换两个子或字节内容的功能,利用这些指令可以解决临界区互斥问题。在多道程序环境中,当多个进程共同访问或修改同一个共享变量时,由于中断的原因,使得一个进程对一个共享变量的检查和修改不能作为一个整体来执行,在这两个动作(通常要2-3条指令来完成)之间,可以插入其他进程对此共享变量的访问和修改,从而破坏了此共享变量数据的完整性和正确性。现在我们用一条指令来完成检查和修改两个功能,这样中断就不可能发生,所以不会影响次共享变量数据的完整性。
实现这种功能的硬件指令有两种:
(1)TS(Test and Set)指令。该指令的功能是读出指令标志后把该标志设置为真。TS指令的功能描述如下:
boolean TS(boolean *lock)
{
boolean old;
old=*lock;
*lock=true;
return old;
}
为了实现多个进程对临界资源的互斥访问,可以为每个临界资源设置一个共享布尔变量lock,表示资源的两种状态:true表示正被占用,false表示空闲,初值为false。在进程访问临界资源之前,利用TS检查和修改lock;若有进程在临界区,则重复检查,直到进程退出。利用TS指令实现互斥的算法描述如下:
while TS(&lock);
进程临界区代码
lock = false;
进程的其他代码
(2)Swap指令。该指令的功能是交换两个字(字节)的内容。Swap指令的功能描述是:
Swap(boolean *a,boolean *b)
{
boolean temp;
temp = *a;
*a = *b;
*b = temp;
}
利用Swap指令实现进程互斥时,应为每个临界资源设置一个共享布尔变量lock,初值为false;在每个进程中再设置一个局部布尔变量key,用于与lock交换信息。在进入临界区之前利用Swap指令交换lock和key的内容,然后检查key的状态;有进程在临界区时,重复交换和检查过程,直到进程退出。利用Swap指令实现进程互斥的算法描述如下:
key = true;
while(k!=false)Swap(&lock,&key);
进程的临界区代码
lock=false;
进程的其他代码
6.信号量
6.1信号量及P、V操作
信号量是一个确定的二元组(S、Q),其中S是一个具有非负初值的整型变量,Q是一个初始状态为空的队列。整形变量S表示系统中某类资源的数目,当其值大于0时,表示系统中当前可用资源的数目;当其值小于0时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目。除信号量的初值外,信号量的值仅能由P操作(wait操作)和V操作(signal操作)改变。
一个信号量的建立必须经过说明,即应该准确说明S的意义和初值,每个信号量都有相应的一个队列,在建立信号量时,队列为空。
P、V操作以原语方式实现,信号量的值仅能由这两条原语加以改变。P、V操作的定义如下:
P操作。P操作记为P(S),其中S为一个信号量,它执行时主要完成下述动作:
- S=S-1
- 若S≥0,则进程继续执行;否则,S<0,阻塞该进程,并将它插入该信号量的等待队列。
V操作。V操作记为V(S),S为一个信号量,它执行时主要完成下述动作:
- S=S+1
- 若S>0,则进程继续执行;若S≤0,从信号量等待队列中移出第一个进程,使其变为就绪状态,并插入就绪队列,然后再返回原进程继续执行。
6.2利用信号量实现互斥
设S为两个进程P1、P2实现互斥的信号量,由于每次只允许一个进程进入临界区,所以S的初始值应该为1(即可用资源数目为1)。只需要把临界区置于P(S)和V(S)之间,即可以实现两个进程的互斥。互斥访问临界区的描述如下:
main()
{
semaphore S=1;
cobegin;
P1();
P2();
coend;
}
P1()
{
......
P(S);
进程P1的临界区
V(S);
......
}
P2()
{
......
P(S);
进程P2的临界区
V(S);
......
}
6.3利用P、V操作描述前趋关系
若干进程为了完成一个共同任务而并发执行。而这些并发进程之间根据逻辑上的需要,有的操作可以没有时间上的先后次序,有的操作要有一定的先后次序(也就是说必须要遵循一定的同步规则,只有这样,并发执行的最后结果才是正确的)。
例如,P1,P2,P3,P4,P5,P6为一组合作进程,其前趋图如下,试用P,V操作实现这6个进程的同步。
任务启动后P1先执行,当它结束后P2,P3可以开始执行,P2完成后,P4,P5可以开始执行,仅当P3,P4,P5执行完后,P6才能开始执行。为了确保这一执行顺序,设置5个同步信号量f1、f2、f3、f4、f5分别表示P1,P2,P3,P4,P5是否执行完成,其初值均为0。这6个进程的同步描述如下:
semaphore f1=0;
semaphore f2=0;
semaphore f3=0;
semaphore f4=0;
semaphore f5=0;
mian()
{
cobegin
P1();
P2();
P3();
P4();
P5();
P6();
coend
}
p1()
{
......
v(f1);
v(f1);//通知P2、P3可以执行了
}
p2()
{
P(f1);//等待P1执行完
......
v(f2);
v(f2);//通知P4,P5可以执行了
}
p3()
{
p(f1);//等待P1执行完
......
v(f3);//通知P6
}
p4()
{
p(f2);//等待P2执行完
......
v(f4);//通知P6
}
p5()
{
p(f2);//等待P2执行完
......
v(f5);//通知P6
}
p6()
{
p(f3);
p(f4);
p(f5);
}
6.4生产者-消费者问题
生产者与消费者共享一个有界缓冲区,生产者向其中投放产品,消费者从中取得产品。生产者消费者问题是许多相互合作进程的一种抽象,例如,在输入时,输入进程是生产者,计算进程是消费者;在输出时,计算进程是生产者,打印进程是消费者。
把一个长度为
n
的有界缓冲区(
为了解决生产者-消费者问题,应该设置两个同步信号量,一个说明空缓冲单元的数目,用empty表示,其初值为有界缓冲区的大小n,另一个说明满缓冲单元的数目,用full表示,其初值为0.
本例中生产者进程
P1,P2......Pm
和消费者进程
C1,C2......Ck
,在执行生产和消费活动中要对有界缓冲区进行操作,而有界缓冲区是一个临界资源,必须互斥使用,因此,另外还需要设置一个互斥信号量mutex,其初值为1.生产者-消费者问题的同步描述如下:
semaphore full = 0;
semaphore empty = n;
semaphore mutex = 1;
main()
{
cobegin
producer();
consumer();
coend
}
producer()
{
while(true)
{
生产一个产品
p(empty);
p(mutex);
将一个产品送入缓冲区;
v(mutex);
v(full);
}
}
consumer()
{
while(true)
{
p(full);
p(mutex);
从缓冲区取出一个产品
v(mutex);
v(empty);
消费一个产品
}
}
7.管程
管程定义了一个数据结构和能为并发进程所执行的一组操作,这组操作能使进程同步和改变管程中的数据。
管程由三部分组成:
- 局部于管程的共享变量说明
- 对该数据结构进行操作的一组过程
- 对局部于管程的数据设置初始值的语句
管程有以下基本特性:
- 局部于管程的数据只能被局部于管程内的过程所访问
- 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
- 每次仅允许一个进程在管程内执行某个内部过程。即进程互斥地通过调用内部过程进入管程。当某进程在管程内执行时,其他想进入管程的进程必须等待。
因为管程是一个语言成分,所以管程的互斥访问完全由编译程序在编译时自动添加,无需程序员关注,而且保证正确。
为了实现进程间的同步,管程还必须包含若干用于同步的设施:
- 局限于管程并仅能从管程内进行访问的若干条件变量,用于区别各种不同的等待原因。
- 在条件变量上进行操作的两个函数过程Cwait和Csignal。Cwait(c)将调用此函数的进程阻塞在与该条件变量相关的队列中,并使管程成为可用,即允许其他进程进入管程。Csignal(c)唤醒在该条件变量上阻塞的进程,如果有多个这样的进程则选择其中的一个进程唤醒,如果该条件变量上没有阻塞进程,则什么也不做。
8.进程通信
进程间的信息交换称为进程通信。
前面的互斥与同步就是一种进程间的通信方式。由于进程互斥与同步交换的信息量较少且效率较低,因此称这两种通信方式为低级进程通信方式,相应地也将P,V原语称为两条低级进程通信原语。
高级通信方式是指进程之间以较高的效率传送大量数据。目前高级进程通信方式分为三大类:
- 共享存储器系统
为了传输大量数据,在存储器中划出一块共享存储区,诸进程可以通过对共享存储区进行读或写来实现通信。进程在通信前,应向系统申请建立一个共享存储区,并指定该共享存储区的关键字;若该共享存储区已经建立,则将该共享存储区的描述符返回给申请者。然后,申请者把获得的共享存储区附接到本进程的地址空间上;这样,进程便可以像读写普通存储器一样地读写共享存储区。 消息传递系统
在消息传递系统中,进程间的数据交换以消息为单位,程序员直接利用系统提供的一组通信命令(原语)来实现通信。操作系统隐藏了通信的实现细节,大大简化了通信程序编制的复杂性,因而获得了广泛的应用。
消息传递系统因其实现方式不同,又可以分为:- 直接通信方式。发送进程直接把消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息。
- 间接通信方式。发送进程把消息发送到某个中间实体中,接收进程从中间实体中取得消息。这种中间实体一般称为信箱,故这种通信方式也称为信箱通信方式。
- 管道通信系统
管道是用于连接读进程和写进程以实现它们之间通信的共享文件,向管道提供输入的发送进(即写进程)以字符流形式将大量的数据送入管道,而接收管道输出的接收进程(即读进程)可以从管道中接收数据。