经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
linux 进程通信之 管道和FIFO
来源:cnblogs  作者:小石王  时间:2019/4/30 11:32:58  对本文有异议

进程间通信:IPC概念

IPC:Interprocess Communication,通过内核提供的缓冲区进行数据交换的机制。

IPC通信的方式:

  • pipe:管道(最简单)
  • fifo:有名管道
  • mmap:打开一块共享的内存(速度最快)
  • 本地socket:最稳定
  • 信号:携带信息量最小
  • 共享内存
  • 消息队列

通信种类:

  • 单工(广播)
  • 单双工(对讲机)
  • 全双工(电话)

一,管道PIPE

pipe通信是单双工的。

pipe通信,只能在有血缘关系的进程间通信。父子进程,兄弟进程,爷孙进程等。

  1. #include <unistd.h>
  2. int pipe(int pipefd[2]);
  • pipefd:【0】是读端,【1】是写端。
  • 返回值:成功返回0;失败返回-1。

例子:

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdio.h>
  4. int main(){
  5. int fds[2];
  6. pipe(fds);
  7. pid_t pid = fork();
  8. if(pid == 0){
  9. write(fds[1], "hello\n", 6);
  10. char buf[10] = {0};
  11. int ret = read(fds[0], buf, sizeof buf);
  12. if(ret > 0){
  13. printf("%s", buf);
  14. }
  15. }
  16. if(pid > 0){
  17. char buf[10] = {0};
  18. int ret = read(fds[0], buf, sizeof buf);
  19. if(ret > 0){
  20. printf("%s", buf);
  21. }
  22. write(fds[1], "world\n", 6);
  23. sleep(1);
  24. }
  25. }

例子1:子进程写,父进程读。

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdio.h>
  4. int main(){
  5. int fds[2];
  6. pipe(fds);
  7. pid_t pid = fork();
  8. if(pid == 0){
  9. write(fds[1], "hello\n", 6);
  10. /*
  11. char buf[10] = {0};
  12. int ret = read(fds[0], buf, sizeof buf);
  13. if(ret > 0){
  14. printf("%s", buf);
  15. }
  16. */
  17. }
  18. if(pid > 0){
  19. char buf[10] = {0};
  20. int ret = read(fds[0], buf, sizeof buf);
  21. if(ret > 0){
  22. printf("%s", buf);
  23. }
  24. //write(fds[1], "world\n", 6);
  25. //sleep(1);
  26. }
  27. }

例子2:用管道实现【ps aux | grep bash】命令。

实现办法,用dup2函数把标准输出,重定向到写端;再把标准输入重定向到读端。

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdio.h>
  4. int main(){
  5. int fds[2];
  6. pipe(fds);
  7. pid_t pid = fork();
  8. int stdoutfd = dup(STDOUT_FILENO);
  9. if(pid == 0){
  10. //close(fds[0]);//------------①
  11. dup2(fds[1], STDOUT_FILENO);
  12. execlp("ps", "ps", "aux", NULL);
  13. }
  14. if(pid > 0){
  15. //close(fds[1]);//----------②
  16. dup2(fds[0], STDIN_FILENO);
  17. execlp("grep", "grep", "bash", NULL);
  18. dup2(stdoutfd, STDOUT_FILENO);
  19. }
  20. }

运行结果:发现程序没有结束,阻塞住了,必须按ctol-c才能结束。

  1. ys@ys:~/test$ ./pi2
  2. ys 1551 0.0 0.2 29692 5548 pts/0 Ss 10:05 0:00 bash
  3. ys 2316 0.0 0.2 29560 5328 pts/1 Ss+ 11:33 0:00 bash
  4. ys 2486 0.0 0.0 21536 1060 pts/0 S+ 11:56 0:00 grep bash

用【ps aux】调查一下,发现,由于父进程【grep bash】没有结束还没有回收子进程,导致【ps】变成了僵尸进程。

  1. ys 2437 0.0 0.0 21536 1088 pts/0 S+ 11:50 0:00 grep bash
  2. ys 2438 0.1 0.0 0 0 pts/0 Z+ 11:50 0:00 [ps] <defunct>
  3. ys 2439 0.0 0.1 44472 3800 pts/1 R+ 11:50 0:00 ps aux

为什么父进程【grep bash】没有结束呢?确实在子进程里给父进程【ps aux】的输出结果了啊!

这是grep命令本身的缘故,在终端执行【grep bash】的话,就变成了阻塞状态,grep在等待标准输入,如果输入了【bash】grep就会给出结果,但是还是在继续等待标准输入,所以这就是父进程没有结束,阻塞在【grep bash】那里的原因。

解决办法:告诉【grep】,管道的写端不会再写入数据了后,grep就不会再继续等待,所以grep就会结束。grep的结束了,父进程也就结束了,所以僵尸进程也就自动消失了。

需要改代码的地方是②处,加上【close(fds[1]);】,就告诉了grep,已经没有写入了,所以grep就不会阻塞,父进程就能够结束掉。

注意:其实应该在子进程里也应该加上【close(fds[1]);】,才能达到写端全部关闭了,为什么没写也没错误呢,因为子进程先执行结束了,进程结束后,系统会自动把进程中打开的文件描述符全部关闭,所以没在子进程里写关闭写端的代码,也没出问题。

管道有如下的规则:

  • 读管道时:
    • 写端全部关闭:read函数返回0,相当于没有再能读取到的了。
    • 写端未全部关闭:
      • 管道里有数据:read函数能够读到数据。
      • 管道里没有数据:read 阻塞。(可以用fcnlt设置成非阻塞)
  • 写管道时:
    • 读端全部关闭:write函数会产生SIGPIPE信号,程序异常结束。
    • 读端未全部关闭:
      • 管道已满:write函数阻塞等待。
      • 管道未满:write函数正常写入。

例子1:写端全部关闭:read函数返回0。

在①和②两处必须都关闭写端,read函数才能返回0.

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdio.h>
  4. #include <sys/wait.h>
  5. int main(){
  6. int fds[2];
  7. pipe(fds);
  8. pid_t pid = fork();
  9. if(pid == 0){
  10. char buf[10] = {0};
  11. int ret = read(fds[0], buf, sizeof buf);
  12. if(ret > 0){
  13. printf("%s", buf);
  14. }
  15. close(fds[1]);//----①
  16. sleep(1);
  17. if(read(fds[0],buf, sizeof buf) == 0){
  18. printf("all closed\n");
  19. }
  20. }
  21. if(pid > 0){
  22. int ret = write(fds[1], "hello\n", 6);
  23. close(fds[1]);//------②
  24. wait(NULL);
  25. }
  26. }

例子2:读端全部关闭:write函数会产生SIGPIPE信号,程序异常结束。

在①和②两处必须都关闭读端,write函数会产生SIGPIPE信号。

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdio.h>
  4. #include <sys/wait.h>
  5. int main(){
  6. int fds[2];
  7. pipe(fds);
  8. pid_t pid = fork();
  9. if(pid == 0){
  10. close(fds[0]);//------②
  11. int ret = write(fds[1], "hello\n", 6);
  12. }
  13. if(pid > 0){
  14. close(fds[0]);//----①
  15. //close(fds[1]);
  16. int status;
  17. wait(&status);
  18. if(WIFSIGNALED(status)){
  19. printf("killed by %d\n", WTERMSIG(status));
  20. }
  21. }
  22. }

执行结果:【killed by 13】。13是SIGPIPE

查看系统默认的管道缓冲区的大小:ulimit -a

  1. pipe size (512 bytes, -p) 8

查看系统默认的管道缓冲区的大小的函数:fpathconf

  1. #include <unistd.h>
  2. long fpathconf(int fd, int name);
  • fd:文件描述符
  • name:可以选择很多宏
    • _PC_PIPE_BUF:代表管道。

例子:

  1. #include <unistd.h>
  2. #include <stdio.h>
  3. int main(){
  4. int fds[2];
  5. pipe(fds);
  6. long ret = fpathconf(fds[0], _PC_PIPE_BUF);
  7. printf("size:%ld\n", ret);
  8. }

执行结果:size:4096

上面的【例子:用管道实现【ps aux | grep bash】命令】有个问题,父进程直接调用了exec函数,导致无法在父进程中回收子进程的资源。下面的例子就去解决这个问题,方法是,不在父进程里调用exec函数,在2个兄弟子进程里分别调用exec函数,然后在父进程里回收资源。

  1. #include <unistd.h>
  2. #include <stdio.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. int main(){
  6. int fds[2];
  7. pipe(fds);
  8. pid_t pid = fork();
  9. if(pid == 0){
  10. pid_t pid1 = fork();
  11. if(pid1 == 0){
  12. dup2(fds[1], STDOUT_FILENO);
  13. execlp("ps", "ps", "aux", NULL);
  14. }
  15. else if(pid1 > 0){
  16. close(fds[1]);//----①
  17. dup2(fds[0], STDIN_FILENO);
  18. execlp("grep", "grep", "bash", NULL);
  19. //dup2(stdoutfd, STDOUT_FILENO);
  20. }
  21. }
  22. else if(pid > 0){
  23. close(fds[1]);//----②
  24. wait(NULL);
  25. }
  26. }

注意在①和②处的关闭代码。

到此为止,可以看出来管道的

  • 优点:使用起来简单。
  • 缺点:只能在有血缘关系的进程间使用。

二,FIFO通信

创建FIFO伪文件的命令:【mkfifo】

  1. prw-r--r-- 1 ys ys 0 4 29 15:59 myfifo

文件类型为P,大小为0。

也可以用函数:mkfifo创建

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. int mkfifo(const char *pathname, mode_t mode);
  • pathname:文件名
  • mode:文件权限
  • 返回值:0成功;-1失败

FIFO通信原理:内核对fifo文件开辟一个缓冲区,操作fifo伪文件,就相当于操作缓冲区,实现里进程间的通信。实际上就是文件读写。

FIFO例子:传进一个事先用mkfifo 创建好的FIFO文件。可以同时打开多个读端和写端。

  • 写端:

    1. #include <sys/types.h>
    2. #include <sys/stat.h>
    3. #include <fcntl.h>
    4. #include <stdio.h>
    5. #include <unistd.h>
    6. int main(int argc, char* argv[]){
    7. printf("begin write\n");
    8. int fd = open(argv[1], O_WRONLY);
    9. printf("end write\n");
    10. int num = 0;
    11. char buf[20] = {0};
    12. while(1){
    13. sprintf(buf, "num=%04d\n", num++);
    14. write(fd, buf, strlen(buf));
    15. sleep(1);
    16. }
    17. close(fd);
    18. }
  • 读端:

    1. #include <sys/types.h>
    2. #include <sys/stat.h>
    3. #include <fcntl.h>
    4. #include <stdio.h>
    5. #include <unistd.h>
    6. #include <string.h>
    7. int main(int argc, char* argv[]){
    8. printf("begin read\n");
    9. int fd = open(argv[1], O_RDONLY);
    10. printf("end read\n");
    11. int num = 0;
    12. char buf[20] = {0};
    13. while(1){
    14. memset(buf, 0x00, sizeof buf);
    15. int ret = read(fd, buf, sizeof buf);
    16. if(ret > 0){
    17. printf("%s\n", buf);
    18. }
    19. else if(ret == 0){
    20. break;
    21. }
    22. sleep(1);
    23. }
    24. close(fd);
    25. }

例子里有两个注意点:

  • open的时候是阻塞的,只有当读端和写端都打开后,open函数才会返回。非FIFO文件的open函数不是阻塞的。

    1. FIFOs
    2. Opening the read or write end of a FIFO blocks until the other end is
    3. also opened (by another process or thread). See fifo(7) for further
    4. details.
  • 强制终止读端进程后,写端会自动终止。理由是读端已经关闭了,再往里写就会收到SIGFIFO信号,这个和管道的原理是一样的。

原文链接:http://www.cnblogs.com/xiaoshiwang/p/10794806.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号