管道:
由pipe函数可以创建管道:
#include <unistd.h> int pipe(int filedes[2]);它有一个读端一个写端,然后通过 filedes 参数传出给用户程序两个文件描述符, filedes[0] 指向管道的读端, filedes[1] 指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过 read(filedes[0]); 或者 write(filedes[1]); 向这个文件读写数据其实是在读写内核缓冲区。 pipe 函数调用成功返回0,调用失败返回-1。
1. 父进程调用 pipe 开辟管道,得到两个文件描述符指向管道的两端。
2. 父进程调用 fork 创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
下面是使用管道的一个小例子:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #define MAX 80 int main() { int n; int fd[2]; pid_t pid; char line[MAX]; if(pipe(fd) < 0){ perror("pipe"); exit(1); }else if((pid = fork()) < 0){ perror("fork"); exit(1); } if(pid > 0){ close(fd[0]); write(fd[1], "hello world\n", 12); wait(NULL); }else{ close(fd[1]); n = read(fd[0], line, MAX); write(STDOUT_FILENO, line, n); } return 0; }运行之后如下图所示:
root@wc:~/Codes/Learn/learnIPC# ./pipe hello world从上面的例子也能看出,使用管道实际上也是存在一些限制的:
1.两个进程通过一个管道只能实现单向通信,比如上面的例子,父进程写子进程读,如果有时候也需要子进程写父进程读,就必须另开一个管道。
管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖
2.先那里继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程之后父子进程之间通信,也可以父进程 fork 两次,把文件描述符传给两个子进程,然后两个子进程之间通信,总之需要通过 fork 传递文件描述符使两个进程都能访问同一管道,它们才能通信。
再进行阻塞io操作的使用,要注意下几种情况:
1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次 read 会返回0,就像读到文件末尾一样。 2. 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次 read 会阻塞,直到管道中有数据可读了才读取数据并返回。 3. 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端 write ,那么该进程会收到信号 SIGPIPE ,通常会导致进程异常终止。在第 33 章 信号会讲到怎样使 SIGPIPE 信号不终止进程。 4. 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次 write 会阻塞,直到管道中有空位置了才写入数据并返回。
FIFO和Unix Domain Socket这两种IPC机制都是利用文件系统中的特殊文件来标识的。可以用 mkfifo 命令创建一个FIFO文件:
root@wc:~/Codes/Learn/learnIPC# mkfifo testFifo root@wc:~/Codes/Learn/learnIPC# ll 总用量 20 drwxr-xr-x 2 root root 4096 1月 15 13:42 ./ drwxr-xr-x 9 root root 4096 1月 15 13:25 ../ -rwxr-xr-x 1 root root 7608 1月 15 13:33 pipe* -rw-r--r-- 1 root root 776 1月 15 13:33 pipe.c prw-r--r-- 1 root root 0 1月 15 13:42 testFifo|注意,fifo在磁盘上实际上没有数据,仅仅用来表示内核中的一条通道。各进程可以打开这个文件进行 read / write ,实际上是在读写内核通道这样就实现了进程间通信。Socket和FIFO的原理类似,也需要一个特殊的socket文件来标识内核中的通道。
前面用到的进程间的IPC总结一下:
父进程通过 fork 可以将打开文件的描述符传递给子进程 子进程结束时,父进程调用 wait 可以得到子进程的终止信息 几个进程可以在文件系统中读写某个共享文件,也可以通过给文件加锁来实现进程间同步 进程之间互发信号,一般使用 SIGUSR1 和 SIGUSR2 实现用户自定义功能 管道 FIFO mmap函数,几个进程可以映射同一内存区 SYS V IPC,以前的SYS V UNIX系统实现的IPC机制,包括消息队列、信号量和共享内 存,现在已经基本废弃 UNIX Domain Socket,目前最广泛使用的IPC机制