写在前面:
- 本系列笔记主要以《计算机操作系统(汤小丹…)》为参考,大部分内容出于此书,笔者的工作主要是挑其重点展示,另外配合下方视频链接的教程展开思路,在笔记中一些比较难懂的地方加以自己的一点点理解(重点基本都会有标注,没有任何标注的难懂文字应该是笔者因为强迫症而加进来的,可选择性地忽略)。
- 视频链接:操作系统(汤小丹等第四版)_哔哩哔哩_bilibili
一、信号量机制
1、整型信号量
(1)整型信号量定义为一个用于表示资源数目的整型量S,它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作wait(S)和signal(S)来访问,这两个操作分别称为P、V操作。
(2)只要是信号量S≤0,就会不断地测试,因此该机制并未遵循“让权等待”的准则,而是使进程处于“忙等”的状态。
2、记录型信号量
(1)用一个整型变量value表示资源数目,然后增加一个进程链表指针list,用于链接所有等待进程。上述两数据项可描述如下:
typedef struct
{
int value; //可用资源数目
struct process_control_block *list;
}semaphore;
(2)wait(S)和signal(S)操作的描述:
①对信号量的每次wait操作,意味着进程请求一个单位的该类资源,使系统中可供分配的该类资源数减少一个,因此描述为S->value--;当S->value<0时,表示该类资源已分配完毕,因此进程应调用block原语进行自我阻塞,放弃处理机,并插入到信号量链表S->list中,此时S->value的绝对值表示在该信号量链表中已阻塞进程的数目。可见,该机制遵循了“让权等待”准则。
②对信号量的每次signal操作表示执行进程释放一个单位资源,使系统中可供分配的该类资源数增加一个,故S->value++操作表示资源数目加1;若加1后仍是S-> value<=0,则表示在该信号量链表中仍有等待该资源的进程被阻塞,故还应调用wakeup原语,将S -> list链表中的第一个等待进程唤醒。
③如果S-> value 的初值为1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量,用于进程互斥。
3、AND型信号量
(1)假定现有两个进程A和B,它们都要求访问共享数据D和E,当然,共享数据都应作为临界资源,为此,可为这两个数据分别设置用于互斥的信号量Dmutex和Emutex,并令它们的初值都是1,相应地,在两个进程中都要包含两个对Dmutex和Emutex 的操作,即
如果进程A访问D之后,切换为进程B访问E,那么之后进程A需要访问E,而E此时正在被进程B访问,进程A无法继续进行,但是进程B需要访问的D此时又被A占用,进程B也无法继续进行,而它们也不释放自己已占用的资源,于是二者容易发生进程死锁。显然,当进程同时要求的共享资源越多,发生进程死锁的可能性就越大。
(2)AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源一次性全部地分配给进程,待进程使用完后再一起释放,只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源也不分配给它。亦即,对若干个临界资源的分配采取原子操作方式,要么把它所请求的资源全部分配到进程,要么一个也不分配。
(3)AND同步机制在wait操作中增加了一个“AND”条件,那么Swait(Simultaneous wait)和Ssignal(Simultaneous signal)的定义如下:
Swait(S1, S2, …, Sn)
{
while(TRUE)
{
if(Si >= 1 && … && Sn >= 1) //资源全部空闲才能进行分配
{
for(i = 1; i <= n; i++) //资源逐一分配
Si--;
break; //结束等待资源的循环
}
else
{
Place the process in the waiting queue associated with the first Si found with
Si < 1, and set the progress count of this process to the beginning of Swait operation
}
}
}
Ssignal(S1, S2, …, Sn)
{
while(TRUE)
{
for(i = 1; i <= n; i++) //资源逐一释放
{
Si++;
Remove all the process waiting in the queue associated
with Si into the ready queue
}
}
}
4、信号量集
(1)对AND信号量机制加以扩充,对进程所申请的所有资源以及类资源不同的资源需求量,在一次P、V 原语操作中完成申请或释放。进程对信号量的测试值不再是1,而是该资源的分配下限值
,即要求
,否则不予分配;一旦允许分配,进程对该资源的需求值为
,即表示资源占用量,进行
操作,由此形成一般化的“信号量集”机制。
(2)“信号量集”机制对应的Swait和Ssignal格式:
Swait(S1, t1, d1, …, Sn, tn, dn)
{
while(TRUE)
{
if(Si >= ti && … && Sn >= ti) //待足够空闲资源之后才能进行分配
{
for(i = 1; i <= n; i++) //资源分配(每次循环分配一种资源)
{
Si = Si - di;
}
}
else
{
Place the executing process in the waiting queue of the first Si with
Si < ti, and set its program counter to the beginning of the Swait operation
}
}
}
Ssignal(S1, t1, d1, …, Sn, tn, dn)
{
while(TRUE)
{
for(i = 1; i <= n; i++) //资源释放(每次循环释放一种资源)
{
Si = Si + di;
Remove all the process waiting in the queue associated
with Si into the ready queue
}
}
}
(3)一般“信号量集”的几种特殊情况:
①Swait(S, d, d),只有一个信号量S,允许每次申请d个资源,若现有资源数少于d,不予分配。
②Swait(S, 1, 1),蜕化为一般的记录型信号量(S>1时)或互斥信号量(S=1时)。
③Swait(S, 1, 0),当S>=1时,允许多个进程进入某特定区,当S变为0后,阻止任何进程进入某特定区,相当于可控开关。
二、信号量的应用
1、利用信号量实现进程互斥
(1)为使多个进程互斥的访问某临界资源,须为该资源设置一互斥信号量mutex,并设其初始值为1,然后将各进程访问资源的临界区CS置于wait(mutex)和signal(mutex)之间即可。
①每个欲访问该临界资源的进程在进入临界区之前都要先对mutex执行wait操作,若该资源此刻未被访问,本次wait操作必然成功,进程便可进入自己的临界区,这时若再有其它进程也欲进入自己的临界区,由于对mutex执行wait操作定会失败,因而此时该进程阻塞,从而保证了该临界资源能被互斥地访问。
②当访问临界资源的进程退出临界区后,应对mutex执行signal操作,以释放该临界资源。
(2)设mutex为互斥信号量,其初值为1,取值范围为(-1,0,1)。
①当mutex=1时,表示两个进程皆未进入需要互斥的临界区。
②当mutex=0时,表示有一个进程进入临界区运行另外一个必须等待,挂入阻塞队列。
③当mutex=-1时,表示有一个进程正在临界区运行,另外一个进程因等待而阻塞在信号量队列中,需要被当前已在临界区运行的进程退出时唤醒。
(3)wait(mutex)和signal(mutex)必须成对地出现。
①缺少wait(mutex)将会导致系统混乱,不能保证对临界资源的互斥访问。
②缺少signal(mutex)将会使临界资源永远不被释放,从而使因等待该资源而阻塞的进程不能被唤醒。
2、利用信号量实现前趋关系
设有两个并发执行的进程P1和P2,P1中有语句S1,P2中有语句S2,希望在S1执行后再执行S2。
为实现这种前趋关系,只需使进程P1和P2共享一个公用信号量S,并赋予其初值为0,将signal(S)操作放在语句S1后面,而在S2语句前面插入wait(S)操作,由于S被初始化为0,若P2先执行必定阻塞,只有在进程P1执行完“S1; signal(S);”操作后使S增为1时,P2进程方能成功执行语句S2。
3、利用记录型信号量实现同步
设有两个进程P1和P2因合作完成一项任务而共用一个变量x,进程P2将处理结果送入x,进程P1将x的结果打印。不严格地说,这实际上也算一种前趋关系,x需要被赋新值后才能打印,已被赋值一次的x需要被打印后才能进行第二次赋值,以防止前一次赋的值不被打印。
为实现这种关系,两个进程需共享两个公用信号量,其中empty的初值为1,表示x允许被赋新值,full的初值为0,表示x暂时不允许打印,初始状态下进程P2可以给x赋新值(empty的值由1变为0),执行完进程P2后full的值变为1,此时进程P1可以打印x(full的值由1变为0),打印完成后empty的值变为1。
三、管程机制
系统中的各种硬件资源和软件资源均可用数据结构抽象地描述其资源特性,即用少量信息和对该资源所执行的操作来表征该资源,而忽略它们的内部结构和实现细节。因此,可以利用共享数据结构抽象地表示系统中的共享资源,并且将对该共享数据结构实施的特定操作定义为一组过程,进程对共享资源的申请、释放和其它操作必须通过这组过程,间接地对共享数据结构实现操作。
代表共享资源的数据结构以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序共同构成了一个操作系统的资源管理模块,称之为管程。管程被请求和释放资源的进程所调用。
管程由四部分组成:
①管程的名称。
②局部于管程的共享数据结构说明。
③对该数据结构进行操作的一组过程。
④对局部于管程的共享数据设置初始值的语句。
封装于管程内部的数据结构仅能被封装于管程内部的过程所访问,任何管程外的过程都不能访问它;反之,封装于管程内部的过程也仅能访问管程内的数据结构。所有进程要访问临界资源时,都只能通过管程间接访问,而管程每次只准许一个进程进入管程执行管程内的过程,从而实现了进程互斥。