Linux进程间通信-管道(pipe)

本系列文章主要是学习记录Linux下进程间通信的方式。

常用的进程间通信方式:管道、FIFO、消息队列、信号量以及共享存储。

参考文档:《UNIX环境高级编程(第三版)》

参考视频:Linux进程通信  推荐看看,老师讲得很不错

Linux核心版本:2.6.32-431.el6.x86_64

注:本文档只是简单介绍IPC,更详细的内容请查看参考文档和相应视频。

本文介绍利用管道进行进程间的通信。

1  简介

管道是最古老的一种方式,局限性:

  • 半双工方式,数据只能在一个方向上流动;
  • 只能在具有公共祖先的两个进程间使用。

2  函数接口

1 #include <unistd.h>
2 int pipe(int pipefd[2]);
3 说明:创建一个pipe
4 返回值:成功返回0,出错返回-1
5 参数[out]:fd保存返回的两个文件描述符,fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]的输入。

3  通信模型

通信模型一:进程先调用pipe,接着调用fork,从而创建从父进程到子进程的IPC通道。

通信模型二:从父进程到子进程的通道。父进程关闭管道的读端(fd[0]),子进程关闭写端(fd[1])。

 

当管道一端被关闭后,以下两条规则起作用:

当读(read)一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束。

如果写(write)一个读端已被关闭的管道,则产生信号SIGPIPE。

 4  读写特性

1)可通过打开两个管道来创建一个双向的管道;

2)管道是阻塞性的,当进程从管道中读取数据,若没有数据进程会阻塞;

3)当一个进程往管道中不断的写入数据但是没有进程去读取数据,此时只要管道没有满是可以的,但若管道满的则会报错。

 5  测试代码

 (1)实例1

创建一个从父进程到子进程的管道,并且父进程通过该管道向子进程传送数据。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 
 4 #define MAXLINE 512
 5 
 6 int main(void)
 7 {
 8     int n;
 9     int fd[2];
10     pid_t pid;
11     char line[MAXLINE];
12 
13     if (pipe(fd) < 0) {  //创建管道
14         perror("pipe error!");
15         return -1;
16     }
17     if ((pid = fork()) < 0) {  //创建子进程
18         perror("fork error!");
19         return -1;
20     } else if (pid > 0) {  //父进程
21         close(fd[0]);    //父进程关闭读管道
22         write(fd[1], "hello world
", 12);  //父进程向管道中写入数据
23         close(fd[1]);
24         wait(0);  //等待子进程结束
25     } else {  //子进程
26         close(fd[1]);  //子进程关闭写管道
27         n = read(fd[0], line, MAXLINE);  //子进程从管道中读取数据
28         write(STDOUT_FILENO, line, n);   //标准输出
29         close(fd[0]);
30     }
31 
32     return 0;
33 }
View Code

(2)实例2

使用pipe实现类似于:cat /etc/passwd | grep root这个命令。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 
 5 char *cmd1[3] = {"/bin/cat", "/etc/passwd", NULL};
 6 char *cmd2[3] = {"/bin/grep", "root", NULL};
 7 
 8 int main(void)
 9 {
10     int fd[2];
11     int i = 0;
12     pid_t pid;
13 
14     if (pipe(fd) < 0) {
15         perror("pipe error");
16         exit(1);
17     }
18 
19     for (i = 0; i < 2; i++) {
20         pid = fork();
21         if (pid < 0) {
22             perror("fork error");
23             exit(1);
24         } else if (pid == 0) {
25             if (i == 0) {  //第一个子进程
26                 //负责往管道写入数据
27                 close(fd[0]);  //关闭读端
28                 //cat命令执行结果是标准输出,需要将标准输出重定向到管道写端
29                 //下面命令执行的结果会写入到管道中,而不是输出到屏幕
30                 if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) {
31                     perror("dup2 error");
32                     exit(1);
33                 }
34                 close(fd[1]);  //已经复制了一份,原来的可以关闭
35                 //调用exce函数执行cat命令
36                 if (execvp(cmd1[0], cmd1) < 0) {
37                     perror("execvp error");
38                     exit(1);
39                 }
40                 break;
41             }
42             if (i == 1) {  //第二个子进程
43                 //负责从管道读取数据
44                 close(fd[1]);  //关闭写端
45                 //grep命令默认读取的内容来源于标准输入
46                 //需要将标准输入重定向到管道的读端
47                 //下面命令执行时从管道的读端读取内容,而不是从标准输入读取
48                 if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) {
49                     perror("dup2 error");
50                     exit(1);
51                 }
52                 close(fd[0]);
53                 //调用exce函数执行grep命令
54                 if (execvp(cmd2[0], cmd2) < 0) {
55                     perror("execvp error");
56                     exit(1);
57                 }
58                 break;
59             }
60         } else {  //父进程,仅用于创建子进程
61             //等待子进程创建并回收
62             if (i == 1) {
63                 //等待子进程全部创建完毕,才回收
64                 close(fd[0]);
65                 close(fd[1]);
66                 wait(0);
67                 wait(0);
68             }
69         }
70     }
71 
72     return 0;
73 }
View Code

(3)实例3

使用pipe实现一个协同进程。

 创建两个管道,父进程向管道1中写入数据,并从管道2中读取子进程计算出的结果值;

子进程从管道1中读取数据,并调用add程序进行累加,将累加的结果写入到管道2中。

add程序实现代码:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 
 5 int main(void)
 6 {
 7     int x, y;
 8 
 9     if (read(STDIN_FILENO, &x, sizeof(int)) < 0) {
10         perror("read error");
11     }
12     if (read(STDIN_FILENO, &y, sizeof(int)) < 0) {
13         perror("read error");
14     }
15 
16     int result = x + y;
17     if (write(STDOUT_FILENO, &result, sizeof(int)) < sizeof(int)) {
18         perror("write error");
19     }
20 
21     return 0;
22 }
View Code

协同进程实现代码:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 
 5 int main(void)
 6 {
 7     int fda[2], fdb[2];
 8 
 9     if ((pipe(fda) < 0) || (pipe(fdb) < 0)) {
10         perror("pipe error");
11         exit(1);
12     }
13     
14     pid_t pid;
15     pid = fork();
16     if (pid < 0) {  //子进程
17         perror("fork error");
18         exit(1);
19     } else if (pid == 0) {
20         //1、子进程负责从管道a中读取父进程写入的累加参数x和y
21         //2、通过exec函数调用/bin/add程序进行累加
22         //3、将累加结果写入到管道b
23         close(fda[1]);
24         close(fdb[0]);
25         //将标准输入重定向到管道a的读端
26         //add程序中将从管道a中读取累加参数x和y
27         if (dup2(fda[0], STDIN_FILENO) != STDIN_FILENO) {
28             perror("dup2 error");
29         }
30         //将标准输出重定向到管道b的写端
31         //add程序累加后的结果会写入到管道b
32         if (dup2(fdb[1], STDOUT_FILENO) != STDOUT_FILENO) {
33             perror("dup2 error");
34         }
35         close(fda[0]);
36         close(fdb[1]);
37         if (execlp("bin/add", "bin/add", NULL) < 0) {
38             perror("execlp error");
39             exit(1);
40         }
41     } else {  //父进程
42         //1、从标准输入上读取累加参数x和y
43         //2、将x和y写入管道a
44         //3、从管道b中读取累加的结果并输出
45         close(fda[0]);
46         close(fdb[1]);
47         //1
48         int x, y;
49         printf("please input x and y: ");
50         scanf("%d %d", &x, &y);
51         //2
52         if (write(fda[1], &x, sizeof(int)) != sizeof(int)) {
53             perror("write error");
54         }
55         if (write(fda[1], &y, sizeof(int)) != sizeof(int)) {
56             perror("write error");
57         }
58         //3
59         int result = 0;
60         if (read(fdb[0], &result, sizeof(int)) != sizeof(int)) {  //阻塞式读写
61             perror("read error");
62         }
63         printf("add result is %d
", result);
64         close(fda[1]);
65         close(fdb[0]);
66         wait(0);
67     }
68 
69 
70     return 0;
71 }
View Code

测试结果:

[root@192 ipc]# gcc -o bin/add add.c
[root@192 ipc]# gcc -o bin/co_pro c_process.c
[root@192 ipc]# ./bin/co_pro
please input x and y: 12 23
add result is 35

(4)案例4

实现一个不完整管道:当读一个写端已被关闭的管道时,在所有数据被读取后,read返回0,以表示到达了文件尾部。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 
 5 /*
 6  * 不完整管道:读取一个写端已经关闭的管道
 7 */
 8 
 9 int main(void)
10 {
11     int fd[2];
12 
13     if (pipe(fd) < 0) {
14         perror("pipe error");
15         exit(1);
16     }
17     pid_t pid;
18     if ((pid = fork()) < 0) {
19         perror("fork error");
20         exit(1);
21     } else if (pid > 0) {  //父进程
22         //父进程从不完整管道(写端关闭)中读取数据
23         sleep(5);  //等子进程将管道写端关闭
24         close(fd[1]);
25         while (1) {
26             char c;
27             if (read(fd[0], &c, 1) == 0) {
28                 printf("
write-end of pipe closed
");
29                 break;
30             } else {
31                 printf("%c", c);
32             }
33         }
34     } else {  //子进程
35         // 子进程负责将数据写入管道
36         close(fd[0]);
37         char *s = "1234";
38         write(fd[1], s, sizeof(s));
39         close(fd[1]);
40     }
41 
42     return 0;
43 }
View Code

(5)案例5

实现一个不完整管道:当写一个读端被关闭的信号,则产生信号SIGPIPE,如果忽略该信号或捕捉该信号并从处理程序返回,则write返回-1,同时errno设置为EPIPE。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <string.h>
 5 #include <errno.h>
 6 #include <signal.h>
 7 
 8 /*
 9  * 不完整管道:写入一个读端已经被关闭的管道
10 */
11 
12 void sig_handler(int signo)
13 {
14     if (signo == SIGPIPE) {
15         printf("SIGPIPE occured
");
16     }
17 }
18 
19 int main(void)
20 {
21     int fd[2];
22 
23     if (pipe(fd) < 0) {
24         perror("pipe error");
25         exit(0);
26     }
27 
28     pid_t pid;
29     if ((pid = fork()) < 0) {
30         perror("fork error");
31     } else if (pid > 0) {  //父进程
32         //父进程负责将数据写入到不完整管道(读端关闭)中
33         sleep(5);
34         close(fd[0]);
35         if (signal(SIGPIPE, sig_handler) == SIG_ERR) {
36             perror("signal sigpipe error");
37             exit(1);            
38         }
39         char *s = "1234";
40         if (write(fd[1], s, sizeof(s)) != sizeof(s)) {
41             fprintf(stderr, "%s, %s
", strerror(errno), (errno == EPIPE) ? "EPIPE" : ", unkown");            
42         }
43         close(fd[1]);
44         wait(0);
45     } else {  //子进程
46         //关闭管道的读端
47         close(fd[0]);
48         close(fd[1]);
49     }
50 
51     return 0;
52 }
View Code

6  标准库中的管道操作

函数实现的操作:创建一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell运行命令,然后等待命令终止。

(1)函数原型

1 #include <stdio.h>
2 FILE *popen(const char *command, const char *type);
3 返回值:成功返回文件指针,出错返回NULL。
4 参数command:命令的路径。
5 参数type:读写特性,”r”或”w”
6 int pclose(FILE *stream);

函数popen先执行fork,然后调用exec执行command,并且返回一个标准I/O文件指针。

如果type是“r”,则文件指针连接到command的标准输出。

如果type是"w",则文件指针连接到command的标准输入。

(2)popen内部原理

 (3)实例

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 int main(void)
 5 {
 6     FILE *fp;
 7 
 8     //命令执行的结果放置到fp指向的结构体缓存中
 9     fp = popen("cat /etc/passwd", "r");
10     char buf[512] = {0};
11     while (fgets(buf, sizeof(buf), fp) != NULL) {
12         printf("%s", buf);
13     }
14     pclose(fp);
15 
16     printf("----------------------------------
");
17     //为wr命令提供统计的数据
18     fp = popen("wc -l", "w");
19     fprintf(fp, "1
2
3
");
20     pclose(fp);
21 
22     return 0;
23 }
View Code
原文地址:https://www.cnblogs.com/mrlayfolk/p/13027545.html