操作系统-进程同步与互斥及算法实现(单标志、双标志先检查、双标志后检查、中断屏蔽、TSL指令、Swap指令、信号量机制)


早期的操作系统学习笔记📗

主要参考来源

操作系统_清华大学(向勇、陈渝)

Operating systems: internals and design principles

Operating System Concepts

还有其余的一些网络博文


博客记录

操作系统-Operating-System第一章:概述

操作系统-Operating-System第二章:启动、中断、异常和系统调用

操作系统-Operating-System第三章01:计算机体系结构及内存分层体系

操作系统-Operating-System第三章02:地址空间和地址生成

操作系统-Operating-System第三章03:内存管理方式(连续内存分配)

作为非科班,前两个月学习操作系统的时候由于很多知识点不是很了解,所以学习起来有点吃力。为打好基础,本人补习了一下《计算机组成原理》的相关知识,并在博客专栏中做了记录。事实证明,先学一点计组知识有助于理解《操作系统》这门课。

《计算机组成原理》笔记可参考:计算机组成原理,博客内容如下:

计算机组成原理-计算机硬件的基本组成

计算机组成原理-计算机的功能部件及层次结构

计算机组成原理-计算机性能指标

计算机组成原理-数制与编码(进制转换)

计算机组成原理-定点数的表示和运算

计算机组成原理-浮点数的表示与运算

计算机组成原理-算术逻辑单元ALU

计算机组成原理-存储系统

计算机组成原理-高速缓冲存储器

计算机组成原理-指令系统 (指令、操作码、地址码、指令寻址、数据寻址)

计算机组成原理-总线(系统总线、总线仲裁、总线操作和定时)

Tips: 个人感觉学习完《计算机组成》和《操作系统》再去看《Java虚拟机》,也许会有更清晰的思路,而不是像大多数非科班一样直接背面筋,效果并不是很好。

资源💾

王道考研

链接: https://pan.baidu.com/s/1Dsk6KjJEwBBgNGLFWrIQDQ 密码: nmo1

英文书籍

Operating System Concepts

Operating systems: internals and design principles

现在的操作系统学习笔记📗

操作系统-处理机调度(调度层次、基本准则、先来先服务、最短作业优先、高响应比、时间片轮转、优先级调度、多级反馈队列)


基本概念

在多道程序环境下,程序的执行是「并发执行」的,不同的进程之间存在着不同的相互制约关系。而进程的并发需要共享的支持,而并发操作避免不了需要共享一些系统资源,例如内存、打印机、摄像头等I/O设备。

两种资源共享的方式:

资源共享
互斥共享
同时共享

「互斥共享」:系统中的某些资源虽然可以提供给多个进程使用,但是同一个时间段内只允许一个进程访问该资源,该类型的资源叫做「临界资源」。

「同时共享」:系统中的某些资源,允许一个时间段内,多个进程同时访问

临界资源的访问过程可以分为4个部分:

  1. 进入区。进入临界区之前需要检查是否可以进入该区域。如果可以进入,则需要设置正在访问该资源的标志,防止其他进程同时进入。
  2. 临界区。进程中正在访问临界资源的那段代码,称为临界段或临界区。
  3. 退出区。将正在访问临界区的标志清除。
  4. 剩余区。代码中的其余部分。
do{
    entry section; 		// 进入区
    critical section;   // 临界区
    exit section; 		// 退出区
    remainder section;  // 剩余区
}while(true)

进程同步

「进程同步」是指直接制约关系,在多个进程中,相互之间存在着因为某些位置协调它们的工作次序而产生的制约关系。


进程互斥

「进程互斥」是指一种间接的制约关系。对于「临界资源」,在一个时间段只有一个进程可以去使用,例如许多物理硬件资源(摄像头、打印机),我们无法做到在一条电脑上同时进行qq和微信的视频聊天功能。当一个进程访问某临界资源的时候,另外一个想要访问该资源的进程必须等待。只有当前访问临界资源的进程访问结束并且释放该资源的时候,另外一个进程才可以访问该临界资源。

为防止两个进程同时进入临界区,同步机制需要遵循以下的准则:

  1. 空闲让进。临界区空闲的时候,允许一个请求进入临界区的进程立即进入临界区。
  2. 忙则等待。如果有进程进入了临界区,其他试图访问临界区的进程必须等待。
  3. 有限等待。对于访问的进程,必须保证在有限时间内进入临界区。
  4. 让权等待。如果一个进程无法进入临界区,则需要释放处理机,防止进程处于忙等待情况。

软件实现方法

单标志法

P0进程

while(trun!=0);
critical section;
trun=1;
remainder section;

P1进程

while(trun!=1);
critical section;
trun=0;
remainder section;

设置一个公共整型变量trun,代表是否允许进入临界区的进程编号。如果trun=0,则P0可以进入临界区,只要P0没有退出临界区,而另外一个进程将会一直停留在while循环,无法访问临界区资源。

如果P0进程无法进入临界区,P1则也会无法进入(trun值未修改),出现临界区资源空闲的状态,违背了「空闲让进」原则

如果P0进入临界区离开后,P1却没有进入临界区的打算,则trun始终无法修改为0,P0无法再次进入临界区。

双标志法先检查

P i P_i Pi进程

while(flag[j]);①
flag[i]=TRUE;③
critical section;
flag[i]=FALSE;
remainder section;

P j P_j Pj进程

while(flag[i]);②
flag[j]=TRUE;④
critical section;
flag[j]=FALSE;
remainder section;

基本思想:在每个进程访问临界区资源之前,先检查一下临界资源是否被其他进程占用,如果flag为true,则当前进程需要等待。flag[i]代表i进程是否访问临界资源,true代表正在访问,false代表未进入临界区。

优点:不用交替的进入,两个进程在时间片轮转下并发执行,可以连续使用。

缺点:如果并发过程中按照①②③④的顺序进行,此时有没进程访问临界区资源,则进程代码块会跳过while循环等待,会出现flag[i]=true并且flag[j]=true,则两个进程会同时进入临界区,违背了「忙则等待」的原则。主要问题来源于,在检查flag并切换自己flag之前有一段时间,导致两个进程都检查通过。

双标志后检查

P i P_i Pi进程

flag[i]=TRUE;
while(flag[j]);
critical section;
flag[i]=FLASE;
remainder section;

P j P_j Pj进程

flag[j]=TRUE;
while(flag[i]);
critical section;
flag[j]=FALSE;
remainder section;

双标志前检查是先检查对方的状态,再设置自己的状态。而双标志后检查是先设置自己的状态,表明自己想要进临界区的意愿,然后再进行检查其他进程的状态。在该算法下,两个进程都会表明自己想要进入临界区的意愿,但是在检查过程中发现,大家都想进去,两个进程都处于等待的状态,导致「饥饿」现象

Peterson’s Algorithm

P i P_i Pi进程

flag[i]=TRUE;turn=j;while(flag[j] && turn==j);③
critical section;
flag[i]=FALSE;
remainder section;

P j P_j Pj进程

flag[j]=TRUE;turn=i;while(flag[i]&&turn=i);④
critical section;
flag[j]=FALSE;
remainder section;

为了防止两个进程为了进入临界区而出现无限等待的现象,在设置一个变量turn,在设置该进程是否进入临界区的标志后,再设置turn标志。while()循环中,同时考虑另一个进程状态当前进程的不允许进入临界标志

假如按照①②③④的顺序执行,则执行完①②后,flag[i]=TRUE, flag=TRUE, trun=i,此时遇到④是会一直处于循环状态的,而③可以顺利跳出while循环,进入临界区。利用trun值可以很好的解决「双标志后检查」算法引起的饥饿现象,Peterson算法是「单标志法」和「双标志后检查」算法的结合,利用flag解决临界资源的互斥访问,利用turn解决饥饿现象

硬件实现方法

中断屏蔽方法

利用“开/关中断指令”的方式实现。和原语的实现思想类似,在整个指令执行过程中,不允许发生进程切换,因此不可能存在两个进程同时都在临界区的情况。

...
关中断   ---->   关闭中断后,就不允许当前的进程被中断,也不会发生进程切换
临界区
开中断	  ---->   当前进程访问完临界区之后,开启中断,经过处理机调度,给需要访问临界资源的进程提供机会
...

该方法简单高效,但是不适合多处理机,只适合操作系统内核进程,不适合用户进程(开/关中断指令只能在内核态运行),如果这组指令让用户随意使用会很危险,可能会影响整个CPU的调度。

TestAndSet指令

// 布尔型变量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; // 解锁
剩余区代码段...

简称TS指令,或者TestAndSetLock指令,或者TSL指令。

TSL指令是通过硬件实现的,执行过程为原子操作,不允许被中断。

如果lock初始为false,while循环条件不满足,直接跳出循环,当前进程进入临界区。

如果lock初始为true,说明当前临界资源被其余的进程访问,等到正在运行的进程在退出区将lock设置为false对临界区进行解锁。

优点:实现简单,不需要想软件实现方法一样严格检查逻辑漏洞(设置标志位表明自己想要用这临界资源,还要考虑饥饿情况,需要设置turn变量);TSL指令适用于多处理机环境。

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

Swap指令

// 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变量先设置为true,进入while循环,交换old值和lock值。当前临界区资源被占用,则lock值为true,此时交换后,依然进入while循环。如果当前临界资源空闲,则old值被替换为false,跳出循环,该进程进入临界区代码。优缺点方面与TSL指令类似。


信号量机制

信号量机制是一种功能较强的机制,可以用来解决互斥与同步问题。并且只能被两个标准的原语wait(S)signal(S),也就是P操作和V操作,PV的命名出自荷兰语,细节可参考WIKI: Semaphore (programming)

The canonical names V and P come from the initials of Dutch words. V is generally explained as verhogen (“increase”). Several explanations have been offered for P, including proberen (“to test” or “to try”),[4] passeren (“pass”), and pakken (“grab”). Dijkstra’s earliest paper on the subject[2] gives passering (“passing”) as the meaning for P, and vrijgave (“release”) as the meaning for V. It also mentions that the terminology is taken from that used in railroad signals. Dijkstra subsequently wrote that he intended P to stand for prolaag,[5] short for probeer te verlagen, literally “try to reduce”, or to parallel the terms used in the other case, “try to decrease”.[6][7][8]

In ALGOL 68, the Linux kernel,[9] and in some English textbooks, the V and P operations are called, respectively, up and down. In software engineering practice, they are often called signal and wait,[10] release and acquire[10] (which the standard Java library[11] uses), or post and pend. Some texts[12][13] call them vacate and procure to match the original Dutch initials.

通俗的讲,P操作对应加锁,V操作对应解锁。

P(S) --> 加锁对应的P操作将信号量减1,申请一个资源S,如果资源不够就执行block原语主动阻塞等待

V(S) --> 解锁对应的V操作将信号量加1,释放一个资源S,如果有进程在等待,则执行wakeup原语唤醒该进程

实现进程互斥

// 记录信号量的定义
typedef struct{
    int value; // 剩余资源数
    struct process *L; // 等待队列
} semaphore;
semaphore mutex = 1; // 初始化信号量

P1(){
    ...
    P(mutex); // 使用临界资源前需要加锁  mutex - 1
    临界区代码段...
    V(mutex); // 使用临界资源后需要解锁 mutex +1
}

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

注意:对于不同的临界资源需要设置不同的互斥信号量,P、V操作必须成对出现。

没有P操作则无法保证临界资源的互斥访问

没有V操作则无法释放占用的资源,而且等待资源的队里中的进程也不会被唤醒

实现进程同步

对于两个进程P1,P2,含有如下的代码段:

P1(){
    代码①;
    代码②;
    代码③;
}

P2(){
    代码④;
    代码⑤;
    代码⑥;
}

在并发执行进程下,访问临界区资源的进程可以按照一定顺序执行,但是里面的代码是无法控制先后顺序的,所以说会涉及到之前提到的同步问题。

而采用信号量机制的PV操作可以很好实现进程的同步,比如我们要求代码的执行顺序是①② | ④⑤,则只需要在②后面进行V操作,④之前执行P操作即可:

P1(){
    代码①;
    代码②;
    V(S);
    代码③;
}

P2(){
    P(S);
    代码④;
    代码⑤;
    代码⑥;
}

假设信号量初始值为0: semaphore S=0;

如果先进入P1()进程的代码段,则首先执行P操作,S–,此时S=-1,没有可用资源,P操作则会执行block原语,P2进程主动请求阻塞。进入P1()代码段,执行完②之后,释放资源S++,S=0,并且在V操作中执行wakeup原语唤醒阻塞队列中的P2进程,P2继续执行④⑤⑥…

简单来记就是前V后P

  • 在"前操作"之后加V(S)
  • 在"后操作"之前加P(S)

实现前驱关系

进程并发需要考虑代码间的同步问题

  • 13
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YuanbaoQiang

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值