Linux内核:进程管理——进程间同步与互斥

1.什么是进程同步?

进程的并发性带来了异步性(各个并发的进程独立的以不可预知的速度向前推进),而有的进程则需要有次序的相互配合来完成作业,所以有了进程同步。
进程同步 :在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系,。为了协调进程之间的相互制约关系,引入了进程同步的概念。

临界资源
虽然多个进程可以共享系统中的各种资源,但其中许多资源一段时间内只能为一个进程所使用,我们把一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如打印机等。此外,还有许多变量、数据等都可以被若干进程共享,也属于临界资源。

2.什么是进程互斥?

对临界资源的访问,需要互斥的进行。 同一个时间段内只允许一个进程访问该资源 。

临界资源的访问过程四个部分

进入区:为了进入临界区使用临界资源,在进入区要检查可否进入临界区,如果可以进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区。
临界区:进程中访问临界资源的那段代码,又称临界段。
退出区:将正在访问临界区的标志清除。
剩余区:代码中的其余部分。

3.同步

同步亦称直接制约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作。
例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A。

4.互斥

互斥亦称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待, 当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。
例如,在仅有一台打印机的系统中,有两个进程A和进程B,如果进程A需要打印时, 系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞状态变为就绪状态。

互斥机制准则

为禁止两个进程同时进入临界区,互斥机制应遵循以下准则:
空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区。
忙则等待。当已有进程进入临界区时,其他试图进入临界区的进程必须等待。
有限等待。对请求访问的进程,应保证能在有限时间内进入临界区。
让权等待。当进程不能进入临界区时,应立即释放处理器,防止进程忙等待。
内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

5.进程互斥的软件实现方法

a.单标志法

算法思想:一个进程在访问完临界区后会把权限交给另一个进程。也就是说一个进程访问临界区的权限只能被另一个进程所赋予。
int  turn = 0;  //表示允许访问的进程号
//p0进程
while(turn != 0);  	//进入区
critical section;	//临界区
turn = 1;			//退出区
remainder section;	//剩余区

//p1进程
while(turn != 1);  
critical section;	//访问临界区
turn = 0;
remainder section;
假如p1先上处理机,p1会一直卡while循环,知道p1时间片被用完,发生调度。p0上处理机,p0顺利访问临界区,假如p0在访问临界区的时候发生调度切换到p1,p1依然会卡住,只有在p0执行完退出区的turn = 1这行代码时,p1才不会卡在while循环。
所以这就验证了算法思想中的 一个进程访问临界区的权限只能被另一个进程所赋予。
也就实现了同一时刻只能由一个进程访问临界区资源,达到了 进程互斥。
但是这样也带来了问题,由于turn初始值为0,所以只能是p0,p1,p0,p1…这样轮流访问。假如p0一直不访问临界区,虽然临界区空闲,但是turn的值得不到改变,所以p1也访问不了临界区。
这就违反了“空闲让进”准则(临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区)

b.双标志先检查法

算法思想:设置一个布尔型数组flag[ ],数组中各个元素,用来表示各个进程是否有访问临界区的意愿,true则是有,比如flag[0]=true表示0号进程现在想访问临界区。每个进程在访问临界区前,都会检查有没有其他想要访问临界区,如果没有,则把自身对应的flag[i]设置为true,表示该进程现在想要访问临界区。
bool flag[2];  
flag[0] = false, flag[1] = false;
//p0进程
while(flag[1]);     1
flag[0] = true;     2
critical section;   3
flag[0] = false;    4
remainder section;

//p1进程
while(flag[0]);     5
flag[1] = true;     6
critical section;   7
flag[1] = false;    8
remainder section;
由于进程是并发执行的,所有一定会有并发性所带来的异步问题,则进程可能会按照1 5 2 6 3 7…这样的顺序来并发执行,则会造成"在同一时刻,有两个进程同时进入了临界区".
所以双标志先检查法违反了“忙时等待”(当已有进程进入临界区时,其他试图进入临界区的进程必须等待)
造成此问题的原因就是:"检查“和“上锁”不是原子操作,也就是说,检查 和 上锁之间有一个空隙,如果在这个空隙进程切换,则会导致该问题。

c.双标志后检查法

算法思想:双标志前检查法的改版,原来是先检查( while(flag[i]) )后上锁 (flag[i] = true)。这个则是先上锁后检查。
bool flag[2];  
flag[0] = false, flag[1] = false;
//p0进程
flag[0] = true;     1
while(flag[1]);     2
critical section;   3
flag[0] = false;    4
remainder section;

//p1进程
flag[1] = true;     5
while(flag[0]);     6
critical section;   7
flag[1] = false;    8
remainder section;
由于并发带来的异步性,所以进程可能会 1 5 2 6…这样的顺序执行,这就导致两个进程都一直进不去临界区, 同时违反了“空闲让进”和“有限等待”两个准则 ,并且两个进程一直访问不了临界区,则会产生“饥饿”现象。

d.peterson算法

算法思想:由于双标志后检查法两个进程都想先访问临界资源,而导致了“互不想让,且谁都访问不到”的局面。而 peterson算法则是如果双发都争着想访问临界资源,则可以让进程尝试,主动让对方先访问临界区。(“孔融让梨”,进程没有孔融那么人性化,它只会让一次“梨”)。
bool flag[2];  
flag[0] = false, flag[1] = false;
int turn = 0;//表示“让”给其他进程
//p0进程
flag[0] = true;     1
turn = 1;			2
while(flag[1] && turn == 1);     3//如果1号进程想要使用临界区,而且我还让给它了,就等他一会吧
critical section;   4
flag[0] = false;    5
remainder section;

//p1进程
flag[1] = true; 	6
turn = 0;    		7
while(flag[0] && turn ==0 );     8//如果0号进程想要使用临界区,而且我还让给它了,就等他一会吧
critical section;   9
flag[1] = false;    10
remainder section;
如果局面到了双标志后检查法“都想争着使用临界区的局面”的时候,这时候, 一方会退一步,即让对方先使用临界区,这时候, 这一方看到另一方都退一步了,所以自己也退了一步,最开始退一步的一方则向下执行了,使用临界区,使用完了之后,设置flag = false,即告诉对方我用完了,大哥你用吧。
这个算法不会导致进程饥饿设置饿死的现象,另一个进程也遵守了“空闲让进,忙时等待,有限等待”的原则,但是还未遵循"让权等待的原则"。

6.进程互斥的硬件实现方法

中断屏蔽方法

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

关中断
临界区
开中断

其中的关中断 后继不允许当前进程被中断 也必然不会发生进程切换
开中断 直到当前进程访问完临界区,在执行开中断指令,才有可能有别的进程上处理机并访问临界区

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

TestAndSet指令

简称TS指令 或者是TSL指令
使用硬件实现的 执行的过程不允许被中断 只能是一气呵成
下面用C语言描述一下(稍微有点难理解 反正我看了好久好久)

//bool类型共享变量lock表示当前临界区是否被加锁
//true表示被加锁 false表示未加锁
bool TestAndSet(bool *lock){
	bool old;
	old=*lock;//old用来存放lock原来的值
	*lock=true;//无论之前是否被加锁 都把lock设置为加锁状态
	return old;//返回原来的值
}
//下面是TLS指令实现互斥算法的逻辑
while(TestAndSet(&lock));//上锁 并 检查
临界区代码段
lock=false;//临界区执行完毕 解锁
剩余区代码段

若刚开始lock是false(未加锁状态)则TSL返回的old是false,while循环条件不满足,直接跳过循环进入临界区
若刚开始lock是true(加锁状态)则执行TLS之后返回的值是true while循环条件满足 一直会卡在这一步上
等待临界区的代码段执行结束之后 lock自动解锁

相比软件方法 TLS指令将上锁 和 检查操作用硬件的方式变成了一气呵成的原子操作
优点实现简单 无需像软件实现方法那样严格检查是否会有逻辑漏洞,适用于多处理机环境
缺点不满足让权等带的原则,暂时无法进入临界区的进程会占用CPU并执行TSL指令,从而导致忙等

Swap指令

也叫Exchange指令 或者简称XCHG指令
从逻辑上看Swap指令和TSL指令没有太大的区别

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

//下面是TLS指令实现互斥算法的逻辑
bool old=true;
while(old==true)
Swap(old,&lock);
临界区代码段
lock=false;//临界区执行完毕 解锁
剩余区代码段

先记录此时临界区是否被上锁,再将lock设置为true,最后检查old
如果old为false 说明之前没有其他的进程对临界区上锁 则跳出循环 进入临界区

优缺点同TSL相同

上边所写就是进程互斥的硬件实现方法 希望对大家有所帮助

总结

原文作者:极致Linux内核

原文地址:Linux内核:进程管理——进程间同步与互斥 - 知乎(版权归原文作者所有,侵权留言联系删除)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值