51-无名管道
来源:互联网 发布:西安智高诚软件怎么样 编辑:程序博客网 时间:2024/06/06 11:48
这恐怕是最古老的 linux 进程间通信的方式了。这种方式简单而又强大,尤其适合有亲缘关系的进程(通常是父子进程)间通信了。
实际上,你或多或少的都用过管道这种通信方式,比如你在使用的 linux 命令中的管道连接符 |
。所以,对于管道来说,你应该有一种亲切感。
1. 管道为何物?
如果你实践过前面我们讲过的使用本地文件进行进程间通信的方式,那么你就可以把管道理解成位于进程内核空间(如果你不记得这个概念,请翻阅前面的博文)的“文件”。
图1 假想的位于内核的管道文件
给文件加引号,是因为它和文件确实很像,因为它也有描述符。但是它确实又不是普通的本地文件,而是一种抽象的存在。
当进程使用 pipe
函数,就可以打开位于内核中的这个特殊“文件”。同时 pipe
函数会返回两个描述符,一个用于读,一个用于写。如果你使用 fstat
函数来测试该描述符,可以发现此文件类型为 FIFO.
而无名管道的无名,指的就是这个虚幻的“文件”,它没有名字。
本质上,pipe 函数会在进程内核空间申请一块内存(比如一个内存页,一般是 4KB),然后把这块内存当成一个先进先出(FIFO)的循环队列来存取数据,这一切都由操作系统帮助我们实现了。
2. pipe 函数
- 函数原型
int pipe(int pipefd[2]);
pipe 函数打开的文件描述符是通过参数(数组)传递出来的,而返回值表示打开成功(0)或失败(-1)。
它的参数是一个大小为 2 的数组。此数组的第 0 个元素用来接收以读的方式打开的描述符,而第 1 个元素用来接收以写的方式打开的描述符。也就是说,pipefd[0] 是用于读的,而 pipefd[1] 是用于写的。
打开了文件描述符后,就可以使用 read(pipefd[0]) 和 write(pipefd[1]) 来读写数据了。
- 注意事项
这两个分别用于读写的描述符必须同时打开才行,否则会出问题。
- 如果关闭读(
close(pipefd[0])
)端保留写端,继续向写端(pipefd[1]) 端写数据(write 函数)的进程会收到 SIGPIPE 信号。 - 如果关闭写(
close(pipefd[1])
)端面保留读端,继续向读端(pipefd[0])读数据(read 函数),read 函数会返回 0.
3. 用 pipe 进行进程间通信
当在进程用 pipe
函数打开两个描述符后,我们可以 fork 出一个子进程。这样,子进程也会继承这两个描述符,而且这两个文件描述符的引用计数会变成 2。(如果忘记了这个特性,请复习《fork 函数与文件共享》 )
如果你需要父进程向子进程发送数据,那么得把父进程的 pipefd[0] (读端)关闭,而在子进程中把 pipefd[1] 写端关闭,反之亦然。为什么要这样做?实际上是避免出错。传统上 pipe 管道只能用于半双工通信(即一端只能发,不能收;而另一端只能收不参发),为了安全起见,各个进程需要把不用的那一端关闭(本质上是引用计数减 1)。
图2 和图 3 演示了上述过程。
步骤一:fork 子进程
图2 fork 后的半双工管道步骤二:关闭父进程读端,关闭子进程写端
图3 从父进程到子进程的管道
要想实现全双工通信(既能发又能收)怎么办?你可以再使用 pipe 打开两个描述符,或者使用其它的 ipc 手段。
接下来,看实例。
4. 实例
下面的程序功能如下:父进程 fork 出一个子进程,通过无名管道向子进程发送字符,子进程收到数据后将字符串中的小写字符转换成大写并输出。
为了突显程序结构,删除了错误处理部分的代码。
- 代码
// hellopipe.c#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <ctype.h>void child(int *fd) { close(fd[1]); // 子进程关闭写端 char buf[64]; int n = 0,i; while(1) { n = read(fd[0], buf, 64); // 如果没有数据可读,read 会阻塞;如果父进程退出,read 返回 0. for (i = 0; i < n; ++i) putchar(toupper(buf[i])); if (*buf == 'q') { exit(0); close(fd[0]); } if (n == 0) { puts("no data to read!"); sleep(1); } } exit(0); }int main() { int fd[2]; int n = 0; char buf[64] = { 0 }; if (pipe(fd) < 0) { perror("pipe"); return -1; } pid_t pid = fork(); if (pid == 0) { child(fd); } close(fd[0]);// 父进程关闭读端 while (1) { n = read(STDIN_FILENO, buf, 64); write(fd[1], buf, n); if (*buf == 'q') { exit(0); close(fd[1]); } } return 0;}
- 编译和运行
$ gcc hellopipe.c -o hellopipe$ ./hellopipe
- 运行结果
hello world // 输入HELLO WORLD // 输出24215lkjl // 输入24215LKJL // 输出
5. 总结
- 掌握 pipe 函数
- 理解 pipe 函数原理(fifo 队列)
练习:在运行上面的程序时,分别 kill 父进程和子进程,看看有什么结果,并解释现象。
- 51-无名管道
- 无名管道
- 无名管道
- 无名管道
- 无名管道
- 无名管道
- 无名管道
- 无名管道
- 无名管道
- 无名管道
- 无名管道
- 无名管道
- 无名管道
- 有名管道&无名管道
- 无名管道和有名管道
- 无名管道有名管道续
- 无名管道和有名管道
- 无名管道和有名管道
- linux下存储管理之三
- jQuery事件绑定(二)
- win+pycharm+django+mysql开发(win系统pythonweb开发)(三)
- 复杂的列表视图
- WebService CXF整合Spring框架
- 51-无名管道
- 讯为4412按键不能关机的问题解决
- StringBuffer 与 StringBuilder的区别与联系
- eclipse 更新sdk
- 出现服务Network List Service不能启动的解决方法
- Ionic入门
- SSH框架之Struts的请求数据自动封装
- Android 自定义控件写法详解
- Android 开发实战经验总结