linux 进程间通信 匿名管道
进程间通信概念
进程是一个独立的资源分配单元,不同进程 (这里所说的进程通常指的是用户进程) 之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源
但进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信 (IPC: Inter Processes Communication)。
进程间通信的目的:
数据传输:一个进程需要将它的数据发送给另一个进程
通知事件:一个进程需要向另一个或者一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
进程控制:有些进程希望完全控制另一个进程的执行 (如 Debug 进程) ,此时控制进程希望能够拦截另一个进程的所有 陷入 和 异常,并能及时直到它的状态改变。
同一主机进程间通信案例:播放器 边下载 边播放
不同主机进程间通信:QQ聊天
匿名管道概述
管道也叫无名(匿名)管道,它是 UNIX 系统 IPC(进程间通信)的最古老形式,所有的 UNIX 系统都支持这种通信机制
统计一个目录中文件的数目命令: ls | wc -l ,|(管道符) 为了执行该命令,shell创建了两个进程分别来执行 ls 和 wc。
将 ls 运行的内容 通过 | 传递给 wc -l 显示到终端
管道的特点
管道其实是一个在内核中维护的缓冲器,这个缓冲器的存储能力是有限的,不同的操作系统大小不一定相同。
管道拥有文件的特质:读操作、写操作,匿名管道(常用于关系进程:父子进程等)没有文件实体,有名管道有文件实体(没有关系的进程),但不存储数据。可以按照操作文件的方式对管道进行操作。
一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少。
通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺序是完全一样的。
在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是 半双工的。
从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据,在管道中无法使用 lseek() 来随机的访问数据
匿名管道只能在具有公共祖先的进程 (父进程与子进程,或者两个兄弟进程,具有亲缘关系) 之间使用
单工:单向传输,遥控器
双工:同时双向传输,打电话
半双工:同一时间只能单向传输
为什么可以使用管道进行进程间通信
frok()之后,父子进程之间共享文件描述符表,父子进程可以对磁盘内同一文件进行操作,管道通信将 文件拟为 管道 进行通信
管道数据结构 :将线性队列 变为 逻辑上环形队列
管道的使用:
创建匿名管道 #include <unistd.h> int pipe(int pipefd[2]); 查看管道缓冲大小命令 ulimit -a 查看管道缓冲大小函数 #include <unistd.h> long fpathconf(int fd, int name);
子进程给父进程发送数据
1 /* 2 man 2 pipe 3 #include <unistd.h> 4 int pipe(int pipefd[2]); 5 功能:创建一个匿名管道,用来进程间通信 6 参数:int pipefd[2] 这个数组是一个传出参数 7 pipefd[0]: 对应的管道读取端 8 pipefd[1]: 对应的管道写入端 9 返回值: 10 成功: 返回 0 11 失败: 返回 -1 12 管道默认是阻塞的,如果管道中没有数据,那么read阻塞,如果管道满了 write阻塞 13 14 注意:匿名管道只能用于具有关系的进程之间的通信(父子进程、兄弟进程、孙子进程) 15 */ 16 #include <unistd.h> 17 #include <stdio.h> 18 #include <sys/types.h> 19 #include <stdlib.h> 20 #include <string.h> 21 //子进程发送数据给父进程,父进程读取到数据输出 22 int main() 23 { 24 //***在fork之前创建管道*** 得到两个文件描述符 25 int pipefd[2]; 26 int ret = pipe(pipefd); 27 if(ret == -1) 28 { 29 perror("pipe"); 30 exit(0); 31 } 32 //创建子进程 33 pid_t pid = fork(); 34 if(pid > 0) 35 { 36 //父进程 37 //从管道的读取端读取数据 38 char buf[1024] = {0}; 39 int len = read(pipefd[0],buf,sizeof(buf));//read默认阻塞 管道有数据才读取 发送方停滞5s则5s后接收数据 40 printf("parent recv: %s, pid: %d\n", buf,getpid());//输出 buf 当前进程号 41 } 42 else if(pid == 0) 43 { 44 sleep(5);//5s后发送数据 父进程读取 45 //子进程发送数据 46 char* str = "hello, i am child"; 47 write(pipefd[1],str,strlen(str)); 48 } 49 50 return 0; 51 }
循环发送-循环读取
1 pid_t pid = fork(); 2 if(pid > 0) 3 { 4 //父进程 5 printf("i am parent process,pid:%d\n",getpid()); 6 char buf[1024] = {0}; 7 while(1) 8 { 9 int len = read(pipefd[0],buf,sizeof(buf)); 10 printf("parent recv: %s, pid: %d\n", buf,getpid()); 11 } 12 } 13 else if(pid == 0) 14 { 15 //子进程 16 printf("i am child process,pid:%d\n",getpid()); 17 while(1) 18 { 19 //向管道中写入数据 20 char* str = "hello, i am child"; 21 write(pipefd[1],str,strlen(str)); 22 sleep(1); 23 }
子发送父读取,父发送子读取:
子进程先发送数据,父进程读取,父进程的发送代码需要写到读取代码之后,而子进程的读取代码也应该写道发送代码之后
但是注释掉两行 sleep() 函数之后,则会出现异常错误,如下图,实际开发中,不会使用sleep(),因此实际开发也不会在同一管道交替读写数据,容易出错。
1 if(pid > 0) 2 { 3 //父进程 4 printf("i am parent process,pid:%d\n",getpid()); 5 char buf[1024] = {0}; 6 while(1) 7 { 8 int len = read(pipefd[0],buf,sizeof(buf)); 9 printf("parent recv: %s, pid: %d\n", buf,getpid()); 10 11 //向管道中写入数据 12 char* str = "hello, i am parent"; 13 write(pipefd[1],str,strlen(str)); 14 sleep(1); 15 } 16 } 17 else if(pid == 0) 18 { 19 //子进程 20 printf("i am child process,pid:%d\n",getpid()); 21 char buf[1024] = {0}; 22 while(1) 23 { 24 //向管道中写入数据 25 char* str = "hello, i am child"; 26 write(pipefd[1],str,strlen(str)); 27 sleep(1); 28 29 int len = read(pipefd[0],buf,sizeof(buf)); 30 printf("child recv: %s, pid: %d\n", buf,getpid());
31 bzero(buf, 1024);//每次读取后清除数据 32 } 33 }
关闭写数据进程的读端,关闭读进程的写端
1 #include <unistd.h> 2 #include <stdio.h> 3 #include <sys/types.h> 4 #include <stdlib.h> 5 #include <string.h> 6 //子进程发送数据给父进程,父进程读取到数据输出 7 int main() 8 { 9 //***在fork之前创建管道*** 得到两个文件描述符 10 int pipefd[2]; 11 int ret = pipe(pipefd); 12 if(ret == -1) 13 { 14 perror("pipe"); 15 exit(0); 16 } 17 //创建子进程 18 pid_t pid = fork(); 19 if(pid > 0) 20 { 21 //父进程 22 printf("i am parent process,pid:%d\n",getpid()); 23 char buf[1024] = {0}; 24 while(1) 25 { 26 int len = read(pipefd[0],buf,sizeof(buf)); 27 printf("parent recv: %s, pid: %d\n", buf,getpid()); 28 //关闭写端 29 close(pipefd[1]);//写端是1 读端是0 30 //向管道中写入数据 31 //char* str = "hello, i am parent"; 32 //write(pipefd[1],str,strlen(str)); 33 //sleep(1); 34 } 35 } 36 else if(pid == 0) 37 { 38 //子进程 39 printf("i am child process,pid:%d\n",getpid()); 40 close(pipefd[0]);//关闭读端 41 //char buf[1024] = {0}; 42 while(1) 43 { 44 //向管道中写入数据 45 char* str = "hello, i am child\n"; 46 write(pipefd[1],str,strlen(str)); 47 48 //sleep(1); 49 50 //int len = read(pipefd[0],buf,sizeof(buf)); 51 //printf("child recv: %s, pid: %d\n", buf,getpid()); 52 //bzero(buf,1024);//每次读取后清除数据 53 } 54 } 55 return 0; 56 }
pipe size: 8 为 8块 一块是 512字节 总共为4096字节 即 4KB ulimit -p 数值(块) 可以修改管道的大小
父子进程管道通信的三种情况
案例:
1 /* 2 实现 ps aux | grep xxx 3 父子进程间通信 4 子进程: ps aux 子进程结束后 将数据发送给父进程 5 父进程: 获取到数据 过滤 6 7 pipe() 8 execlp() 9 子进程将标准输出stdout_fileno 重定向到管道的写端。 dup2 10 */ 11 #include <unistd.h> 12 #include <stdio.h> 13 #include <string.h> 14 #include <sys/types.h> 15 #include <stdlib.h> 16 #include <wait.h> 17 18 int main() 19 { 20 //创建一个管道 21 int fd[2]; 22 int ret = pipe(fd); 23 if(ret == -1) 24 { 25 perror("pipe"); 26 exit(0); 27 } 28 //创建子进程 29 pid_t pid = fork(); 30 if(pid > 0) 31 { 32 //父进程 33 close(fd[1]);//关闭写端 34 //从管道中读取数据 35 char buf[1024] = {0}; 36 int len = -1; 37 //循环读取 否则只能读取1024 - 1 = 1023个字节内容 38 while((len = read(fd[0],buf,sizeof(buf) - 1 )) > 0)// -1 留一个字符串结束符 39 { 40 //过滤数据输出 41 printf("%s",buf);//不用 \n 42 memset(buf,0,1024);//清空数据 43 } 44 wait(NULL); 45 } 46 else if(pid == 0) 47 { 48 //子进程 49 close(fd[0]);//关闭读端 50 //文件描述符的重定向 stdout_fileno -> fd[1] 51 dup2(fd[1], STDOUT_FILENO);//重定向到fd[1]写端 52 //执行 ps aux 53 execlp("ps","ps","aux",NULL); 54 perror("execlp"); 55 exit(0); 56 } 57 else 58 { 59 //-1 60 perror("fork"); 61 exit(0); 62 } 63 return 0; 64 }
代码查询pipe大小
1 /* 2 man 2 fpathconf 3 */ 4 #include <unistd.h> 5 #include <stdio.h> 6 #include <sys/types.h> 7 #include <stdlib.h> 8 #include <string.h> 9 int main() 10 { 11 int pipefd[2]; 12 int ret = pipe(pipefd); 13 //获取管道的大小 返回long值 14 long size = fpathconf(pipefd[0],_PC_PIPE_BUF); 15 printf("pipe size: %ld\n",size); 16 return 0; 17 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)