引言
- 进程间除了可以使用 fork和exec传送打开的文件,也可以使用IPC(Inter Processing Communication)的方式通信。
- 经典的IPC包括:管道、FIFO、消息队列、信号量、以及共享存储。
- 在新的程序中要尽可能的避免使用消息队列和信号量,应该考虑全双工管道和记录锁,他们使用会更加的简单。共享存储依然有它的用途,虽然使用mmap函数也能提供同样的功能。
- 以下的所有的方式,除了POSIX信号量,其他方式生命周期都是随内核,不手动释就不会消失。
管道
- 管道是UNIX系统最古老的通信方式。有以下局限性:
1、历史上是半双工的,由于兼容性,只有部分系统支持全双工。
2、管道只能在公共祖先的两个进程间使用。通过由一个进程f创建,然后fork 之后,管道就能在父子啊进程中使用。FIFO就没有这个局限性。 - 虽然管道具有一定的局限性,但是目前应用最多的依然还是未命名管道和FIFO(命名管道) 。
- 使用pipe(int fd[2])创建管道,fd[0]表示读端,fd[1]表示写端,可使用close关闭,也可以使用read/write直接读写。
函数popen和pclose
- 常见的操作是创建一个链接到另一个进程的管道,然后输入或者输出数据,这两个函数就是标准IO提供的。
#include <stdio.h>
// 返回值:若成功返回文件指针,出错返回NULL
FILE *popen(const char*cmdstring, const char *type);
//成功返回cmdstring的终止状态,出错返回-1
int pclose(FILE *fp);
- 函数popen先执行fork,然后调用exec执行cmdstring,返回一个IO文件指针,若type是r,则是标准输出。如type是“w”,则是标准输入。
- cmdstring由Bourne shell执行:sh -c cmdstring
协同进程
- popen函数只提供连接到另一个进程的标准输入或者标准输出的一个单向通道,而协同进程则有连接到另一个进程的两个单向管道:一个标准输入一个标准输出。具体例子详见C++常用函数中的实现。
FIFO
- FIFO有时候被成为命名管道,未命名的管道只能在两个相关进程间通信,而FIFO可以在不相关的进程间通信。
- 使用mkfifo或者mkfifoat创建FIFO,要用open打开它。
- FIFO有两个用途:
1、shell使用FIFO将数据从一个管道传送到另一个管道,无需创建中间临时文件。
2、在C/S程序中FIFO用作汇聚点,在客户端于服务器之间传递数据。
XSI IPC结构的优缺点
- XSI IPC 有三种:消息队列、信号量以及共享存储器。
- 缺点:
1、IPC结构是系统范围内起作用的,没有引用计数。导致资源的删除不彻底。
2、IPC在文件系统中没有名字,无法直接的去修改它们的属性。
3、IPC不使用文件描述符,不能对他们使用多路转接IO函数(select、poll)。 - 优点:
1、它们是可靠的、控制流的以及面向记录的。控制流的意思是:当缓冲区短缺的时候,或者接收线程不能接收更多消息的时候,发送线程就要休眠,到控制流条件消失时,发送进程自动唤醒。
2、它们可以以非先进先出的次序处理。
消息队列
- 消息队列是消息的链接表,存储在内核中,由消息队列标识符标识。
- 并不一定要按照先进先出的次序来取得消息,也可以按照消息的字段取得消息。
- 最大的消息队列还要根据系统安装的RAM数量来决定。
- 如果需要客户进程于服务器进程要进行双向数据交流,可以使用消息队列或者全双工管道。
- 消息度列原先的目的是提供高于一般速度的IPC,但是现在与其他的IPC比较,速度方面已经没有太大的差别了。所以在新的应用程序中并不实用。
- 使用:
1、调用的第一个函数通常是msgget,其功能是打开一个现有的队列,或者创建一个队列,
2、msgctl函数对队列执行多种操作。
3、调用msgsnd将数据放到消息队列中。
4、调用msgrcv从队列中取用消息。
信号量
-
进程间的信号量与已经介绍的IPC机构不太一样,他是一个计数器,用于未多个进程提供对共享数据对象的访问。
-
为了获得共享资源,进程需要执行以下几个步骤:
1、测试该资源信号量。
2、若信号量的值为正,则进程可以使用该资源,进程会将信号量的值减1,表示使用了一个资源单位。当进程不再使用时,信号量加1.
3、若为0,进程进入休眠,直到信号量大于0才被唤醒,并重新进入1中。 -
常用的信号量使用形式称之为二元信号量,控制单个资源,初始值为1.
-
XSI信号量则要复杂很多,会有以下三种缺陷:
1、信号量并非单个非负值,当创建信号量的时候,要指定集合中信号量的数量。
2、信号量的创建(semget)独立于初始化(semctl),无法保证其原子性。
3、即便线程没有使用XSI IPC了,它们任然存在,有的在程序终止时并没有释放已经分配给它的信号量。 -
使用:
1、使用semget来获得一个信号量ID。
2、调用semctl函数进行多种信号量的操作。
3、semop函数自动执行信号量集合上的操作数组。 -
与锁的比较:在Linux上记录锁比信号更快,但是共享的存储中,互斥量的性能比信号量和记录锁都要优越。
共享存储
- 共享存储允许两个或者多个进程共享一个给定的区域,数据不需要再进程间进行复制,所以速度最快。
- 若服务器正在将数据放入共享存储区,则在它做完存储操作之前,客户线程不应该去取这些数据。
- 使用:
1、调用shmget获得一个共享存储标识符
2、使用shmctl函数对共享存储段执行多种操作。
3、一旦创建一个共享存储段,进程可以用shmat将其连接到它的地址空间中。注意将addr置为0,以便系统自己选择地址。
4、当调用结束,使用shmdt将其分离。
POSIX信号量
- POSIX信号量解决了XSI信号量的几个缺陷,使用相对更加的简单,上述的所有方式都。
- 命名信号量使用:
1、可以调用sem_open函数创建一个新的命名信号量或者使用一个现有信号量。
2、如果进程没有调用sem_close退出,内核会自动关闭任何打开的信号
3、可以使用sem_unlink销毁一个命名的信号量;可以使用sem_wait和sem_trywait来实现信号的减1操作
4、可以使用sem_timewait函数阻塞一段时间。
5、可以使用sempost函数使得信号增加1 - 未命名信号量使用:
1、可以调用sem_init函数创建一个未命名的信号量。
2、能够使用sem_getvalue检索信号量的值。
3、使用完信号量时,调用sem_destroy函数丢弃信号量资源。