1.进程间通信
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
- 1
- 2
不同进程间的通信本质:进程之间可以看到一份公共资源;而提供这份资源的形式或者提供者不同,造成了通信方式不同,而 pipe就是提供这份公共资源的形式的一种。
2.匿名管道
2.1管道的创建
管道是由调用pipe函数来创建
- 1
- 2
#include <unistd.h>
int pipe (int fd[2]);
//返回:成功返回0,出错返回-1
- 1
- 2
- 3
fd参数返回两个文件描述符,fd[0]指向管道的读端,fd[1]指向管道的写端。fd[1]的输出是fd[0]的输入。
- 1
- 2
2.2管道如何实现进程间的通信
(1)父进程创建管道,得到两个⽂件描述符指向管道的两端
(2)父进程fork出子进程,⼦进程也有两个⽂件描述符指向同⼀管道。
(3)父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信。
2.3如和用代码实现管道通信
[cpp] view plain copy
1. #include <stdio.h>
2. #include <unistd.h>
3. #include <string.h>
4. #include <errno.h>
5. int main()
6. {
7. int fd[2];
8. int ret = pipe(fd);
9. if (ret == -1)
10. {
11. perror(”pipe error\n”);
12. return 1;
13. }
14. pid_t id = fork();
15. if (id == 0)
16. {//child
17. int i = 0;
18. close(fd[0]);
19. char *child = “I am child!”;
20. while (i<5)
21. {
22. write(fd[1], child, strlen(child) + 1);
23. sleep(2);
24. i++;
25. }
26. }
27. else if (id>0)
28. {//father
29. close(fd[1]);
30. char msg[100];
31. int j = 0;
32. while (j<5)
33. {
34. memset(msg,’\0’,sizeof(msg));
35. ssize_t s = read(fd[0], msg, sizeof(msg));
36. if (s>0)
37. {
38. msg[s - 1] = ’\0’;
39. }
40. printf(”%s\n”, msg);
41. j++;
42. }
43. }
44. else
45. {//error
46. perror(”fork error\n”);
47. return 2;
48. }
49. return 0;
50. }
运行结果:
每隔2秒打印一次I am child! 并且打印了五次。
- 1
- 2
2.4管道读取数据的四种的情况
(1)读端不读,写端一直写
(2)写端不写,但是读端一直读
(3)读端一直读,且fd[0]保持打开,而写端写了一部分数据不写了,并且关闭fd[1]。
如果一个管道读端一直在读数据,而管道写端的引⽤计数⼤于0决定管道是否会堵塞,引用计数大于0,只读不写会导致管道堵塞。
(4)读端读了一部分数据,不读了且关闭fd[0],写端一直在写且f[1]还保持打开状态。
[cpp] view plain copy
1. #include <stdio.h>
2. #include <unistd.h>
3. #include <string.h>
4. #include <errno.h>
5. int main()
6. {
7. int fd[2];
8. int ret = pipe(fd);
9. if (ret == -1)
10. {
11. perror(”pipe error\n”);
12. return 1;
13. }
14. pid_t id = fork();
15. if (id == 0)
16. {//child
17. int i = 0;
18. close(fd[0]);
19. char *child = “I am child!”;
20. while (i<10)
21. {
22. write(fd[1], child, strlen(child) + 1);
23. sleep(2);
24. i++;
25. }
26. }
27. else if (id>0)
28. {//father
29. close(fd[1]);
30. char msg[100];
31. int status = 0;
32. int j = 0;
33. while (j<5)
34. {
35. memset(msg, ’\0’, sizeof(msg));
36. ssize_t s = read(fd[0], msg, sizeof(msg));
37. if (s>0)
38. {
39. msg[s - 1] = ’\0’;
40. }
41. printf(”%s %d\n”, msg, j);
42. j++;
43. }
44. //写方还在继续,而读方已经关闭它的读端
45. close(fd[0]);
46. pid_t ret = waitpid(id, &status, 0);
47. printf(”exitsingle(%d),exit(%d)\n”, status & 0xff, (status >> 8) & 0xff);
48. //低八位存放该子进程退出时是否收到信号
49. //此低八位子进程正常退出时,退出码是多少
50. }
51. else
52. {//error
53. perror(”fork error\n”);
54. return 2;
55. }
56. return 0;
57. }
运行结果:
使用kill -l 查看13号信号,可以知道13号信号代表SIGPIPE。
总结:
如果一个管道的写端一直在写,而读端的引⽤计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只写不读再次调用write会导致管道堵塞;
如果一个管道的读端一直在读,而写端的引⽤计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只读不写再次调用read会导致管道堵塞;
而当他们的引用计数等于0时,只写不读会导致写端的进程收到一个SIGPIPE信号,导致进程终止,只写不读会导致read返回0,就像读到⽂件末尾⼀样。
2.5管道特点
1.管道只允许具有血缘关系的进程间通信,如父子进程间的通信。
2.管道只允许单向通信。
3.管道内部保证同步机制,从而保证访问数据的一致性。
4.面向字节流
5.管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2.6管道容量大小
测试管道容量大小只需要将写端一直写,读端不读且不关闭fd[0],即可。
测试代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if (ret == -1)
{
perror("pipe error\n");
return 1;
}
pid_t id = fork();
if (id == 0)
{//child
int i = 0;
close(fd[0]);
char *child = "I am child!";
while (i++)
{
write(fd[1], child, strlen(child) + 1);
printf("pipe capacity: %d\n", i*(strlen(child) + 1));
}
close(fd[1]);
}
else if (id>0)
{//father
close(fd[1]);
waitpid(id, NULL, 0);
}
else
{//error
perror("fork error\n");
return 2;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
可以看到写到65520之后管道堵塞了,而65520即为64K大小即为管道的容量。