信号量临界区保护

什么是信号量?

通过对这个量的访问和修改,让大家有序推进。

为啥需要保护信号量?

既然大家都要修改它,那它肯定有并发问题(逼格高点👍:竞态条件),因此,需要保护信号量

不保护会出现的问题?

由于CPU时间片的调度,共享数据(信号量),将出现语义错误。以生产者-消费者为例,假设有两个生产者p1,p2,信号量sem=n

image-20210723105611229

若在改变p1时,时间片发生调度,将可能产生以下次序:

p1.register = sem
p1.register = p1.register - 1
p2.register = sem
p2.register = p2.register - 1
sem = p2.register
sem = p1.register

此时sem=-1,信号量的语义将出现问题🤦‍♂️

如何解决这种问题

分析问题:这个问题源于有多个线程去竞争修改信号量,由于修改过程不具备原子性,因此出现了修改一半,时间片就结束,最终不合时宜的运行顺序导致sem的语义出现错误

解决想法:🤷‍♀️它不体面,就帮他体面,让信号量修改时具备原子性,我们抽象出一个🔒,它锁住的区域叫做临界区,该区域内的代码片段运行具备原子性

p1 p2
🔒
p1.register = sem
p1.register = p1.register - 1
检查🔒,发现没有,等待把
sem = p1.register
解锁
p2.register = sem
p2.register = p2.register - 1
sem = p2.register

临界区

临界区是指一次只允许一个进程进入的该进程的那一段代码,而🔒保护的是临界区在被使用时不被侵犯,一般设置锁之前需要确定临界区的范围

临界代码的保护原则

基本原则:

  • 互斥进入:如果一个进程在临界区等待,则其他进程不允许进入
  • 有空让进:若干个进程需要进入临界区时,应尽快使一进程进入临界区
  • 有限等待:从进程发出进入请求到允许进入,不能无限等待(避免饥饿)

实现方式

一、轮转法

# P0
while(turn == 0); #空转
# 临界区
turn = 1
# 剩余区

# P1
while(turn == 1);
# 临界区
turn = 0
# 剩余区

轮转法,也作值日法,就是一替一次。但是有个问题,假设p0运行完毕,此时临界区不存在任何进程,时间片又再一次分配给P0,该进程此时将无法再次进入,不满足有空等待这一基本原则

二、标记法

# P0
flag[0] = true
while(flag[1] == true);
flag[0] = false

# p1
flag[1] = true
while(flag[0] == true);
flag[1] = false

标记法,不当的时间片切换,将出现死锁的情况

三、Peterson算法

结合标记和轮转两种思想,在标记的基础上增加轮转值(非对称标记),使得无论如何都不会产生死锁的情况

# P0
flag[0] = true
while(flag[1] == true && turn == 1);

# ===
turn = 1
flag[1] = true


# p1
flag[1] = true
while(flag[0] == true && turn == 0);

# ===
turn = 0
flag[0] = true

验证:

  • 满足互斥进入:如果两个进程都进入临界区,此时flag[0]=flag[1]=true turn=0=1,不成立
  • 满足有空让进:如果p1不在临界区,此时flag[0]=true or turn = 0,p0都有机会进入
  • 满足有限等待:p0要求进入时,p1不会一直进入,因为p1执行一次就将turn=0,因此p0不会饥饿

多进程临界区保护算法—面包店算法

仍然是标记与轮转结合:

  • 轮转:每个进程获得一个序号,序号最小的进入
  • 标记:进程离开时标记为num[i]=0

正如面包店一样:进店买单、取号,买单是指得到标记,取号表示加入轮转

# pi 共有n个进程
# 取号
choosing[i] = true
num[i] = max(num[0],num[n-1]) + 1

# 加入轮转
choosing[i] = false
for(j=0;j<n;j++){
	while(choosing[j]);# 有人在选号停一停
	while((num[j]!=0) && (num[j]<num[i])); 
	}
# 临界区	
nums[i] = 0

正确性分析:

  • 互斥进入:pi在临界区,pk要进入,此时一定有num[i]<num[k])pk循环等待
  • 有空让进:如果进程没在临界区,最小序号的进程一定能进入
  • 有限等待:刚离开临界区的进程,将再次排到最后,所以任意一个想再次进入临界区的进程,至多等待n个进程

临界保护的另一类解法—硬件下场

临界区被破坏一个重要的原因就是被调度,而被调度一个主要因素就是时钟中断,那就靠硬件阻止中断,也就能阻止调度。

硬件给了我们一个很好的指令

cli()

# 临界区

sti()

该指令,其实将控制CPU中断的寄存器INTR关闭,可以直接关闭单CPU的时钟中断

弊端:多CPU不好使

临界区保护的硬件原子指令

主要思想:上锁—修改信号量—开锁

利用原子指令,原子执行修改🔒变量的代码(需要硬件的支持)

TestAndSet(boolean &x){
	# 一次执行
	boolean rv = x;
	x = true;
	return rv;
	# 不中断
}

while(TestAndSet(&lock));
# 临界区
lock = false

信号量的代码实现

// 概念程序
Producer(item){
	p(empty);
	...
	v(full)
}

// 真实程序 producer.c
main(){
	sd = sem_open("empty") // 申请信号量,需要进入内核态,因为要申请PCB队列
	
	for(i=1 to 5)
		sem_wait(sd) //判断是否有空闲缓冲区
		write(fd,&i,4)//在文件写出这5个数,每个数四个字节
	
}

// sem_open sem.c
typedef struct {
	char name[20];
	int value;
	task_struct *queue;
} semtable[20] // 定义全局数组

sys_sem_open(char *name){
	//在semtable中寻找name对上的;
	//没找到就创建;
	//返回对应下标
}

// sem_wait
sys_sem_wait(int sd){
	cli(); //单CPU
	if(semtable[sd].value -- < 0){
		//设置自己为阻塞 cur.state = '阻塞'
		//将自己加入semtable[sd].queue
		//schedule()
	}
	sti();
}

操作系统内部存在的同步代码

读磁盘块

image-20210726152551074
bread(int dev, int block){
	struct buffer_head *bh;//申请一段空闲缓冲区
	l1_rw_block(READ,bh);//启动读命令
	wait_on_buffer(bh);//阻塞
}

启动磁盘读命令后睡眠,等待磁盘读完之后,由磁盘中断将其唤醒

lock_buffer(buffer_head *bh){
	cli();
	while(bh -> b_lock != 1) // 特色:没有负数,while检测
		sleep_on(&bh->b_wait);
	bh->b_lock = 1;
	sti();
}

void sleep_on(struct task_struct **p){
	struct task_struct *tmp;
	temp = *p;
	*p = current;
	current->state = TASK_UNINTERRUPTIBLE;
	schedule();
    //tmp 下一个进程信息
	if(tmp)
		tmp->state = 0;
}
image-20210726154949284

利用栈存储局部变量temp,最终找到下一个task_struct

唤醒过程

磁盘中断,将队首唤醒,队员再按次序依次唤醒后边的进程

  • while 是将阻塞队列全部唤醒的机制,不需要有负数,因为它不需要记录等待队列有多少的进程需要被唤醒(全部唤醒)
  • if 是将阻塞队列中第一个唤醒的机制

所有进程全部唤醒的原因:有时队列后方进程的优先级更高,需要优先执行

static void read_intr(void){
	...
	end_request(1);
}

end_request(int uptodate){
	...
	unlock_buffer(CURRENT->br);
}
unlock_buffer(struct buffer_head *bh){
	bh->b_lock = 0;
	wake_up(&bh->b_wait);
}
wake_up(struct task_struct **p){
	if(p && *p){
		(**p).state=0;
        // 出队
		*p = NULL;
	}
}
posted @ 2021-07-26 14:58  mhp  阅读(292)  评论(0)    收藏  举报