Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷贝到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制成为进程间通信(IPC)
进程间通信的方式
最常用:
- 管道(最简单)
- 信号(开销最小)
- 共享映射区(无血缘关系)
- 本地套接字 (最稳定)
管道
管道的原理:
- 管道的实质是内核缓冲区,内部使用环形队列实现
- 默认的缓冲区大小为4K,可以使用ulimit -a 命令获取大小
- 实际操作过程中缓冲区会根据数据压力做适当调整
- 数据只能在一个方向上流动,若要实现双向流动,必须使用两个管道
- 只能在有血缘关系的进程间使用管道
- 管道的读写两端都是阻塞的
- 数据被读走之后,在管道中就消失了。
创建管道 -pipe函数
- 函数作用:创建一个管道
- 函数原型:
- int pipe(int fd[2]);
- int pipe(int *fd);
- 函数参数:
- 若调用成功,fd[0]存放管道的读端,fd[1]存放管道的写端
- 返回值:
- 成功返回0
- 失败返回-1,并设置errno值
- 操作流程:
- 父进程创建管道pipe
- 父进程调用fork函数创建子进程
- 父进程关闭一端
- 子进程关闭一端
- 父进程和子进程分别执行read或者write操作
px -aux | grep bash 思路解析
操作流程:
- 创建管道pipe
- 创建子进程fork
- 父进程关闭读端fd[0]
- 子进程关闭写端fd[1]
- 在父进程中将标准输出重定向到管道写端 dup2(fd[1],STDOUT_FILENO);
- 在子进程中将标准输入重定向到管道读端 dup2(fd[0],STDIN_FILENO);
- 在父进程中调用execl函数执行ps aux命令
- 在子进程中调用execl函数执行grep bash命令
代码示例:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<sys/types.h>
5 #include<unistd.h>
6 #include<sys/wait.h>
7
8
9 int main()
10 {
11 //创建管道
12 //int pipe(int pipefd[2])
13 int fd[2];
14 int ret = pipe(fd);
15 if(ret<0)
16 {
17 perror("pipe error");
18 return -1;
19 }
20
21 //创建子进程
22 pid_t pid = fork();
23 if(pid<0)
24 {
25 perror("fork error");
26 }
27 else if(pid>0)
28 {
29 //关闭读端
30 close(fd[0]);
31 //将标准输出重定向到管道的写端
32 dup2(fd[1],STDOUT_FILENO);
33
34 execlp("ps","ps","aux",NULL);
35 wait(NULL);
36
37 }else
38 {
39 //关闭写端
40 close(fd[1]);
41 //将标准输入重定向到管道的读端
42 dup2(fd[0],STDIN_FILENO);
43 execlp("grep","grep","bash",NULL);
44 }
45 return 0;
46 }
~
管道的读写行为
- 读操作
- 有数据:read正常读,返回读出的字节数
- 无数据
- 写端全部关闭:read解除阻塞,返回0,相当于读文件到了尾部
- 没有全部关闭:read会阻塞
- 写操作
- 读端全部关闭:管道破裂,进程终止,内核给当前进程发SIGPIPE信号
- 读端没有全部关闭:
- 缓冲区写满了:write阻塞
- 缓冲区没有满:继续write
如何设置管道为非阻塞
默认情况下,管道的读写两端都是阻塞的,若要设置成读或者写为非阻塞要进行下面步骤
- int flags = fcntl(fd[0],F_GETFL,0);
- flag | = O_NONBLOCK;
- fcntl(fd[0],F_SETFL,flags)
- 若是读端为非阻塞:
- 写端没有关闭,管道中没有数据可读,read返回-1
- 写端没有关闭,read中有数据可读,则read返回实际读到的字节数
- 写端已经关闭,管道中有数据可读,同上
- 写端已经给关闭,管道中没有数据可读,则read返回0
非阻塞代码与运行结果
-
1 //非阻塞代码与运行结果 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<sys/types.h> 5 #include<unistd.h> 6 #include<sys/wait.h> 7 #include<fcntl.h> 8 9 int main() 10 { 11 //创建管道 12 //int pipe(int pipefd[2]) 13 int fd[2]; 14 int ret = pipe(fd); 15 if(ret<0) 16 { 17 perror("pipe error"); 18 return -1; 19 } 20 21 //设置管道的读端为非阻塞 22 int flag = fcntl(fd[0],F_GETFL); 23 flag |= O_NONBLOCK; 24 fcntl(fd[0],F_SETFL,flag); 25 26 //关闭写端 27 28 char buf[64]; 29 memset(buf,0x00,sizeof(buf)); 30 int n=read(fd[0],buf,sizeof(buf)); 31 printf("read over,n==[%d],buf==[%s]\n",n,buf); 32 33 34 return 0; 35 } ~ root@lxy-virtual-machine:~/test/course/day6# make pipe2 cc pipe2.c -o pipe2 root@lxy-virtual-machine:~/test/course/day6# ./pipe2 read over,n==[-1],buf==[]
阻塞代码与运行结果
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<sys/types.h>
5 #include<unistd.h>
6 #include<sys/wait.h>
7 #include<fcntl.h>
8
9 int main()
10 {
11 //创建管道
12 //int pipe(int pipefd[2])
13 int fd[2];
14 int ret = pipe(fd);
15 if(ret<0)
16 {
17 perror("pipe error");
18 return -1;
19 }
20
21 //设置管道的读端为非阻塞
22
23 //关闭写端
24
25 char buf[64];
26 memset(buf,0x00,sizeof(buf));
27 int n=read(fd[0],buf,sizeof(buf));
28 printf("read over,n==[%d],buf==[%s]\n",n,buf);
29
30
31 return 0;
32 }
~
root@lxy-virtual-machine:~/test/course/day6# make pipe3
cc pipe3.c -o pipe3
root@lxy-virtual-machine:~/test/course/day6# ls
pipe pipe2 pipe2.c pipe3 pipe3.c pipe.c ps ps.c
root@lxy-virtual-machine:~/test/course/day6# ./pipe3
s
^Z
[1]+ 已停止 ./pipe3
如何查看管道缓冲区大小
- 命令:ulimit -a
- 函数
- long fpathconf(int fd,int name);
- printf("pipe size==[%ld]\n",fpathconf(fd[0],_PC_PIPE_BUF))
- printf("pipe size==[%ld]\n",fpathconf(fd[1],_PC_PIPE_BUF))