对于多个进程访问共享数据,需要进程进行同步合作完成操作。
我们可以使用信号量来描述共享数据。
struct semaphore{
int value; // 记录资源的个数
PCB *queue; //记录等待在该信号量上的进程
};
P(semaphore s){ //消费资源
s.value--;
if(s.value < 0){
sleep(s.queue);
}
}
V(semaphore s){ // 产生资源
s.value++;
if(s.value>=0){
wakeup(s.queue);
}
}
这样的话当我们访问共享数据时,我们可以先调用P操作,访问结束后调用V操作,这样就可以当成进程同步的目的。
使用信号量的问题
信号量实际上也是一个共享数据,我们也需要保证在不同调度顺序下,访问其数据的正确性。那么我们在访问信号量时可以通过阻止其他进程的访问来达到此目的,我们可以使用临界区来完成该功能(将读写信号量的代码放在临界区)。
临界区:一次只允许一个进程进入的该进程的那一段代码。
实现临界区的3种方法:
- 软件操控:通过逻辑处理进行->复杂麻烦
- 通过阻止中断来阻止调度。临界区只允许一个进程进入,当另一个进程进入临界区时,一定是发生了调度。要想调度,必须中断(时间片到点发生时钟中断或者当前进程阻塞),那么在进入临界区时关中断,退出时开中断就行了。
可是对多CPU不适用,因为在一个CPU上关中断后,在另一CPU仍然可以有一进程进行访问。需要注意的是关中断只对当前进程有效,因为进程切换会导致EFLAGS寄存器同样进行切换 - 使用硬件原子指令对信号量进行上锁开锁。
总的来说,用临界区保护信号量,用信号量实现进程的同步
死锁
死锁:多个进程相互等待对方持有的资源而造成谁都无法执行的情况。
死锁处理方法:
- 死锁预防:破坏死锁出现的条件
- 死锁避免:检测每个资源请求,如果造成死锁就拒绝
- 死锁检测+恢复:检测到死锁出现,让一些进程回滚,让出资源