基础知识导引
临界资源
在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源)。但对于某些资源来说,其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。
典型的临界资源比如物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(如果这类资源不被看成临界资源加以保护,那么很有可能造成丢数据的问题)。
对于临界资源的访问,必须是互诉进行。也就是当临界资源被占用时,另一个申请临界资源的进程会被阻塞,直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。
对于临界区的访问过程分为四个部分:
1.进入区:查看临界区是否可访问,如果可以访问,则转到步骤二,否则进程会被阻塞
2.临界区:在临界区做操作
3.退出区:清除临界区被占用的标志
4.剩余区:进程与临界区不相关部分的代码
互斥
进程互斥是进程之间的间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待。只有当使用临界资源的进程退出临界区后,这个进程才会解除阻塞状态。
比如进程B需要访问打印机,但此时进程A占有了打印机,进程B会被阻塞,直到进程A释放了打印机资源,进程B才可以继续执行。概念如图所示。
同步
进程同步也是进程之间直接的制约关系,是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系来源于他们之间的合作。
比如说进程A需要从缓冲区读取进程B产生的信息,当缓冲区为空时,进程B因为读取不到信息而被阻塞。而当进程A产生信息放入缓冲区时,进程B才会被唤醒。概念如图所示。
实现临界区互斥的基本方法
硬件实现方法
1 关中断。
2利用Test-and-Set指令实现互斥
3利用Swap指令实现进程互斥
信号量实现方式
这也是我们比较熟悉P V操作。通过设置一个表示资源个数的信号量S,通过对信号量S的P和V操作来实现进程的的互斥。
P和V操作分别来自荷兰语Passeren和Vrijgeven,分别表示占有和释放。P V操作是操作系统的原语,意味着具有原子性。
P操作首先减少信号量,表示有一个进程将占用或等待资源,然后检测S是否小于0,如果小于0则阻塞,如果大于0则占有资源进行执行。
V操作是和P操作相反的操作,首先增加信号量,表示占用或等待资源的进程减少了1个。然后检测S是否小于0,如果小于0则唤醒等待使用S资源的其它进程。
经典题总结
下面将以大量的篇幅总结最近所学和接触到的进程同步问题作出总结,难免有错误出现,还请包涵指出。
Q1:生产者和消费者
问题描述:一组生产者进程和消费者进程共享一个初始为空,大小为n的缓冲区。只有当缓冲区没满的时候,生产者才能将消息放进去。同理,只有当缓冲区不空的时候,消费者才能从中取消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,也只允许一个消费者拿出消息。这里我再解释一下,意思是,同一个时刻只能是一个生产者或者一个消费者操作缓冲区,禁止一下情况:多个生产者或者多个消费者操作缓冲区,同样,一个生产者和一个消费者同时操作也是禁止的。
分析:首先,生产者之间,消费者之间是互斥的关系,同时生产者消费者之间又是协同的关系,属于进程同步。
其次,设置信号量,我们知道信号量个数等于资源数,我们用empty=n表示缓冲区空的缓冲区数目,用full=0表示缓冲区满的缓冲区数目。同时,还要一个信号量mutex来实现,诸进程对缓冲区的互斥访问。
1利用记录性信号量解决生产者-消费者问题
1 int in=0,out=0; 2 item buffer[n]; 3 semaphore mutex=1,empty=n,full=0; 4 void proceducer(){ 5 do{ 6 producer an item nextp; 7 ... 8 wait(empty); 9 wait(mutex); 10 buffer[in]=nextp; 11 in=(in+1)%n; 12 signal(mutex); 13 signal(full); 14 }while(true); 15 } 16 void consumer(){ 17 do{ 18 wait(full); 19 wait(mutex); 20 nextc=buffer[out]; 21 out=(out+1)%n; 22 signal(mutex); 23 signal(empty); 24 consumer the item in nextc; 25 }while(true); 26 } 27 void main() 28 { 29 cobegin 30 proceducer(); consumer(); 31 coend; 32 }
注意8和9行及18和19行的次序,写反会导致死锁
2利用and信号量解决生产者和消费者问题
通过Swait(empty,mutex)代替wait(empty)和wait(mutex).Signal(mutex,full)代替signal(mutex)和signal(full)
通过Swait(full,mutex)代替wait(full)和wait(mutex).Signal(mutex,empty)代替signal(mutex)和signal(empty)
1 int in=0,out=0; 2 item buffer[n]; 3 semaphore mutex=1,empty=n,full=0; 4 void proceducer(){ 5 do{ 6 producer an item nextp; 7 ... 8 Swait(empty,mutex); 9 buffer[in]=nextp; 10 in:=(in+1)%n; 11 Ssignal(mutex,full); 12 }while(true); 13 } 14 void consumer(){ 15 do{ 16 Swait(full,mutex); 17 nextc=buffer[out]; 18 out:=(out+1)%n; 19 Ssignal(mutex,empty); 20 consumer the item in nextc; 21 ... 22 }while(true); 23 } 24 void main() 25 { 26 cobegin 27 proceducer(); consumer(); 28 coend; 29 }
Q2哲学家进餐问题
问题描述:有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上。
在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。
分析:筷子是临界资源,在一段时间内只允许一位哲学家使用,为实现筷子的互斥使用,可以使用一个信号量来表示一只筷子,这五个信号量构成信号量数组。
1 semaphore chopsticks[5]={1,1,1,1,1}; 2 do{ 3 wait(chopsticks[i]);\ 4 wait(chopsticks[i+1]%5); 5 ... 6 //eat 7 signal(chopsticks[i]); 8 signal(chopsticks[i+1]%5); 9 ... 10 //think 11 ... 12 }while(true)