Linux:进程间通信(1)
目录
编辑
1.进程间通信
1.1进程间通信目的
1.2.进程间通信发展
1.3.进程间通信分类
2.管道
3.匿名管道
3.1实例代码
3.2⽤fork来共享管道原理
3.3站在文件描述符角度-深度理解管道
3.4站在内核角度-管道本质
3.5管道样例
3.6管道读写规则
3.7管道特点
4.命名管道
4.1创建⼀个命名管道
4.2匿名管道与命名管道的区别
4.3命名管道的打开规则
4.4综合实例
1.进程间通信
1.1进程间通信目的
• 数据传输:实现进程间的数据交换与共享
• 资源共享:允许多个进程共同访问相同资源
• 事件通知:当特定事件发生时,向目标进程发送提醒信息(如进程终止时通知父进程)
• 进程控制:支持主控进程对目标进程的全方位监管(如调试进程),包括拦截异常、监控状态变更等
1.2.进程间通信发展
- 管道(Pipes)
- SystemV 进程间通信
- POSIX 进程间通信
接下来我们进程通信将围绕这三个来讲解!!!!
1.3.进程间通信分类
通信机制:
-
管道通信:
- 匿名管道(pipe)
- 命名管道
-
SystemV IPC机制:
- 消息队列
- 共享内存
- 信号量
-
POSIX IPC机制:
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
2.管道
什么是管道?
- 管道是Unix系统中最古老的进程间通信机制。
- 它指的是连接两个进程的数据流。

3.匿名管道
int pipe(int fd[2]);
功能 : 创建⼀⽆名管道
其头文件:
#include <unistd.h>
参数说明:
- fd:文件描述符数组
- fd[0] 表示读端
- fd[1] 表示写端
返回值:
- 成功:返回0
- 失败:返回错误代码

3.1实例代码
从键盘获取输入数据后,将数据写入管道,再从管道读取数据并输出到显示器。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>int main()
{int fd[2] = {0};if(pipe(fd)<0) //创建管道{perror("pipe err");exit(1);}char buf[100];int len;while(fgets(buf,100,stdin)){len = strlen(buf);if( write(fd[1], buf, len) != len ) //写数据到管道{perror("write err");break;}memset(buf, 0x00, sizeof(buf));if ( (len=read(fds[0], buf, 100)) == -1 ) { //从管道中读取数据perror("read from pipe");break;}if ( write(1, buf, len) != len ) { //将读取的数据写到屏幕上perror("write to stdout");break;}}return 0;
}
3.2⽤fork来共享管道原理

3.3站在文件描述符角度-深度理解管道

3.4站在内核角度-管道本质

在Linux系统中,管道的使用方式与文件操作完全一致,完美体现了"Linux一切皆文件"的设计理念。
3.5管道样例
1 #include <unistd.h>2 #include <stdlib.h>3 #include <stdio.h>4 #include <errno.h>5 #include <string.h>6 #define ERR_EXIT(m) \7 do \8 { \9 perror(m); \10 exit(EXIT_FAILURE); \11 } while(0)12 int main(int argc, char *argv[])13 { 14 int pipefd[2];15 if (pipe(pipefd) == -1)16 ERR_EXIT("pipe error");17 pid_t pid;18 pid = fork();19 if (pid == -1)20 ERR_EXIT("fork error");21 if (pid == 0) {22 close(pipefd[0]);23 write(pipefd[1], "hello", 5);24 close(pipefd[1]);25 exit(EXIT_SUCCESS);26 }27 close(pipefd[1]);28 char buf[10] = {0};29 read(pipefd[0], buf, 10);30 printf("buf=%s\n", buf);31 return 0;32 }
执行后的结果:

3.6管道读写规则
• 无数据可读时:
◦ O_NONBLOCK关闭:read调用会阻塞进程,直到有数据到达
◦ O_NONBLOCK开启:read调用立即返回-1,并设置errno为EAGAIN
• 管道已满时:
◦ O_NONBLOCK关闭:write调用会阻塞,直到其他进程读取数据
◦ O_NONBLOCK开启:write调用立即返回-1,并设置errno为EAGAIN
• 管道状态影响:
◦ 所有写端关闭时:read调用返回0
◦ 所有读端关闭时:write调用会触发SIGPIPE信号,可能导致进程终止
• 写入原子性保证:
◦ 数据量≤PIPE_BUF:Linux保证写入操作的原子性
◦ 数据量>PIPE_BUF:Linux不保证写入操作的原子性
3.7管道特点
• 仅适用于具有亲缘关系的进程间通信,通常由父进程创建管道后调用fork,实现父子进程间的数据传输
• 采用流式数据传输机制
(采用流式数据传输机制是一种高效的数据传输方式,它允许数据在发送方和接收方之间连续、实时地流动,而无需等待整个数据集完全传输完毕。这种机制特别适用于需要低延迟和大规模数据处理的场景,如视频流、实时监控、在线游戏和金融交易等。)
• 管道的生命周期与进程绑定,进程终止时自动释放
• 内核默认对管道操作实施同步与互斥管理
• 管道采用半双工通信模式,双向通信需建立两条独立管道

4.命名管道
• 管道应用存在一个主要限制:只能在具有共同祖先(即存在亲缘关系)的进程间进行通信。
• 要在不相关进程间交换数据,可以使用FIFO文件(也称为命名管道)来实现。
• 命名管道属于一种特殊类型的文件。
4.1创建⼀个命名管道
命名管道可以通过命令行创建,具体操作是执行以下命令:
mkfifo filename
程序内部可以通过特定函数创建命名管道,常用的相关函数包括:
int mkfifo(const char *filename,mode_t mode);
创建命名管道:

创建好的管道(p1):

注意:
-
实际创建的文件权限会受到umask(文件默认掩码)的影响,最终权限计算公式为:mode & (~umask)。umask默认值通常为0002。例如,当mode设置为0666时,最终创建的文件权限将是0664。(所以我们开头就将umask值设置为0,这样就不会影响到我们权限的赋予)
-
管道文件类型标识符以字母"p"开头。
![]()
4.2匿名管道与命名管道的区别
• 匿名管道通过pipe函数创建并开启。
• 命名管道由mkfifo函数创建,使用open函数开启。
• 命名管道(FIFO)和匿名管道(pipe)的主要区别仅在于创建和开启方式不同,完成这些操作后,两者的行为特性完全一致。
4.3命名管道的打开规则
• 如果当前打开操作是为读⽽打开FIFO时:
◦ O_NONBLOCK disable:阻塞直到有相应进程为写⽽打开该FIFO
◦ O_NONBLOCK enable:⽴刻返回成功
• 如果当前打开操作是为写⽽打开FIFO时:
◦ O_NONBLOCK disable:阻塞直到有相应进程为读⽽打开该FIFO
◦ O_NONBLOCK enable:⽴刻返回失败,错误码为ENXIO
4.4综合实例
1.用命名管道实现文件拷贝:
读取文件内容并写入命名管道:
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <errno.h>#include <sys/types.h>#include <sys/stat.h>#include <string.h>#include <fcntl.h>#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while(0)
int main(int argc, char *argv[]){mkfifo("tp", 0644);int infd;infd = open("abc", O_RDONLY);if (infd == -1) ERR_EXIT("open");int outfd;outfd = open("tp", O_WRONLY);if (outfd == -1) ERR_EXIT("open");char buf[1024];int n;while ((n=read(infd, buf, 1024))>0){write(outfd, buf, n);}close(infd);close(outfd);return 0;}
读取管道数据并写入目标文件:
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <errno.h>#include <sys/types.h>#include <sys/stat.h>#include <string.h>#include <fcntl.h>#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while(0)int main(int argc, char *argv[]){int outfd;outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (outfd == -1) ERR_EXIT("open");int infd;infd = open("tp", O_RDONLY);if (outfd == -1)ERR_EXIT("open");char buf[1024];int n;while ((n=read(infd, buf, 1024))>0){write(outfd, buf, n);}close(infd);close(outfd);unlink("tp");return 0;}
2.用命名管道实现server&client通信:
在搭建客户端之前,需要先建立服务端并创建通信管道。具体实现方式是:服务端以读取模式从管道接收数据,而客户端则以写入模式向管道发送数据。
comm.h:(因为客户端和服务端的头文件相同所以我们只用打一遍就行,到时候引用comm.h就行)

server:

client:

运行起来后的结果如下:

从上述实例可以看出,命名管道能够实现两个独立进程之间的通信连接。
本节先到此结束,如过对进程通信感兴趣的话,还有后续进程通信(2)可以去看看哦!!!!

