1 进程间通信
进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用了这种方法。但一般说来, 进程间通信(IPC:InterProcess Communication)不包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多,而且不幸的是,极少方法能在所有的Unix系 统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式)。而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信 方法:管道、消息队列、共享内存、信号量、套接口等等。
2 进程的同步与互斥
(1) 进程同步: 多个进程需要相互配合共同完成一项任务。
(2) 进程互斥: 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥;系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源, 而在进程中涉及到互斥资源的程序段叫临界区.
对于多个进程的并发状态,进程是基于CPU时间片轮转的。
我们可以从下面司机和售票员的关系理解进程间的同步:
司机和售票员是合作的关系,售票员先关门,然后司机启动车辆,正常运行时间售票,然后到站停车,售票员开门,这是进程间同步的例子。
我们可以利用后面提到的信号量解决进程间的同步和互斥的问题。
3 进程间通信的目的
(1) 数据传输:一个进程需要将它的数据发送给另外一个进程。
(2) 资源共享:多个进程之间共享同样的资源。
(3) 进程事件:一个进程需要向另一个或一组进程发送消息,通知他(他们)发生了某种事件(如进程终止时要通知父进程)。
(4) 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变(可使用SIGTRAP信号实现)。
在进程间通信的发展中:管道->System V(系统基本都支持)->POSIX进程间通信,其中System V使用的很广泛。
4 进程间通信分类
注意:进程间通信是通过操作系统,而线程间通信是基于进程的,不需要经过操作系统。
(1) 文件
(2) 文件锁
(3) 管道(pipe), 管道可用于具有亲缘关系进程间的通信,允许一个进程和另外一个与它有共同祖先的进程之间进行通信。
(4) 命名管道(FIFO), 命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间通信,命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
(5) 信号, 信号是比较复杂的通信方式,用于通知接收进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身。
(6) 套接字(socket), 套接字(socket)更为一般的进程间通信机制,可用于不同机器之间的进程通信。
(7) System V IPC
System V 消息队列, System V 共享内存, System V 信号量
(8) POSIX IPC
消息队列, 共享内存, 信号量, 互斥量, 条件变量, 读写锁
消息队列是消息的连接表,包括Posix消息队列、systern V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读队列中的消息、消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓存区大小受限等缺陷。
共享内存:使得多个进程可以访问同一块内存空间,是最快可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其他通信机制,如与信号量结合使用,来达到进程间的同步及互斥。
内存映射;内存映射允许多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
信号量:主要作为进程间以及同一进程不同线程之间的同步手段。
4.1 进程间共享信息的三种方式
4.2 IPC对象的持续性
(1) 随进程持续:一直存在直到打开的最后一个进程结束。(如pipe和FIFO(进程结束,数据删除))
(2) 随内核持续:一直存在直到内核自举(重启)或显式删除(如System V消息队列、共享内存、信号量)
(3) 随文件系统持续:一直存在直到显式删除,即使内核自举还存在。(POSIX消息队列、共享内存、信号量如果是使用映射文件来实现)
5 进程死锁及处理
死锁是指多个进程之间相互等待对方的资源,而在得到对方资源之前又不释放自己的资源,这样,造成循环等待的一种现象。如果所有进程都在等待一个不可能发生的事,则进程就死锁了。
死锁产生的四个必要条件
(1) 互斥条件
进程对资源进行排它性使用,即在一段时间内某资源仅为一个进程所占用。
(2) 请求和保持条件
当进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不可剥夺条件
进程已获得的资源在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
(4) 环路等待条件
各个进程组成封闭的环形链,每个进程都等待下一个进程所占用的资源
5.1 死锁预防
上面四个条件缺一不可,只要我们随意破坏2,3,4就可以避免死锁。
(1) 资源一次性分配:(破坏请求和保持条件)
(2) 可剥夺资源:破坏不可剥夺条件)
(3) 资源有序分配法:(破坏循环等待条件)
5.2 死锁避免
但是上面预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。
由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
5.2.1 银行家算法
为保证资金的安全,银行家规定:
(1) 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
(2) 顾客可以分期贷款,但贷款的总数不能超过最大需求量
(3) 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款
(4) 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
5.2.2 哲学家就餐问题
五个哲学家围在一个圆桌就餐,每个人都必须拿起两把叉子才能用餐;
哲学家就餐问题解法:
(1)服务生解法: 将服务生看作是一个管理者, 哲学家在拿叉子之前需要征得服务生的同意;
(2)最多4个哲学家;
(3)仅当一个哲学家两边筷子都可用时才允许他拿筷子;
(4)给所有哲学家编号,奇数号的哲学家必须首先拿左边的筷子,偶数号的哲学家则反之;
6 信号量
信号量和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 原语分布在不同的进程中一般是为了解决同步问题的,在同一进程中一般是解决互斥的问题。
本文转自: