进程管理
进程的基本概念
程序的顺序执行及其特征
-
程序的顺序执行:仅当前一操作(程序段)执行完后,才能执行后续操作。
-
程序顺序执行时的特征:顺序性,封闭性,可再见性。
前趋图
前趋图(Precedence Graph)是一个有向无循环图,记为DAG(Directed Acycilc Graph),用于描述进程之间执行的前后关系。图中的每一个节点可用于描述一个程序段或进程,乃至一条语句。结点间的有向边则用于表示两个结点之间存在的偏序(Partial Order)或前趋关系(Precedence Relation)“→”
→={(Pi, Pj)|Pi must complete before Pj may start}, 如果(Pi, Pj)∈→,可写成Pi→Pj,称Pi是Pj的直接前趋,而称Pj是Pi的直接后继。在前趋图中,把没有前趋的结点称为初始结点(Initial Node),把没有后继的结点称为终止结点(Final Node)
程序的并发执行及其特征
- 程序的并发执行
-
程序并发执行时的特征
-
间断性
-
失去封闭性
-
不可再现性
进程的特征和状态
进程的特征和定义
结构特征:
-
动态性
-
并发性
-
独立性
-
异步性
较典型的进程定义:
-
进程是程序的一次执行。
-
进程是一个程序及其数据在处理机上顺序执行时所发生的活动。
-
进程是出现在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
进程的三种基本状态
-
就绪(Ready)状态
-
执行状态
-
阻塞状态
挂起状态
引起挂起状态的原因:
-
终端用户的请求;
-
父进程请求;
-
负责调节的需求;
-
操作系统的需要
进程状态的转换
-
活动就绪-->静止就绪
-
活动阻塞-->静止阻塞
-
静止就绪-->活动就绪
-
静止阻塞-->活动阻塞
具有挂起状态的进程状态图
进程控制块(PCB)
进程控制块的作用:进程控制块的作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程。
进程控制块中的信息
-
进程标识符: 进程标识符用于惟一地标识一个进程。一个进程通常有两种标识符:
-
内部标识符:在所有的操作系统中,都为每一个进程赋予一个惟一的数字标识符,它通常是一个进程的序号。 设置内部标识符主要是为了方便系统使用
-
外部标识符:它由创建者提供,通常是由字母、数字组成,往往是由用户(进程)在访问该进程时使用。为了描述进程的家族关系, 还应设置父进程标识及子进程标识。此外,还可设置用户标识,以指示拥有该进程的用户
-
-
处理机状态:处理机状态信息主要是由处理机的各种寄存器中的内容组成的。
-
进度调度信息:在PCB中还存放一些与进程调度和进程对换有关的信息,包括:
-
进程状态,指明进程的当前状态,作为进程调度和对换时的依据
-
进程优先级,用于描述进程使用处理机的优先级别的一个整数,优先级别高的进程应优先获得处理机
-
进程调度所需的其它信息,它们与所采用的进程调度算法有关。
-
事件,是指进程由执行状态转变为阻塞状态所等待发生的事件,即阻塞原因。
-
-
进程控制信息
-
程序和数据的地址,是指进程的程序和数据所在的内存或外存地(首)址,以便再调度到该进程执行时,能从PCB中找到其程序和数据
-
进程同步和通信机制,指实现进程同步和进程通信时必需的机制,如信息队列指针、信号量等,他们可能全部或部分地放在PCB中
-
资源清单,是一张列出了除CPU以外的、进程所需的全部资源及已经分配到该进程的资源的清单。
-
链接指针,它给出了本进程(PCB)所在队列中的下一个进程的PCB的首地址。
-
进程控制块的组织方式
- 链接方式
PCB链表队列示意图:
- 索引方式
进程控制
进程的创建
进程图(Process Graph)
引起创建进程的事件
- 用户登录
- 作业调度
- 提供服务
- 应用请求
进程的创建
- 申请空白PCB
- 为新进程分配资源
- 初始化进程控制块
- 将新进程插入就绪队列,如果进程就绪队列能够接纳新进程,便将新进程插入就绪队列。
进程的终止
引起进程终止的事件
- 正常结束 在任何计算机系统中,都应有一个用于表示进程已经运行完成的指示。
- 异常结束 在进程运行期间,由于出现某些错误和故障而迫使进程终止。
- 越界错误。这是指程序所访问的存储区,已越出该进程的区域;
- 保护错。进程试图去访问一个不允许访问的资源或文件,或者以不适当的方式进行访问,例如,进程试图去写一个只读文件;
- 非法指令。程序试图去执行一条不存在的指令。出现该错误的原因,可能是程序错误地转移到数据区,把数据当成了指令;
- 特权指令错。用户进程试图去执行一条只允许OS执行的指令;
- 运行超时。
- 等待超时。
- 算术运算错。进程试图去执行一个被禁止的运算,例如,被0除;
- I/O故障。这是指在I/O过程中发生了错误等
- 外界干预 外界干预并非指在本进程运行中出现了异常事件,而是指进程应外界的请求而终止运行。
- 操作员或操作系统干预
- 父进程请求
- 父进程终止
进程的终止过程
- 根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态
- 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度
- 若该进程还有子孙进程,还应将其所有子孙进程予以终止,以防他们成为不可控的进程
- 将被终止进程所拥有的全部资源,或者归还给其父进程, 或者归还给系统
- 将被终止进程(它的PCB)从所在队列(或链表)中移出, 等待其他程序来搜集信息
进程的阻塞与唤醒
引起进程阻塞和唤醒的事件
- 请求系统服务
- 启动某种操作
- 新数据尚未到达
- 无工作可做
进程阻塞过程
正在执行的进程,当发现上述某事件时,由于无法继续执行,于是进程便通过调用阻塞原语block把自己阻塞。可见,进程的阻塞是进程自身的一种主动行为。进入block过程后,由于此时该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的现行状态由“执行”改为阻塞,并将PCB插入阻塞队列。如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞(等待)队列。 最后,转调度程序进行重新调度,将处理机分配给另一就绪进程,并进行切换,亦即,保留被阻塞进程的处理机状态(在PCB中),再按新进程的PCB中的处理机状态设置CPU的环境。
进程唤醒过程
当被阻塞进程所期待的事件出现时,如I/O完成或其所期待的数据已经到达,则由有关进程(比如,用完并释放了该I/O设备的进程)调用唤醒原语wakeup( ),将等待该事件的进程唤醒。
原语执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中
进程的挂起与激活
- 进程的挂起 当被阻塞进程所期待的事件出现时,如I/O完成或其所期待的数据已经到达,则由有关进程(比如,用完并释放了该I/O设备的进程)调用唤醒原语wakeup( ),将等待该事件的进程唤醒。 >唤醒原语执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中
- 进程的激活过程 当发生激活进程的事件时,例如,父进程或用户进程请求激活指定进程,若该进程驻留在外存而内存中已有足够的空间时,则可将在外存上处于静止就绪状态的进程换入内存。这时,系统将利用激活原语active( )将指定进程激活。 >激活原语先将进程从外存调入内存,检查该进程的现行状态,若是静止就绪,便将之改为活动就绪;若为静止阻塞便将之改为活动阻塞。假如采用的是抢占调度策略,则每当有新进程进入就绪队列时,应检查是否要进行重新调度,即由调度程序将被激活进程与当前进程进行优先级的比较,如果被激活进程的优先级更低,就不必重新调度;否则,立即剥夺当前进程的运行,把处理机分配给刚被激活的进程。
进程同步
进程同步的主要任务是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源和相互合作,从而使程序的执行具有可再现性。
进程同步的基本概念
- 两种形式的制约关系
- 间接相互制约关系,源于资源共享。
- 直接相互制约关系,源于进程间的合作。
-
临界资源(Critical Resouce) 许多硬件资源如打印机、磁带机等,都属于临界资源,诸进程间应采取互斥方式,实现对这种资源的共享。
生产者-消费者(producer-consumer)问题:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中; 消费者进程可从一个缓冲区中取走产品去消费。尽管所有的生产者进程和消费者进程都是以异步方式运行的,但它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品;也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。
我们可利用一个数组来表示上述的具有n个(0,1,…,n-1)缓冲区的缓冲池。用输入指针in来指示下一个可投放产品的缓冲区,每当生产者进程生产并投放一个产品后,输入指针加1;用一个输出指针out来指示下一个可从中获取产品的缓冲区,每当消费者进程取走一个产品后,输出指针加1。 由于这里的缓冲池是组织成循环缓冲的,故应把输入指针加1表示成 in∶=(in+1)mod n;输出指针加1表示成out∶=(out+1) mod n。当(in+1) mod n=out时表示缓冲池满;而in=out则表示缓冲池空。此外,还引入了一个整型变量counter, 其初始值为0。每当生产者进程向缓冲池中投放一个产品后,使counter加1;反之,每当消费者进程从中取走一个产品时, 使counter减1。生产者和消费者两进程共享下面的变量:
Var n, integer; type item=…; var buffer:array[0, 1, …, n-1] of item; in, out: 0, 1, …, n-1; counter: 0, 1, …, n;
指针in和out初始化为1。在生产者和消费者进程的描述中,no-op是一条空操作指令,while condition do no-op语句表示重复的测试条件(condication),重复测试应进行到该条件变为false(假),即到该条件不成立时为止。在生产者进程中使用一局部变量nextp,用于暂时存放每次刚生产出来的产品;而在消费者进程中,则使用一个局部变量nextc,用于存放每次要消费的产品。 producer: repeat ... produce an item in nextp; ... while counter=n do no-op; buffer[in]:=nextp; in:=(in+1)mod n; counter: =counter+1; until false; consumer: repeat while counter=0 do no-op; nextc: =buffer[out]; out: =(out+1) mod n; counter: =counter-1; consumer the item in nextc; until false;
虽然上面的生产者程序和消费者程序,在分别看时都是正确的,而且两者在顺序执行时其结果也会是正确的,但若并发执行时,就会出现差错,问题就在于这两个进程共享变量counter。生产者对它做加1操作,消费者对它做减1操作,这两个操作在用机器语言实现时, 常可用下面的形式描述:
register 1:=counter; register 2:=counter; register1:=register 1+1; register 2:=register 2-1; counter:=register 1; counter: =register 2;
假设:counter的当前值是5。如果生产者进程先执行左列的三条机器语言语句,然后消费者进程再执行右列的三条语句, 则最后共享变量counter的值仍为5;反之,如果让消费者进程先执行右列的三条语句,然后再让 生产者进程执行左列的三条语句,counter值也还是5,但是,如果按下述顺序执行: register 1 :=counter; (register 1=5) register 1 :=register 1+1; (register 1=6) register 2 :=counter; (register 2=5) register 2 :=register 2-1; (register 2=4) counter :=register 1; (counter=6) counter :=register 2; (counter=4)
正确的counter值应该是5,但是现在是4.为了预防产生这种错误,解决此问题的关键是应把变量counter作为临界资源处理,即,令生产者进程和消费者进程互斥地访问变量counter。
-
同步机制应遵循的规则
- 空闲让进
- 忙则等待
- 有限等待
- 让权等待
信号量机制
整型信号量
最初由Dijkstra把整型信号量定义为一个用于表示资源数目的整型量S,除初始化外,仅能通过两个标准的原子操作(Atomic Operation) wait(S)和signal(S)来访问。 这两个操作被分别称为P、V操作。 wait和signal操作可描述为:
wait(S): while S≤0 do no-op;
S:=S-1;
signal(S):S:=S+1;
记录型信号量
在信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表L,用于链接等待进程。记录型信号量是由于它采用了记录型的数据结构而得名的。它所包含的上述两个数据项可描述为:
type semaphore=record
value:integer;
L:list of process;
end
相应地,wait(S)和signal(S)操作可描述为:
procedure wait(S)
var S: semaphore;
begin
S.value∶ =S.value-1;
if S.value<0 then
block(S,L)
end
procedure signal(S)
var S: semaphore;
begin
S.value∶ =S.value+1;
if S.value≤0 then
wakeup(S,L);
end
在记录型信号量机制中,S.value的初值表示系统中某类资源的数目, 因而又称为资源信号量,对它的每次wait操作,意味着进程请求一个单位的该类资源,因此描述为S.value∶ =S.value-1; 当S.value<0时,表示该类资源已分配完毕,因此进程应调用block原语,进行自我阻塞,放弃处理机,并插入到信号量链表S.L中。可见,该机制遵循了“让权等待”准则。 此时S.value的绝对值表示在该信号量链表中已阻塞进程的数目。 对信号量的每次signal操作,表示执行进程释放一个单位资源,故S.value∶ =S.value+1操作表示资源数目加1。 若加1后仍是S.value≤0,则表示在该信号量链表中,仍有等待该资源的进程被阻塞,故还应调用wakeup原语,将S.L链表中的第一个等待进程唤醒。如果S.value的初值为1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量。
AND型信号量
AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部地分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源,也不分配给他。亦即,对若干个临界资源的分配,采取原子操作方式:要么全部分配到进程,要么一个也不分配。 由死锁理论可知,这样就可避免上述死锁情况的发生。为此,在wait操作中,增加了一个“AND”条件,故称为AND同步,或称为同时wait操作, 即Swait(Simultaneous wait)定义如下:
Swait(S1, S2, …, Sn)
if Si≥1 and … and Sn≥1 then
for i∶ =1 to n do
Si∶=Si-1;
endfor
else
place the process in the waiting queue associated with the first Si found with Si<1, and set the program count of this process to the beginning of Swait operation
endif
Ssignal(S1, S2, …, Sn)
for i∶ =1 to n do
Si=Si+1;
Remove all the process waiting in the queue associated with Si into the ready queue.�
endfor;
信号量集
一般“信号量集”的几种特殊情况: (1) Swait(S, d, d)。 此时在信号量集中只有一个信号量S, 但允许它每次申请d个资源,当现有资源数少于d时,不予分配。 (2) Swait(S, 1, 1)。 此时的信号量集已蜕化为一般的记录型信号量(S>1时)或互斥信号量(S=1时)。 (3) Swait(S, 1, 0)。这是一种很特殊且很有用的信号量操作。当S≥1时,允许多个进程进入某特定区;当S变为0后,将阻止任何进程进入特定区。换言之,它相当于一个可控开关。
信号量的应用
- 利用信号量实现进程互斥 Var mutex:semaphore:=1; begin parbegin process 1: begin repeat wait(mutex); critical section signal(mutex); remainder seetion until false; end process 2: begin repeat wait(mutex); critical section signal(mutex); remainder section until false; end parend
- 利用信号量实现前趋关系 设有两个并发执行的进程P1和P2.P1中有语句S1;P2中有语句S2。我们希望在S1执行后再执行S2,为实现这种前趋关系,我们只需使进程P1和P2共享一个公共信号值S,并赋予其初值为0,将signal(S)操作放在语句S1后面;而在S2语句前面插入wait(S)操作,即 在进程P1中,用S1: signal(S); 在进程P2中,用wait(S);S2; 由于S被初始化为0,这样,若P2先执行必定阻塞,只有在进程P1执行完S1;signal(S);操作后使S增为1时,P2进程方能执行语句S2成功。
管程机制
管程(Monitors):一种新的进程同步工具 1. 管程的定义 管程由四部分组成: - 管程的名称、 - 局部于管程内部的分享数据结构说明、 - 对该数据进行操作的一组过程、 - 对局部于管程内部的共享数据设置初始值的语句。
管程相当于围墙,它把共享变量和对它进行操作的若干过程围了起来,所有进程要访问临界资源时,都必须经过管程(相当于通过围墙的门)才能进入,而管程每次只准许一个进程进入管程,从而实现了进程互斥。 管程的特性:
- 模块化
- 抽象数据类型
- 信息掩蔽
2.条件变量 考虑一种情况:当一个进程调用了管程,在管程中时被阻塞或挂起,直到阻塞或挂起的原因解除,而在此期间,如果该进程不释放管程,则其他进程无法进入管程,被迫长时间地等待。为了解决这个问题,引入了条件变量condition。对这些条件变量的访问,只能在管程中进行。 管程中对每一个条件变量都需予以说明,其形式为:Var x,y:condition。对条件变量的操作仅仅是wait和signal,因此条件变量也是一种抽象数据类型,每个条件变量保存了一个链表,用来记录因该条件变量而阻塞的所有进程,同时提供的两个操作即可表示为x.wait和x.signal。其含义为
- x.wait:正在调用管程的进程因x条件需要被阻塞或挂起,则调用x.wait将自己插入到x条件的等待队列上,并释放管程,直到x条件变化。此时其他进程可以使用该管程。
- x.signal:正在调用管程的进程发现x条件发生了变化,则调用x.signal,重新启动一个因x条件而阻塞或挂起的进程。如果存在多个这样的进程,则选择其中一个,如果没有,则继续执行原进程,而不产生任何结果。这和信号量机制中的signal操作不同,因为后者总要执行s:=s+1操作,因而总会改变信号量的状态。 如果有进程Q因x条件处于阻塞状态,当正在调用管程的进程P执行了x.signal操作后,进程Q被重新启动了。此时两个进程P和Q,如何确定那个执行,哪个等待,可采用下述两种方式之一进行处理:
- P等待,直至Q离开管程或等待另一条件
- Q等待,直至P离开管程或等待另一条件
进程通信
信号量机制作为同步工具是卓有成效的,但作为通信工具,则不够理想,主要表现在两个方面:
- 效率低
- 通信对用户不透明
本节所介绍的是高级进程通信,是指用户可直接利用操作系统所提供的一组通信命令高效地传送大量数据的一种通信方式。操作系 统隐藏了进程通信的实现细节。即通信过程对用户是透明的,这样就大大减少了通信程序编制上的复杂性。
进程通信的类型
目前,高级通信机制可归结为三大类:共享存储器系统、消息传递系统以及管道通信系统。 1. 共享存储器系统 在共享存储器系统(Shared-Memory System)中,相互通信的进程共享某些数据结构或共享数据存储区,进程之间能够通过这些空间进行通信。 1. 基于共享数据结构的通信方式。 在这种通信方式中,要求诸进程公用某些数据结构,借以实现诸进程间的信息交换。这种通信方式是低效的,只适于传递相对少量的数据。 2. 基于共享存储区的通信方式 为了传输大量数据、在存储器中划出了一块共享存储区,诸进程可通过对共享存储区中数据的读或写来实现通信。进程在通信前,先向系统申请获得共享存储区中的一个分区,并指定该分区的关键字;若系统已经给其他进程分配了这样的分区,则将该分区的描述符返回给申请者,继之,由申请者把获得的共享存储区连接到本进程中;此后,便可像读、写普通存储器一样地读、写该公用存储分区。 2. 消息传递系统 消息传递系统(Message passing system)是当前应用最广泛的一种进程间的通信机制。在该机制中,进程间的数据交换是以格式化的消息(message)为单位的;在计算机网络中又把message称为报文。程序员直接利用操作系统提供的一组通信命令(原语),不仅能实现大量数据的传递,而且还隐藏了通信的实现细节,是通信过程对用户是透明的,从而大大减化了通信程序编制的复杂性。 3. 管道通信 所谓"管道",是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件。向管道(共享文件)提供输入
线程
线程的基本概念
进程的操作
- 创建进程
- 撤消进程
- 进程切换
线程的属性
- 轻型实体
- 独立调度和分派的基本单位
- 可并行执行
- 共享进程资源
线程的状态
状态参数
在OS中的每一个线程都可以利用线程标识符和一组状态参数进行描述。状态参数通常有这样几项:
- 寄存器状态,包括程序计数器PC和堆栈指针中的内容;
- 堆栈,在堆栈中通常保存有局部变量和返回地址
- 线程运行状态,用于描述线程正处于何种运行状态
- 优先级,描述线程执行的优先程序
- 线程专有存储器,用于保存线程自己的局部变量拷贝
- 信号屏蔽,即对某些信号加以屏蔽
线程运行状态
线程在运行时,具有三种基本状态:
- 执行状态,表示线程正获得处理机而运行;
- 就绪状态,指线程已具备了各种执行条件,一旦获得CPU便可执行的状态
- 阻塞状态,指线程在执行中因某事件而受阻,处于暂停执行时的状态
线程的创建和终止
在多线程OS环境下,应用程序在启动时,通常仅有一个线程在执行,该线程被人们称为“初始化线程”。它可根据需要再去创建若干个线程。在创建新线程时,需要利用一个线程创建函数(或系统调用),并提供相应的参数,如指向线程主程序的入口指针、堆栈的大小,以及用于调度的优先级等。在线程创建函数执行完后,将返回一个线程标识符供以后使用。 终止线程的方式有两种:一种是在线程完成了自己的工作后自愿退出;另一种是线程在运行中出现错误或由于某种原因而被其它线程强行终止。
多线程OS中的进程
在多线程OS中,进程是作为拥有系统资源的基本单位,通常的进程都包含多个线程并为它们提供资源,但此时的进程就不再作为一个执行的实体。 多线程OS中的进程有以下属性: 1. 作为系统资源分配的单位 2. 可包括多个线程 3. 进程不是一个可执行的实体
线程间的同步和通信
- 互斥锁(mutex) 互斥锁是一种比较简单的、用于实现进程间对资源互斥访问的机制。由于操作互斥锁的时间和空间开锁都较低, 因而较适合于高频度使用的关键共享数据和程序段。互斥锁可以有两种状态, 即开锁(unlock)和关锁(lock)状态。 相应地,可用两条命令(函数)对互斥锁进行操作。其中的关锁lock操作用于将mutex关上,开锁操作unlock则用于打开mutex。
- 条件变量 每一个条件变量通常都与一个互斥锁一起使用,亦即,在创建一个互斥锁时便联系着一个条件变量。单纯的互斥锁用于短期锁定,主要是用来保证对临界区的互斥进入。而条件变量则用于线程的长期等待, 直至所等待的资源成为可用的。 线程首先对mutex执行关锁操作,若成功便进入临界区,然后查找用于描述资源状态的数据结构,以了解资源的情况。 只要发现所需资源R正处于忙碌状态,线程便转为等待状态, 并对mutex执行开锁操作后,等待该资源被释放; 若资源处于空闲状态,表明线程可以使用该资源,于是将该资源设置为忙碌状态,再对mutex执行开锁操作。 下面给出了对上述资源的申请(左半部分)和释放(右半部分)操作的描述: Lock mutex Lock mutex check data structures; mark resource as free; while(resource busy); unlock mutex; wait(condition variable); wakeup(condition variable); mark resource as busy; unlock mutex;
- 信号量机制
- 私有信号量(private samephore) 当某线程需利用信号量来实现同一进程中各线程之间的同步时,可调用创建信号量的命令来创建一私用信号量,其数据结构是存放在应用程序的地址空间中。私用信号量属于特定的进程所有,OS并不知道私用信号量的存在,因此,一旦发生私用信号量的占用者异常结束或正常结束,但并未释放该信号量所占有空间的情况时,系统将无法使它恢复为0(空), 也不能将它传送给下一个请求它的线程。
- 公有信号量(public samephore) 公用信号量是为实现不同进程间或不同进程中各线程之间的同步而设置的。由于它有着一个公开的名字供所有的进程使用,故而把它称为公用信号量。其数据结构是存放在受保护的系统存储区中,由OS为它分配空间并进行管理,故也称为系统信号量。如果信号量的占有者在结束时未释放该公用信号量,则OS会自动将该信号量空间回收,并通知下一进程。可见,公用信号量是一种比较安全的同步机制。 ##内核支持线程和用户级线程
-
内核支持线程 这里所谓的内核支持线程,也都同样是在内核的支持下运行的,即无论是用户进程中的线程,还是系统进程中的线程,他们的创建、撤消和切换等,也是依靠内核实现的。此外,在内核空间还为每一个内核支持线程设置了一个线程控制块, 内核是根据该控制块而感知某线程的存在的,并对其加以控制。
-
用户级线程 用户级线程仅存在于用户空间中。对于这种线程的创建、 撤消、线程之间的同步与通信等功能,都无须利用系统调用来实现。对于用户级线程的切换,通常是发生在一个应用进程的诸多线程之间,这时,也同样无须内核的支持。由于切换的规则远比进程调度和切换的规则简单,因而使线程的切换速度特别快。可见,这种线程是与内核无关的。
线程控制
内核支持线程的实现
用户级线程的实现
- 运行时系统(Runtime System) 所谓“运行时系统”,实质上是用于管理和控制线程的函数(过程)的集合, 其中包括用于创建和撤消线程的函数、 线程同步和通信的函数以及实现线程调度的函数等。正因为有这些函数,才能使用户级线程与内核无关。运行时系统中的所有函数都驻留在用户空间,并作为用户级线程与内核之间的接口。
- 内核控制线程 这种线程又称为轻型进程LWP(Light Weight Process)。 每一个进程都可拥有多个LWP, 同用户级线程一样, 每个LWP都有自己的数据结构(如TCB),其中包括线程标识符、优先级、 状态, 另外还有栈和局部存储区等。 它们也可以共享进程所拥有的资源。LWP可通过系统调用来获得内核提供的服务,这样,当一个用户级线程运行时,只要将它连接到一个LWP上,此时它便具有了内核支持线程的所有属性。