进程同步: 多个进程需要相互配合共同完成一项任务。
进程互斥: 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥;系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源, 而在进程中涉及到互斥资源的程序段叫临界区.
对于多个进程的并发状态,进程是基于CPU时间片轮转的。
我们可以从下面司机和售票员的关系理解进程间的同步:
司机和售票员是合作的关系,售票员先关门,然后司机启动车辆,正常运行时间售票,然后到站停车,售票员开门,这是进程间同步的例子。
我们可以利用后面提到的信号量解决进程间的同步和互斥的问题。
进程间通信的目的:
1)数据传输:一个进程需要将它的数据发送给另外一个进程。2)资源共享:多个进程之间共享同样的资源。
3)进程事件:一个进程需要向另一个或一组进程发送消息,通知他(他们)发生了某种事件(如进程终止时要通知父进程)。
4)进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变(可使用SIGTRAP信号实现)。
在进程间通信的发展中:管道->System V(系统基本都支持)->POSIX进程间通信,其中System V使用的很广泛。
进程间通信分类
文件
文件锁
管道(pipe)和命名管道(FIFO)
信号(signal)
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
套接字(socket)
进程间共享信息的三种方式:
IPC对象的持续性:
随进程持续:一直存在直到打开的最后一个进程结束。(如pipe和FIFO(进程结束,数据删除))
随内核持续:一直存在直到内核自举(重启)或显式删除(如System V消息队列、共享内存、信号量)
随文件系统持续:一直存在直到显式删除,即使内核自举还存在。(POSIX消息队列、共享内存、信号量如果是使用映射文件来实现)
死锁是指多个进程之间相互等待对方的资源,而在得到对方资源之前又不释放自己的资源,这样,造成循环等待的一种现象。如果所有进程都在等待一个不可能发生的事,则进程就死锁了。
死锁产生的四个必要条件
(1)互斥条件
进程对资源进行排它性使用,即在一段时间内某资源仅为一个进程所占用。
(2)请求和保持条件
当进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不可剥夺条件
进程已获得的资源在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
(4)环路等待条件
各个进程组成封闭的环形链,每个进程都等待下一个进程所占用的资源
死锁预防
上面四个条件缺一不可,只要我们随意破坏2,3,4就可以避免死锁。
资源一次性分配:(破坏请求和保持条件)
可剥夺资源:破坏不可剥夺条件)
资源有序分配法:(破坏循环等待条件)
死锁避免
但是上面预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。
由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
银行家算法
为保证资金的安全,银行家规定:
(1) 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
(2) 顾客可以分期贷款,但贷款的总数不能超过最大需求量
(3) 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款
(4) 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
哲学家就餐问题
五个哲学家围在一个圆桌就餐,每个人都必须拿起两把叉子才能用餐;
哲学家就餐问题解法:
(1)服务生解法: 将服务生看作是一个管理者, 哲学家在拿叉子之前需要征得服务生的同意;
(2)最多4个哲学家;
(3)仅当一个哲学家两边筷子都可用时才允许他拿筷子;
(4)给所有哲学家编号,奇数号的哲学家必须首先拿左边的筷子,偶数号的哲学家则反之;
信号量和P、V原语由Dijkstra(迪杰斯特拉)提出, 迪杰斯特拉的三大贡献: goto有害, PV原语, 迪杰斯塔拉最短路算法;
信号量值含义
S>0:S表示可用资源的个数
S=0:表示无可用资源,无等待进程
S<0:|S|表示等待队列中进程个数
下面代码是原子性的,硬件中通过关闭中断来实现,软件通过关闭中断实现。
//信号量定义 typedef struct{ int value; struct process_control_block *list; }semaphore;PV操作:
//P原语 //P(semaphore *S) wait(semaphore *S) { -- S->value; if (S->value < 0) { //将当前进程设置为阻塞状态 //将当前进程的PCB插入相应的阻塞队列S->list末尾 block(S->list); } }
//V原语 //V(semaphore *S) signal(semaphore *S) { ++ S->value; if (S->value <= 0) //表示有进程处于阻塞状态 { //唤醒阻塞队列S->list中等待的一个进程,将其置为就绪态; //将其插入就绪队列; wakeup (S->list); } }使用PV原语解决开篇提到的问题:
注意:
PV 原语分布在不同的进程中一般是为了解决同步问题的,在同一进程中一般是解决互斥的问题。