管道只能在具有公共祖先的两个进程之间使用,通常,一个管道由一个进程创建,在进程调用fork之后,这个管道就可以在父子进程之间使用;
#include
int pipe(int fd[2]);
fd[0]为读打开,fd[1]为写打开,fd[1]的输出是fd[0]的输入(创建一个管道,然后调用fork,父进程关闭其读端,子进程关闭其写端,就建立了父进程写子进程读的管道);
#include
FILE * popen(const char *cmdstring, const char *type); // 成功返回文件指针,错误返回NULL
int pclose(FILE *fp); // 成功返回cmdstring的终止状态,错误,返回-1
函数popen先执行fork,然后调用exec执行cmdstring,并且返回一个标准的I/O文件指针,type如果是“r”,则文件指针链接到cmdstring的标准输出,如果type是“w”,则文件则连接到cmdstring的标准输入;
popen只提供连接到另外一个进程的标准输入或标准输出的单向管道,而协同进程则有连接到另外一个进程的两个单向管道:一个接到其标准输入,另外一个接到其标准输出(可以将数据写到标准输入,处理后,从标准输出读取数据);
int fd1[2],fd2[2];
if((pid = fork()) > 0){ // parent
close(fd1[0]);
close(fd2[1]);
...
}else{ // child
close(fd1[1]);
close(fd2[0]);
...
}
FIFO被称为命名管道( popen,协同进程其实都是匿名管道的一种),未命名的管道只能在两个相关的进程之间使用,而且这两个相关进程还必须有一个共同创建它们的祖先进程,FIFO可以用于不相关进程交换数据;
#include
int mkfifo(const char* *path, mode_t mode); //成功返回0,失败返回-1
int mkfifioat(int fd, const char *path, mode_t mode); //成功返回0,失败返回-1
path实际上相当于管道的“名字”,所以一个给定的FIFO会有多个写进程是常见的,这就意味着如果不希望多个进程所写的数据交叉,则必须考虑原子写操作;
FIFO常用的两种用途:
1.shell命令将FIFO将数据从一条管道传到另外几条管道时,不需要创建中间临时文件;(单写入多读取,发散)
2.客户进程-服务器进程中,FIFO用作汇聚点,在客户进程和服务器进程之间传递数据;(多写入单读取,汇聚)
消息队列是消息的链接表,存储在内核中,由消息队列标识符标识;
#include
int msgget(key_t key, int flag); // 成功,返回队列ID,错误,返回-1
int msgsnd(int msqid, const void *ptr, siez_t nbytes, int flag); // 成功,返回0,错误,返回-1
ssize_t msgrcv(int msqid, void* ptr, size_t nbytes, long type, int flag); // 成功,返回消息数据部分的长度,错误返回-1
msgget用于创建一个新队列,或打开一个现有队列,msgsed将心消息添加到队列尾端,msgrcv用于从队列中读取消息,并不一定以先进先出次序读取消息,也可以按消息的类型字段读取消息;
信号量是一个计数器(与管道,FIFO,和消息队列不同),用于多个进程(线程)提供对共享数据的访问;
为了获得共享资源,进程需要执行以下的动作:
1. 测试控制资源的信号量;
2. 若为正,则进程可以使用该资源,这种情况下,进程会将信号量减一(资源被占用);
3. 否则,信息量的值为0,进程进入休眠状态,直到信号量大于0,进程被唤醒,进入第一步;
共享存储的一种形式上,在多个进程中将同一个文件映射到它们的地址空间,XSI共享则是没有相关的文件,XSI共享存储的是内存的匿名段;
IPC结构不使用文件描述符,所以不能对它们使用多路转接I/O函数(select 和 poll),使得很难一次使用一个以上的IPC结构;
信息量实际上是同步原语,而不是IPC,常用于共享资源(共享存储)的同步访问;