14.信号量的代码实现

Posted PacosonSWJTU

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了14.信号量的代码实现相关的知识,希望对你有一定的参考价值。

【README】

1.本文内容总结自 B站 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;

2.信号量基础知识,refer2 posts below.

12.进程同步与信号量_PacosonSWJTU的博客-CSDN博客1.本文内容总结自 B站 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;【1】https://blog.csdn.net/PacosonSWJTU/article/details/12553612013.信号量临界区保护_PacosonSWJTU的博客-CSDN博客1.本文内容总结自 B站 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;2.操作系统使用信号量实现进程同步(合作),走走停停,推进多进程合理有序向前执行; 3.靠临界区保护信号量,靠信号量实现进程同步;0)信号量1)问题 图解: 并发问题例子,empty = -1,但有2个生产者进程睡眠,这说明empty信号量的值是错误的;1)竞争条件: 图片解说:错误和调度顺序有关;2)解决竞争条件的方法 上图显示的执行顺序如下:时间进程代码或操作1P1检查并给empty上锁2P1.register=empthttps://blog.csdn.net/PacosonSWJTU/article/details/125542224


【1】信号量回顾

1)信号量定义:

  • 一个整型数字;通过对信号量的访问修改,实现多进程同步(合作),有序执行,(或互相等待,交替执行);

2)操作系统内部也是有信号量来实现多进程同步合作

  • 如一个进程操作磁盘,磁盘寻道时,该进程阻塞;
  • 一旦磁盘准备好数据后,通过中断通知cpu读取数据;则cpu唤醒上述进程读取数据;

3)借助信号量可以完成的工作

  • 工作1:借助信号量实现用户态的进程调度,实现上层应用程序间的同步(合作),交替执行;
  • 工作2:借助信号量实现内核态的进程调度;

4)信号量数据结构

  • 信号量数据结构包括 value值(信号量值), pcb队列(阻塞到信号量的进程队列);
  • 因为信号量的值要被所有进程看到,所以信号量在内核态;pcb是存储进程信息的数据结构;

【2】信号量代码

【2.1】代码1-打开信号量

 


Producer(item) 
	P(empty);  // P是生产者,empty减1 
	...
	V(full);   // V是消费者,full 加1  


// 用户态程序  producer.c 
main()
	sd = sem_open("empty"); // 打开信号量 
	for (i=1 to 5) 
		sem_wait(sd); // 写数据前,要看是否有空闲缓冲区; 
		write(fd, &i, 4); // 把5个数字(1 2 3 4 5)	写入到磁盘,每个数字4个字节 


// sem.c // 进入内核 
// 信号量结构体
typedef struct 
	char name[20]; // 信号量名称 
	int value; // 信号量值,一个整型变量 
	task_struct* queue; // 缓冲区队列
 semtable[20];

// 系统调用:打开信号量或创建信号量
sys_sem_open(char *name) 
	在 semtable 中寻找name对上的项;
	没找到则创建;
	找到则返回对应下标;


// 写数据前,要看是否有空闲缓冲区 
// 开关中断用于保护临界区  
sys_sem_wait(int sd) 
	cli();// 关中断,不允许cpu响应其他中断请求,
	// 不响应中断的目的是不响应时钟中断,从而不触发进程调度
     // 这里判断信号量用的是if ,目的是唤醒第1个进程 
	if (semtable[sd].value-- < 0) 
		// 设置自己为阻塞;
		// 将自己加入到 semtable[sd].queue 中; 
		schedule(); // 切换到其他进程执行 
	
	sti();	// 开中断



【2.2】代码2-读磁盘的信号量

 

// 读磁盘块 
bread(int dev, int block) 

	struct buffer_head* bh; // 申请一段空闲缓冲区内存(用于映射磁盘块空间)
	ll_rw_block(READ, bh); // 发起读命令 
	wait_on_buffer(bh);   // 等待磁盘响应,然后读数据到缓冲区bh 


// 启动磁盘读以后睡眠,
// 等待磁盘读完由磁盘中断将其唤醒,也是一种同步
// 开关中断用于保护临界区,临界区保证同时仅有一个进程操作信号量
lock_buffer(buffer_head* bh) 

	cli(); // 关中断  
    // 这里判断信号量用的是 while,目的是唤醒所有阻塞进程 
	while(bh->b_lock)
		sleep_on(&bh->b_wait); // 如果被锁上,当前进程睡眠
	bh->b_lock = 1; // b_lock也是信号量;这里上锁; 数据读完后,中断服务程序解锁;  
	sti(); // 开中断 


// 当前进程阻塞(睡眠),切换到其他进程 
void sleep_on(struct task_struct **p) 

	struct task_struct *tmp;
	tmp = *p;
	*p = current;
	current->state = TASK_UNINTERRUPTIBLE; // 把进程状态更新为阻塞态 
	schedule(); // 调度, 底层调用switch_to()以切换到其他进程执行; 
	if (tmp) 
		tmp->state = 0; 


【2.3】代码3-sleep_on形成的队列


【2.4】 代码4-从队列中唤醒阻塞进程

 


// 对阻塞进程的唤醒 (通过磁盘中断来唤醒) 
static void read_intr(void) 

	...
	end_request(1); // 磁盘准备数据完成后调用 


end_request(int uptodate) 

	...
	unlock_buffer(CURRENT->bh);  // 解锁缓冲区 


// 解锁缓冲区 
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; // 把阻塞进程的state设置为0,该进程就变成就绪态了 
		*p = NULL;  
	


注意:代码1用 if 判断信号量,代码2用 while 判断信号量,注意两者的区别

以上是关于14.信号量的代码实现的主要内容,如果未能解决你的问题,请参考以下文章

c - semctl GETVAL 无法读取信号量值

如何将 POSIX 信号量值设置为 1?

POSIX 信号量父信号量值不受影响

条件变量和信号量的区别

iOS 信号量

使用监视器编写信号量