最近开始学习操作系统原理这门课程,特将学习笔记整理成技术博客的形式发表,希望能给大家的操作系统学习带来帮助。同时盼望大家能对文章评论,大家一起多多交流,共同进步!
本文主要分以下几个方面:
- 临界区/段问题
- 硬件机制保证同步
- 信号量
- 经典的同步问题
- 管程(Monitors)
背景:数据并发访问共享的数据 => 不确定性/不一致性
保持数据的一致性 => 有序的执行协作进程
并行:某一时刻多个进程同时运行; 并发(Coccurrent):某一时间段多个进程同时运行
应对共享的变量采用互斥操作。
临界区:一次只能被一个进程使用的资源被称为临界资源(Critical Resource),进程使用临界资源代码段称为临界区/段。
应在共享的变量和数据结构或者慢速设备资源上使用互斥。
分为四个区:进入区(Entry section) 用于判断共享资源是否被访问,临界区(Critical section),退出区(Exit section) 用于取消互斥,剩余区(Remainder section),非共享或非互斥的资源区。
临界区的使用原则:
- 互斥:智能互斥,不能共享。 (忙则等待)
- 空闲让进
- 有限等待
- 让权等待,禁止busy waiting
通过硬件指令在执行过程中不会被中断。
信号量机制(Semaphore)
好处: 1. 复杂度很低; 2. 消除忙等
信号量S为整型变量,可用wait()和signal()两种原语的标准方式修改S。
wait()用于申请资源。
wait(s){ while s <= 0 ; //no-op s--; }
signal()用于释放资源。
signal(s){ s++; }
信号量的类型:计数型信号; 二元信号量(0,1),用于互斥锁。
物理意义:
wait()用于申请资源,执行value--,如果value<0则表示已无该类资源,则将该进程放入等待S的队列中阻塞。|value|表示等待S的进程数(value<0时)。
signal()用于释放资源,执行value++,如果value<=0,表示有等待进程,用一定的算法唤醒进程之一。
死锁和饥饿(Deadlock and Starvation)
死锁表示系统由于资源调度不合理而导致的僵局,死锁时系统处于饥饿状态。
死锁时CPU没事做;死循环时CPU不停运算。
有界缓存问题(Bounded-Buffer Problem)/生产者-消费者问题
- N个缓冲区,每个能存放一个资源
- 互斥信号量初值为1
- 满信号量初值为0(已放资源的缓冲区量)
- 空信号量初值为N(未放资源的缓冲区量)
分析:
缓冲 共享 -> 互斥 mutex = 1
前趋 P -> C 缓冲区全空,C受制于P,full信号量是否大于零(full < 0)
C -> P 缓冲区全满,p受制于C,empty信号量是否大于零(empty < 0)
The strcture of the producer process
while(true){ // produce an item wait(empty); wait(mutex); // add the item to the buffer signal(mutex); signal(full); }
The structure of the consumer process
while(true){ wait(full); wait(mutex); // remove an item from buffer signal(mutex); signal(empty); // consume the removed item }
读者-写者问题(Readers-Writers Problem)
一组数据集被一系列进程所共享
- 读者--只能读数据集
- 写者--能读且能写
互斥信号量mutex=1,保证readcount被正确更新
写信号量wrt=1
整型量readcount被初始化为0
1 Semaphore mutex,wrt := 1,1; 2 Integer readcount := 0; 3 begin 4 parbegin 5 reader writer 6 while(true){ while(true){ 7 wait(mutex); wait(wrt); 8 readcount++; 9 if(readcount==1) wait(wrt); // waiting 10 signal(mutex); 11 signal(wrt); 12 // reading is performed } 13 14 wait(mutex); 15 readcount--; 16 if(readcount==0) signal(wrt); 17 signal(mutex); 18 } 19 parend 20 end
哲学家进餐问题(Dining-Philosophers Problem)
The structure of Philosopher i:
1 while(true){ 2 wait(chopstick[i]); 3 wait(chopstick[(i+1)%5]); 4 5 //eat 6 7 signal(chopstick[i]); 8 signal(chopstick[(i+1)%5]); 9 10 //think 11 }
该算法存在死锁问题!!!
避免死锁问题:
- 允许最多四个哲学家同时吃饭(当桌上有五只筷子时),添加互斥量mutex=4,在吃之前执行wait(mutex),吃之后执行signal(mutex)
- 同时wait操作,即同时拿起筷子,在两次wait操作之间不可中断 => swait(s1,s2)
- 给哲学家按顺时针或逆时针排序,序号为奇数的先拿左边筷子再拿右边筷子。序号为偶数的先拿右边筷子再拿左边筷子(或相反)
管程(monitor) -- 秘书进程
进程间通信的另一实例:理发师睡觉问题(The Sleeping-Barber Problem)
- Barbershop consists of a waiting room with n chairs, and a barber room with one barber chair;
- No customers, barber goes to sleep;
- If barber is asleep, new customers wakes up him.
1 Var Semephore empty, full, mutex := n, 0, 1; 2 begin 3 parbegin 4 Customer: Barber: 5 begin begin 6 repeat repeat 7 wait(empty); wait(full); 8 wait(mutex); signal(empty); 9 // find a seat; // cutting 10 signal(mutex); until false 11 signal(full); end 12 until false 13 end 14 parend 15 end
该算法未解决顾客来了之后发现没位置离开的情况;
1 Var Semephore customers, barber, mutex := 0, 0, 1; 2 Var int waiting := 0; 3 begin 4 parbegin 5 Customer: Barber: 6 begin begin 7 wait(mutex); repeat: 8 if(waiting < n){ wait(customers); 9 waiting++; wait(mutex); 10 signal(customers); waiting--; 11 signal(mutex); signal(mutex); 12 wait(barber); // cutting 13 } signal(barber); 14 else signal(mutex); until false 15 end 16 parend 17 end