王道操作系统OS进程管理

Posted 生命是有光的

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了王道操作系统OS进程管理相关的知识,希望对你有一定的参考价值。

✍、脑图时刻

1、调度算法

1.1、时间片轮转调度算法(RR)

时间片轮转(RR,Round-Robin)调度算法:轮流让就绪队列中的进程依次执行一个时间片(每次选择的都是排在就绪队列队头的进程)

算法思想公平地、轮流地为各个进程服务,让每个进程在一定时间间隔内都可以得到响应
算法规则按照各进程到达就绪队列的顺序,轮流让各个进程执行一个时间片(如100ms)。若进程未在一个时间片内执行完,则剥夺处理机,将进程重新放到就绪队列队尾重新排队。
用于作业/进程调度用于进程调度(只有作业放入内存建立了相应的进程后,才能被分配处理机时间片)
是否可抢占若进程未能在时间片内运行完,将被强行剥夺处理机使用权,因此时间片轮转调度算法属于抢占式的算法。由时钟装置发出时钟中断来通知CPU时间片已到
优缺点优点:公平、响应快,适用于分时操作系统
缺点:由于高频率的进程切换,因此有一定开销;不区分任务的紧急程度
是否会导致饥饿(某进程/作业长期得不到服务)不会

0时刻:(p1(5)),0时刻只有p1到达就绪队列,让p1上处理机运行一个时间片

2时刻:(p2(4)->p1(3)),2时刻p2到达就绪队列,p1运行完一个时间片,被剥夺处理机,重新放到队尾,此时P2排在队头,因此让P2上处理机。(注意:2时刻,P1下处理机,同一时刻新进程P2到达,如果在题目中遇到这种情况,默认新到达的进程先进入就绪队列)

4时刻:(p1(3)->p3(1)->p2(2)):4时刻,P3到达,先插到就绪队尾,紧接着,P2下处理机也插到队尾

5时刻:(p3(1)->p2(2)->p4(6)):5时刻,p4到达插到就绪队尾(注意:由于p1的时间片还没用完,因此暂时不调度。另外,此时p1处于运行态,并不在就绪队列中)

6时刻:(p3(1)->p2(2)->p4(6)->p1(1)):6时刻,p1时间片用完,下处理机,重新放回就绪队尾,发生调度

7时刻:(p2(2)->p4(6)->p1(1)):虽然p3的时间片没用完,但是由于p3只需运行1各单位的时间,运行完了会主动放弃处理机,因此也会发生调度。队头进程P2上处理机。

9时刻:(p4(6)->p1(1)):进程p2时间片用完,并刚好运行完,发生调度,p4上处理机

11时刻:(p1(1)->p4(4)):p4时间片用完,重新回到就绪队列。p1上处理机。

12时刻:(p4(4)):p1运行完,主动放弃处理机,此时就绪队列中只剩p4,p4上处理机

14时刻:():就绪队列为空,因此让p4接着运行一个时间片

16时刻:所有进程运行结束


0时刻(p1(5)):只有p1到达,p1上处理机。

2时刻(p2(4)):p2到达,但p1时间片尚未结束,因此暂不调度

4时刻(p2(4)->p3(1)):p3到达,但p1时间片尚未结束,因此暂不调度

5时刻(p2(4)->p3(1)->p4(6)):p4到达,同时,p1运行结束。发生调度,p2上处理机

9时刻(p3(1)->p4(6)):p2运行结束,虽然时间片没用完,但是会主动放弃处理机。发生调度。

10时刻(p4(6)):p3运行结束,虽然时间片没用完,但是会主动放弃处理机。发生调度

15时刻():p4时间片用完,但就绪队列为空,因此会让p4继续执行一个时间片

16时刻():p4运行完,主动放弃处理机。所有进程运行完。


如果时间片太大,使得每个进程都可以在一个时间片内就完成,则时间片轮转调度算法退化为先来先服务调度算法,并且会增大进程响应时间。因此时间片不能太大

另一方面,进程调度、切换是有时间代价的(保存、恢复运行环境),因此如果时间片太小,会导致进程切换过于频繁,系统会花大量的时间来处理进程切换,从而导致实际用于进程执行的时间比例减少。可见时间片也不能太小

1.2、优先级调度算法

算法思想随着计算机的发展,特别是实时操作系统的出现,越来越多的应用场景需要根据任务的紧急程度来决定处理顺序
算法规则每个作业/进程有各自的优先级,调度时选择优先级最高的作业/进程
用于作业/进程调度既可用于作业调度,也可用于进程调度。甚至,还会用于在之后会学习的I/O调度中
是否可抢占抢占式、非抢占式都有。做题时的区别在于:非抢占式只需在进程主动放弃处理机时进行调度即可,而抢占式还需在就绪队列变化时,检查是否会发生抢占。
优缺点优点:用优先级区分紧急程度、重要程度,适用于实时操作系统。可灵活地调整对各种作业/进程的偏好程度。
缺点:若源源不断地有高优先级进程到来,则可能导致饥饿
是否会导致饥饿(某进程/作业长期得不到服务)

1.2.1、非抢占式的优先级调度

非抢占式的优先级调度算法:每次调度时选择当前已到达且优先级最高的进程,当前进程主动放弃处理机时发生调度。

注意:以下括号内表示当前处于就绪队列的进程

0时刻(p1):只有p1到达,p1上处理机

7时刻(p2、p3、p4): p1 运行完成主动放弃处理机,其余进程都已到达,p3优先级最高,p3上处理机

8时刻(p2、p4):p3完成,p2、p4优先级相同,由于p2先到达,因此p2优先上处理机

12时刻(p4):p2完成,就绪队列只剩p4,p4上处理机

16时刻():p4完成,所有进程都结束

1.2.2、抢占式的优先级调度

抢占式的优先级调度算法:每次调度时选择当前已到达且优先级最高的进程。当前进程主动放弃处理机时会发生调度。另外,当就绪队列发生改变时也需要检查是否会发生抢占。

0时刻(p1):只有p1到达,p1上处理机

2时刻(p2):p2到达就绪队列,优先级比p1更高,发生抢占。p1回到就绪队列,p2上处理机。

4时刻(p1、p3):P3到达,优先级比P2更高,P2回到就绪队列,P3抢占处理机。

5时刻(p1、p2p4):P3完成,主动释放处理机,同时,P4也到达,由于P2比P4更先进入就绪队列,因此选择P2上处理机

7时刻(p1、p4):P2完成,就绪队列只剩P1、P4,P4上处理机。

11时刻(p1):P4完成,P1上处理机

16时刻():P1完成,所有进程均完成


就绪队列未必只有一个:可以按照不同优先级来组织。另外,也可以把优先级高的进程排在更靠近队头的位置。

根据优先级是否可以动态改变,可将优先级分为静态优先级动态优先级两种。

静态优先级:创建进程时确定,之后一直不变。

动态优先级:创建进程时有一个初始值,之后会根据情况动态地调整优先级。

  1. 如何合理地设置各类进程的优先级?
    • 通常:系统进程优先级高于用户进程、前台进程优先级高于后台进程、操作系统更偏好I/O型进程(或称I/O繁忙型进程)
    • 注:与I/O型进程相对的是计算型进程(或称CPU繁忙型进程)
  2. 如果采用的是动态优先级,什么时候应该调整?
    • 可以从追求公平、提升资源利用率等角度考虑
    • 如果某进程在就绪队列中等待了很长时间,则可以适当提升其优先级,如果某进程占用处理机运行了很长时间,则可适当降低其优先级,如果发现一个进程频繁地进行I/O操作,则可适当提升其优先级

1.3、多级反馈队列调度算法

算法思想对其他调度算法的折中权衡
算法规则1. 设置多级就绪队列,各级队列优先级从高到低,时间片从小到大
2. 新进程到达时先进入第1级队列,按FCFS原则排队等待被分配时间片,若用完时间片进程还未结束,则进程进入下一级队列队尾。如果此时已经是在最下级的队列,则重新放回该队列队尾。
3. 只有第k 级队列为空时,才会为k+1 级队头的进程分配时间片
用于作业/进程调度用于进程调度
是否可抢占抢占式的算法。在k级队列的进程运行过程中,若更上级的队列(1~k-1级)中进入了一个新进程,则由于新进程处于优先级更高的队列中,因此新进程会抢占处理机,原来运行的进程放回k 级队列队尾。
优缺点优点:用优先级区分紧急程度、重要程度,适用于实时操作系统。可灵活地调整对各种作业/进程的偏好程度。
缺点:若源源不断地有高优先级进程到来,则可能导致饥饿
是否会导致饥饿(某进程/作业长期得不到服务)

2、进程同步/互斥

进程具有异步性的特征。异步性是指,各并发执行的进程以各自独立的、不可预知的速度向前推进。

同步亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作。

两种资源共享方式:

  • 互斥共享方式:系统中的某些资源,虽然可以提供给多个进程使用,但一个时间段内只允许一个进程访问该资源

  • 同时共享方式:系统中的某些资源,允许一个时间段内由多个进程"同时"对它们进行访问

我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。

对临界资源的访问,必须互斥地进行。互斥,亦称间接制约关系进程互斥指当一个进程访问某临界资源,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。

对临界资源的互斥访问,可以在逻辑上分为如下四个部分:

do
    entry section; // 进入区(负责检查是否可进入临界区,若可进入,则应设置正在访问临界资源的标志(可理解为"上锁"),以防止其他进程同时进入临界区)
    critical section; // 临界区(访问临界资源的那段代码)
    exit section;		// 退出区(负责解除正在访问临界资源的标志(可理解为"解锁"))
    remainder section; //剩余区(做其他处理)
while(true)

注意:

  • 临界区是进程中访问临界资源的代码段。
  • 进入区退出区负责实现互斥的代码段。
  • 临界区也可称为“临界段”。

为了实现对临界资源的互斥访问,同时保证系统整体性能,需要遵循以下原则:

  1. 空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
  2. 忙则等待。当已有进程进入临界区时,其他试图进入临界区的进程必须等待
  3. 有限等待。对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿)
  4. 让权等待。当进程不能进入临界区时,应立即释放处理机,防止进程忙等待

2.1、进程互斥的软件实现方法

  1. 理解各个算法的思想、原理
  2. 结合上小节学习的“实现互斥的四个逻辑部分”,重点理解各算法在进入区、退出区都做了什么
  3. 分析各算法存在的缺陷(结合“实现互斥要遵循的四个原则”进行分析)

2.1.1、单标志法

算法思想:两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予。

turn 的初值为0,即刚开始只允许0号进程进入临界区。

若p1先上处理机运行,则会一直卡在 ,直到p1的时间片用完,发生调度,切换p0上处理机运行。

代码不会卡住p0,p0可以正常访问临界区,在p0访问临界区期间即时切换回p1,p1依然会卡在

只有p0在退出区将turn改为1后,p1才能进入临界区。

因此,该算法可以实现"同一时刻最多只允许一个进程访问临界区"

请著名的老渣和小渣来解释:

只能按照p0->p1->p0->p1->…这样轮流访问。这种必须"轮流访问"带来的问题是,如果此时允许进入临界区的进程是p0,而p0一直不访问临界区,那么虽然此时临界区空闲,但是并不允许p1访问。因此:单标志法存在的主要问题是:违背"空闲让进"原则

2.1.2、双标志先检查法

算法思想:设置一个布尔型数组flag[],数组中各个元素用来标记各进程想进入临界区的意愿,比如 flag[0]=true意味着0号进程p0现在想要进入临界区。每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自身对应的标志flag[i]设为true,之后开始访问临界区。

比如此时p1进程想要进入临界区,那么p1会检查p0此时是否想进入临界区,若p0想进入临界区,那么p1就循环等待,若p0不想进入临界区,则p1才会进入临界区。

若按照①⑤②⑥③⑦...的顺序执行,p0和p1将会同时访问临界区。因此双标志先检查法的主要问题是:违反"忙则等待"原则。原因在于,进入区的"检查"和"上锁"两个处理不是一气呵成的。“检查”后,“上锁”前可能发生进程切换。

请著名的老渣和小渣来解释:

2.1.3、双标志后检查法

算法思想:双标志先检查法的改版。前一个算法的问题是先“检查”后“上锁”,但是这两个操作又无法一气呵成,因此导致了两个进程同时进入临界区的问题。因此,人们又想到先“上锁”后“检查”的方法,来避免上述问题。

若按照①⑤②⑥...的顺序执行,p0和p1都将无法进入临界区。

因此,双标志后检查法虽然解决了"忙则等待"的问题,但是又违背了"空闲让进"和"有限等待"原则,会因各进程都长期无法访问临界资源而产生“饥饿”现象。两个进程都争着想进入临界区,但是谁也不让谁,最后谁都无法进入临界区。

请著名的老渣和小渣来解释:

2.1.4、Peterson算法

算法思想:结合双标志法、单标志法的思想。如果双方都争着想进入临界区,那可以让进程尝试“孔融让梨”(谦让)。做一个有礼貌的进程。

bool flag[2]// 表示进入临界区意愿的数组,初始值都是false
int turn = 0; 		 // turn 表示优先让哪个进程进入临界区

//p0进程
flag[0] = true;		// p0表示自己想进入临界区
turn = 1;			// 但是p0可以优先让对方进入临界区
while(flag[1] && turn==1);//对方想进入临界区,且最后一次是自己"让梨",那自己就循环等待
critical section;
flag[0]=false;		//访问完临界区,表示自己已经不想访问临界区了
remainder section;

//p1进程
flag[1] = true;		// p1表示自己想进入临界区
turn = 0;			// 但是p1可以优先让对方进入临界区
while(flag[0] && turn==0);//对方想进入临界区,且最后一次是自己"让梨",那自己就循环等待
critical section;
flag[0]=false;			//访问完临界区,表示自己已经不想访问临界区了
remainder section;

请著名的小渣和老渣解释:

小渣:

  1. 我小渣要使用(表达意愿)
  2. 但是我愿意优先让老渣用(谦让)
  3. 老渣想用,且最后是小渣自己表达了谦让,那么小渣就等待
  4. 当老渣表示愿意让小渣优先使用或者老渣使用完了,那么小渣就不再等待,就进入临界区
  5. 小渣使用完后就表示我小渣不想用了

所以Peterson算法在进入区进程是执行了三件事:

  1. 主动争取
  2. 主动谦让
  3. 检查对方是否也想使用,且最后一次是不是自己说了"客气话"

谁最后说了"客气话",谁就失去了行动的优先权。

Peterson 算法用软件方法解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待三个原则,但是依然未遵循让权等待的原则。

Peterson 算法相较于前三种软件解决方案来说,是最好的,但依然不够好。

2.1.5、小结

2.2、进程互斥的硬件实现方法

2.2.1、中断屏蔽方法

利用"开/关中断指令"实现(与原语的实现思想相同,即在某进程开始访问临界区到结束访问为止都不允许被中断,也就不能发生进程切换,因此也不可能发生两个同时访问临界区的情况)

优点:简单、高效

缺点:不适用于多处理机;只适用于操作系统内核进程,不适用于用户进程(因为开/关中断指令只能运行在内核态,这组指令如果能让用户随意使用会很危险)

2.2.2、TestAndSet指令

简称TS指令,也有地方称为TestAndSetLock指令,或TSL指令

TSL指令使用硬件实现的,执行的过程中不允许被中断,只能一气呵成。以下是用C语言描述的逻辑。

//布尔型共享变量 lock 表示当前临界区是否被加锁
//true表示已加锁,false表示未加锁
bool TestAndSet(bool *lock)
    bool old;
    old = *lock; // old用来存放lock原来的值
    *lock = true; // 无论之前是否已加锁,都将lock设为true
    return old; // 返回lock原来的值


// 以下是使用TSL指令实现互斥的算法逻辑
while(TestAndSet(&lock)); // 上锁并检查
临界区代码段...
lock = false; //解锁
剩余区代码段...

若刚开始lock是false,则TSL返回的old值为false,while循环条件不满足,直接跳过循环,进入临界区。若刚开始lock是true,则执行TLS后od返回的值为true,while循环条件满足,会一直循环,直到当前访问临界区的进程在退出区进行"解锁"。

相比软件实现方法,TSL指令把"上锁"和"检查"操作用硬件的方式变成了一气呵成的原子操作。

优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞,适用于多处理机环境

缺点:不满足"让权等待"原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致"忙等"

2.2.3、Swap指令

有的地方也叫Exchange指令,或简称XCHG指令。

Swap指令是用硬件实现的,执行的过程不允许被打断,只能一气呵成。以下是用C语言描述的逻辑:

//Swap指令的作用是交换两个变量的值
Swap(bool *a,bool *b)
    bool temp;
    temp = *a;
    *a = *b;
    *b = temp;

//以下使用Swap指令实现互斥的算法逻辑
//lock表示当前临界区是否被加锁
bool old = true;
while(old == true)
    Swap(&lock,&old);

临界区代码段...
lock = false;
剩余区代码段...

逻辑上看Swap和TSL并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在old变量上),再将上锁标记lock设置为true,最后检查old,如果old为false则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区。

优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境

缺点:不满足"让权等待"原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致"忙等"

2.2.4、小结

3、信号量机制

  • 用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。

  • 信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为1的信号量。

  • 原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。软件解决方案的主要问题是由“进入区的各种操作无法一气呵成”,因此如果能把进入区、退出区的操作都用“原语”实现,使这些操作能“一气呵成”就能避免问题。

  • 一对原语:指的是wait(S)原语和signal(S)原语,可以把原语理解为我们自己写的函数,函数名分别为wait和signal,括号里的信号量S其实就是函数调用时传入的一个参数。

  • wait、signal 原语常简称为P、V操作(来自荷兰语proberen 和verhogen)。因此,做题的时候常把wait(S)、signal(S) 两个操作分别写为P(S)、V(S)

3.1、整型信号量

用一个整数型的变量作为信号量,用来表示系统中某种资源的数量 (与普通整数变量的区别:普通的整数变量可以进行加减乘除等运行,而信号量的操作只有三种,即初始化、P操作、V操作)

int S = 1; //初始化整型信号量S,表示当前系统中可用的打印机资源数

void wait(int S)		// wait原语,相当于"进入区"
    while(S≤0);			// 如果资源数不够,就一直循环等待
    S=S-1;				// 如果资源数够,则占用一个资源


void signal(int S)		// signal 原语,相当于"退出区"
    S=S+1;				// 使用完资源后,在退出区释放资源


// 进程p0
wait(S);		//进入区,申请资源
使用打印机资源	  //临界区,访问资源
signal(S);		//退出区,释放资源

​ 如果有一个进程P0要使用打印机资源,由于这种资源是有限的只有一个,并且我们需要互斥的访问打印机,所以在使用打印机资源之前,进程P0必须要使用一个wait原语对信号量S进行操作,wait原语会做两件事情,第一个是判断当前资源数是否足够,如果资源数S≤0,就说明系统当中已经没有这种资源了,那么P0进程就会被一直卡住。但是由于P0进程执行wait原语的时候S的值是1,所以不会被卡住从而执行 S=S-1=0,也就是说这个打印机资源已经被进程P0使用了,没有其他打印机资源了。

​ 当P0在访问打印机资源当中,如果发生了进程切换,有另外的进程P1也想使用打印机资源,那么P1在使用之前先执行 wait 原语,不过由于此时S的值已经是0,也就是说系统当中已经没有打印机资源了,所以P1会在执行while循环时卡住。直到P0进程释放打印机资源,P1才能不被卡住。其他进程若想使用打印机资源与P1相同。

​ 因为是用原语来实现的"检查"和"上锁",所以就避免了两个进程同时进入临界区的问题。

整型信号量存在的问题:当进程被卡在while循环时,进程如果获取不到资源,会导致进程一直占用处理机,产生"忙等"的情况,并不满足"让权等待"的原则。

3.2、记录型信号量

整型信号量的缺陷是存在"忙等"问题,因此人们又提出了"记录型信号量",即用记录型数据结构表示的信号量。

//记录型信号量的定义
typedef struct
    int value;		// 剩余资源数
    struct process *L;	// 等待队列
semaphore;

//某进程需要使用资源时,通过wait原语申请
void wait(semaphore S)
    S.value--;
    if(S.value < 0)
        block(S.L);	//如果剩余资源数不够,使用block原语使进程从运行态进入阻塞态,并把进程挂到信号量S的等待队列(即阻塞队列)中
    


// 进程使用完资源后,通过signal原语释放
void signal(semaphore S)
    S.value++;
    if(S.value<=0)
        wakeup(S.L);	// 释放资源后,若还有别的进程在等待这种资源,则使用wakeup原语唤醒等待队列中的一个进程,该进程从阻塞态变为就绪态
    

举个例子:

某计算机系统中有2台打印机,则可在初始化信号量S时将S.value的值设为2,队列S.L设置为空。各个进程在使用打印机资源之前需要使用wait原语来申请打印机资源,在使用打印机资源之后需要使用signal原语来释放打印机资源。

  1. 首先p0进程要使用打印机资源,CPU为p0进程服务,使用wait原语来申请打印机资源,在wait原语中,S.value--,此时S.value的值变为1,代表剩余打印机数量为1

  2. 之后p1进程要使用打印机资源,CPU为p1进程服务,使用wait原语来申请打印机资源,在wait原语中,S.value--,此时S.value的值变为0,

    代表剩余打印机数量为0

  3. 之后p2进程要使用打印机资源,CPU为p2进程服务,使用wait原语来申请打印机资源,在wait原语中,S.value--,此时S.value的值变为-1,由于S.value<0,所以会使用block原语进行自我阻塞(p2进程从运行态->阻塞态),S.value=-1,表示有1个进程在等待

  4. 之后p3进程要使用打印机资源,CPU为p3进程服务,使用wait原语来申请打印机资源,在wait原语中,S.value--,此时S.value的值变为-2,由于S.value<0,所以会使用block原语进行自我阻塞(p3进程从运行态->阻塞态),S.value=-2,表示有2个进程在等待

  5. p0使用完打印机资源之后会执行 signal 原语,在signal原语中,S.value++,此时S.value的值变为-1,由于S.value≤0,所以会使用wakeup原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态->就绪态),也就是p2进程被唤醒,CPU为p0进程服务,并且将p0进程释放的打印机资源给p2进程使用


  • 在考研题目中wait(S),signal(S)也可以记为P(S)、V(S),这对原语可用于实现系统资源的申请和释放。

  • S.value 的初值表示系统中某种资源的题目

  • 对信号量S的一次P操作意味着进程请求一个单位的该类资源,因此需要执行S.value–,表示资源数减1,当S.value<0时表示该类资源已分配完毕,因此进程应调用block原语进行自我阻塞(当前运行的进程从运行态->阻塞态),主动放弃处理机,并插入该类资源的等待队列S.L中。可见,该机制遵循了"让权等待"原则,不会出现"忙等"现象。

  • 对信号量S的一次V操作意味着进程释放一个单位的该类资源,因此需要执行S.value++,表示资源数加1,若加1后仍是S.value<=0,表示依然有进程在等待该类资源,因此应调用wakeup原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态->就绪态)

3.3、小结

4、信号量机制的实现

  • 一个信号量对应一种资源
  • 信号量的值 = 这种资源的剩余数量(信号量的值如果小于0,说明此时有进程在等待这种资源)
  • P(S)-申请一个资源S,如果资源不够就阻塞等待
  • V(S)-释放一个资源S,如果有进程在等待该资源,则唤醒一个进程

4.1、信号量机制实现进程互斥

  1. 分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)
  2. 设置互斥信号量mutex,初值为1
  3. 在进入区P(mutex)–申请资源
  4. 在退出区V(mutex)–释放资源
//记录型信号量的定义
typedef struct
    int value;		//剩余资源数
    struct process *L;	//等待队列
semaphore;

我们自己要会定义记录型信号量,但是如果题目中没有特别说明,则我们可以把信号量的声明简单写成下 semaphore mutex

// 信号量机制实现互斥
semaphore mutex=1 //初始化信号量
    
P1()
    P(mutex);		//使用临界资源前需要加锁
    临界区代码段...
    V(mutex);		//使用临界资源后需要解锁


P2()
    P(mutex);		//使用临界资源前需要加锁
    临界区代码段...
    V(mutex);		//使用临界资源后需要解锁

注意:对不同的临界资源需要设置不同的互斥信号量。例如临界区打资源为打印机和摄像头,则必须为打印机和摄像头分别设置互斥信号量。

P(mutex)就不能保证临界资源的互斥访问。缺少V(mutex)会导致资源永不被释放,等待进程永不被唤醒。

4.2、信号量机制实现进程同步

进程同步:要让各并发进程按要求有序地推进。

用信号量实现进程同步:

  1. 分析什么地方需要实现"同步关系",即必须保证"**一前一后"**执行的两个操作(或两句代码)

  2. 设置同步信号量S,初始为0

  3. 在"前操作"之后执行V(S)

  4. 在"后操作"之前执行P(S)

    代码2必须在前执行,代码4必须在后执行,所以代码2是"前操作",代码4是"后操作",所以在前操作代码2之后执行V操作,在后操作代码4之前执行P操作。

技巧口诀:前V后P

//信号量机制实现进程同步
semaphore S=0	// 初始化同步信号量,初始值为0。理解:信号量S代表"某种资源",刚开始是没有这种资源的,P2需要使用这种资源,而又只能由p1产生这种资源
    
P1()
    代码1;
    代码2;
    V(S);
    代码3;


P2()
    P(S);
    代码4;
    代码5;
    代码6;

​ 若先执行到V(S)操作,则 S++ 后 S=1。之后当执行到P(S)操作时,由于S=1,表示有可用资源,会执行S–,S的值变回0,P2进程不会执行block原语,而是继续往下执行代码4。

​ 若先执行到P(S)操作,由于S=0,S-- 后S=-1,表示此时没有可用资源,因此P操作中会执行block 原语,主动请求阻塞。之后当执行完代码2,继而执行V(S)操作,S++,使S变回0,由于此时有进程在该信号量对应的阻塞队列中,因此会在V操作中执行wakeup原语,唤醒P2 进程。这样P2 就可以继续执行代码4了。

​ 保证了代码4一定是在代码2之后执行。

4.3、信号量机制实现前驱关系

进程P1中有句代码S1,P2中有句代码S2…,P3中有句代码S6。这些代码要求按如下前驱图所示的顺序来执行。

这个前驱图的意思是,只有S1这一句代码执行了之后,才可以执行S2这一句代码,而只有S2执行了代码之后,才可以执行S2和S5这两句代码。

其实每一对前驱关系都是一个进程同步问题(需要保证一前一后的操作)。因此:

  1. 要为每一对前驱关系各设置一个同步变量
  2. 在"前操作"之后对相应的同步变量设置V操作
  3. 在"后操作"之前对相应的同步变量执行P操作

前V后P

S1要在S2之前执行,所以S1之后要执行V操作,S2要在之前执行P操作

S1要在S3之前执行,所以S1之后要执行V操作,S3要在之前执行P操作

4.4、小结

5、生产者消费者问题

系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区取出一个产品并使用。(注:这里的"产品"理解为某种数据),生产者、消费者共享一个初始为空、大小为n的缓冲区。

  • 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。(缓冲区没满->生产者生产)
  • 只有缓冲区不空时,消费者才能从中取出产品,否则必须等待(缓冲区没空->消费者消费)
  • 缓冲区是临界资源,各进程必须互斥的访问(互斥关系)

PV操作题目分析步骤:

  1. 关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系
  2. 整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
  3. 设置信号量。并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)

消费者进程在消费之前,需要消耗产品资源,所以在它之前的P操作其实是在申请一个产品,因此full同步信号量它所对应的资源是产品的数量,也就是非空缓冲区的数量,而题中刚开始产品的数量是0,所以full初始值为0

生产者每生产一个产品就需要消耗一个空闲缓冲区,因此 empty 这个同步信号量它所对应的资源就应该是空闲缓冲区这种资源,它的数量就是空闲缓冲区的数量,题目中的空闲缓冲区的数量为n,所以empty初始值为n

semaphore mutex = 1;		// 互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n;		// 同步信号量,表示空闲缓冲区的数量
semaphore full = 0;			// 同步信号量,表示产品的数量,也即非空缓冲区的数量

// 生产者
producer(王道操作系统OS进程管理

进程管理(二[2])

进程管理(二[1])

(王道408考研操作系统)第二章进程管理-第三节9:读者写者问题

(王道408考研操作系统)第二章进程管理-第三节8:经典同步问题之吸烟者问题

(王道408考研操作系统)第二章进程管理-第三节3:实现进程互斥的硬件方法