1.管道
管道是UNIX系统中最古老的 IPC 方法,管道通常用来在有关系的进程中传递数据,即父子进程间,父孙子进程间,两个子进程间等(由于fork会复制父进程的文件描述符)。管道是一个字节流,也就是说它的数据没有边界(但可以在程序中实现独立的固定大小数据的传输),它可以读或写任意大小的数据,但是数据是按先进先出的顺序传输,而不能进行随机访问(如lseek)。
管道一般用两个文件描述符标记,fd[0]表示读取端,fd[1]表示写入端。进程读取空的管道将会阻塞,直到有数据可读。当管道的写入端关闭时,管道的读取端将读到EOF标志(即不会阻塞,read函数会返回0)。管道的容量为PIPE_BUF(在limits.h中定义的宏,64KB),如果写入大于管道容量大小的数据,将会引起阻塞,直到有空间可写。使用fcntl(fd, F_SETPIPE_SZ, size)可以该变管道的容量(fd即管道的描述符),该函数会返回内核对管道的容量调整后的大小
UNIX标准中,管道是单向的,即只能一端进行读操作(通常会关闭写端),另一端进行写操作(通常会关闭读端)。需要双向通信时,一般需要建立两个管道。
关闭一端的原因:
1.只有当管道的所有文件描述符关闭时,管道才销毁
2.只有当所有读端关闭时,管道满时写端才会收到SIGPIPE信号(默认会关闭进程)
3.只有当所有写端关闭时,管道空时读端才会收到EOF标志
例子:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define BUF_SIZE 5
void err_exit(char *s)
{
perror(s);
exit(-1);
}
int main(int argc, char *argv[])
{
int pfd[2]; /* 管道描述符 */
char buf[BUF_SIZE];
ssize_t numRead;
if (pipe(pfd) == -1)/* 创建管道 */
err_exit("pipe");
switch (fork()) {
case -1:
err_exit("fork");
case 0: /* 子进程,读管道 */
if (close(pfd[1]) == -1) /* 关闭写端 */
err_exit("close - child");
while(1) {
printf("before read\n");
numRead = read(pfd[0], buf, BUF_SIZE);
if (numRead == -1)
err_exit("child - read");
if (numRead == 0)
break; /* 读到EOF */
printf("%s\n", buf);
printf("after read\n");
}
write(STDOUT_FILENO, "\n", 1);
if (close(pfd[0]) == -1)
err_exit("close");
return 0;
default: /* 父进程,写管道 */
if (close(pfd[0]) == -1) /* 关闭读端 */
err_exit("close - parent");
if (write(pfd[1], "hello", 5) != 5)
printf("parent pipe failed write\n");
sleep(3);
if (write(pfd[1], "world", 5) != 5)
printf("parent pipe failed write\n");
if (close(pfd[1]) == -1) /* 子进程将读到 EOF */
err_exit("close");
wait(NULL); /* 等待子进程结束 */
exit(0);
}
}
2.命名管道(FIFO)
命名管道(FIFO)则是管道的一种变种,它可以在任意进程间传递数。FIFO在文件系统中创建FIFO文件,并拥有文件名。对FIFO的操作,就和操作普通文件一样。
可以在命令行创建FIFO:
$ mkfifo [ -m mode ] pathname