当前位置: 首页 > news >正文

IPC进程间通信 interprocess communicate

目录

  • 1. IPC概述
  • 2. IPC三大通信方式分类
    • 2.1 古老的通信方式
    • 2.2 IPC对象通信
    • 2.3 socket通信
  • 3. 管道通信详解
    • 3.1 管道基本特性
    • 3.2 无名管道
      • 3.2.1 无名管道特性
      • 3.2.2 无名管道使用流程
      • 3.2.3 无名管道练习验证
    • 3.3 有名管道
      • 3.3.1 有名管道框架
      • 3.3.2 有名管道操作函数
      • 3.3.3 有名管道注意事项
  • 4. 信号通信
    • 4.1 信号发送端
    • 4.2 信号接收处理
      • 4.2.1 信号处理方式
      • 4.2.2 信号注册函数
  • 5. 练习与作业
    • 5.1 基础练习
    • 5.2 大作业

1. IPC概述

IPC 进程间通信 interprocess communicate
进程间通信的原理:a和b是独立的,所以要找到一个公共的空间,把信息存储在这个空间上,在内核空间上,开一片内存区域,a和b都可以找到这个公共的区域

2. IPC三大通信方式分类

三大类:古老的通信方式,ipc对象通信、socket通信

2.1 古老的通信方式

无名管道、有名管道、信号(siginal)

2.2 IPC对象通信

system v、BSD、suse fedora、kernel.org、unix
消息队列(用的相对少,这里不讨论)
共享内存
信号量集

2.3 socket通信

网络通信(不同主机之间的通信)

线程信号,posix sem_init

特列:

  1. 古老的通信方式中信号是唯一的异步通信
  2. 所有的通信方式中共享内存是唯一的最高效

3. 管道通信详解

根据使用场景来区分:
管道分类:

  • 无名管道(pipe):只能给有亲缘关系进程通信
  • 有名管道(fifo):可以给任意单机进程通信

3.1 管道基本特性

  1. 管道是半双工的工作模式
  2. 所有管道都是特殊的文件不支持定位操作。lseek->> fd fseek ->>FILE*
  3. 管道是特殊文件,读写使用文件IO。fgets,fread,fgetc,open,read,write,close

管道使用注意事项:

  1. 读端存在,一直向管道中去写,超过64k,写会阻塞。
  2. 写端存在,读管道,如果管道为空,读会阻塞。(有数据的话就取出来)
  3. 管道破裂:读端关闭,写管道。是写端破裂了
  4. read 0:写端关闭,如果管道没有内容,read 0。
    如果
    使用框架:
    创建管道 → 读写管道 → 关闭管道
    代码举例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int	main(int argc, char **argv)
{int fd[2]={0};int ret = pipe(fd);pid_t pid = fork();if(pid>0){//父进程写管道,不读.所以关闭管道的读段.close(fd[0]);char buf[]="hello";sleep(3);write(fd[1],buf,strlen(buf));exit(0);}else if(0 == pid){//子进程读管道,不写.所以关闭管道的写段.close(fd[1]);char buf[50]={0};// 读阻塞. 父进程会等待一会,在写管道 .read(fd[0],buf,sizeof(buf)); printf("pipe %s\n",buf);exit(0);}else  {perror("fork");exit(1);}//system("pause");return 0;
}

3.2 无名管道

3.2.1 无名管道特性

  1. 亲缘关系进程使用
  2. 有固定的读写端

3.2.2 无名管道使用流程

创建并打开管道:pipe函数

#include <unistd.h>
int pipe(int pipefd[2]);

功能:创建并打开一个无名管道
参数:

  • pipefd[0]:无名管道的固定读端
  • pipefd[1]:无名管道的固定写端
    返回值:成功0,失败-1

注意事项:

  1. 无名管道的架设应该在fork之前进行。

无名管道的读写:使用文件IO的读写方式

  • 读:read()
  • 写:write()

关闭管道:close()

管道通信举例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char **argv)
{int fd[2] = {0};int ret = pipe(fd);pid_t pid = fork();if (pid > 0){//父进程写管道,不读.所以关闭管道的读段.close(fd[0]);int i = 0;char buf[1024] = {0};memset(buf, 'a', sizeof(buf));//这个地方会写阻塞, 管道大小64K for (i = 0; i < 65; ++i){write(fd[1], buf, sizeof(buf));printf("i  is %d\n", i + 1);}}else if (0 == pid){//子进程读管道,不写.所以关闭管道的写段.close(fd[1]);int i = 5;while (i--){sleep(1);}}else{perror("fork");exit(1);}// system("pause");return 0;
}

管道破裂的代码举例如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char **argv)
{int fd[2] = {0};int ret = pipe(fd);pid_t pid = fork();if (pid > 0){//父进程写管道,不读.所以关闭管道的读段.close(fd[0]);char buf[] = "hello";printf("father id:%d\n", getpid());sleep(5);  //确保子进程 关闭管道的读段//借助gdb 观测管道破裂write(fd[1], buf, strlen(buf));  // 管道破裂 ,当前进程结束,异常printf("------------\n");}else if (0 == pid){printf("child %d\n", getpid());//子进程读管道,不写.所以关闭管道的写段.close(fd[1]);close(fd[0]);  //读段关闭int i = 5;}else{perror("fork");exit(1);}// system("pause");return 0;
}

下面代码为无名管道的单相数据传递,最基础的

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char **argv)
{int fd[2] = {0};int ret = pipe(fd);pid_t pid = fork();if (pid > 0){//父进程写管道,不读.所以关闭管道的读段.close(fd[0]);char buf[] = "hello";write(fd[1], buf, strlen(buf));exit(0);}else if (0 == pid){//子进程读管道,不写.所以关闭管道的写段.close(fd[1]);while (1){char buf[50] = {0};int ret = read(fd[0], buf, sizeof(buf));// read的返回值,如果为0 代表通信结束if (ret <= 0){printf("通讯结束\n");break;}printf("pipe %s\n", buf);}exit(0);}else{perror("fork");exit(1);}// system("pause");return 0;
}

复制一个二进制文件,然后读取,在父子进程中:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
int	main(int argc, char **argv)
{int fd[2]={0};int ret = pipe(fd);pid_t pid = fork();if(pid>0){close(fd[0]);int srcfd = open("/home/linux/1.png",O_RDONLY);if(-1 == srcfd ){perror("father open");exit(1);}int num =0;while(1){char buf[4096]={0};int ret = read(srcfd,buf,sizeof(buf));if(ret<=0){time_t tm;time(&tm);printf("发送结束,num %d, %lu\n",num,tm);break;}write(fd[1],buf,ret);num+=ret;}close(srcfd);close(fd[1]);exit(0);}else if(0 == pid){close(fd[1]);int dstfd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);if(-1 == dstfd ){perror("child open");exit(1);}int num = 0 ;while(1){char buf[4096]={0};int ret = read(fd[0],buf,sizeof(buf));if(ret<=0){time_t tm;time(&tm);printf("读取数据结束,%d, tm:%lu\n",num,tm);break;}write(dstfd,buf,ret);num+=ret;}close(fd[0]);close(dstfd);exit(0);}else  {perror("fork");exit(1);}

3.2.3 无名管道练习验证

练习:
设计一个多进程程序,用无名管道完成父子进程间的任意信息传送,包括数字、字符串等。

验证问题:

  1. 父子进程是否都有fd[0] fd[1]?
    • 可以,写fd[1]可以从fd[0]读
  2. 管道的数据存储方式?
    • 队列形式存储,读数据会剪切取走数据不会保留,先进先出
  3. 管道的数据容量?
    • 操作系统建议值:512*8=4k
    • 实际测试值:65536byte=64k
  4. 管道的同步效果?
    • 读端关闭不能写(→SIGPIPE异常终止)
    • 写端关闭可以读(取决于pipe有无内容,read返回0不阻塞)
  5. 固定的读写端能否互换?能否写fd[0]能否读fd[1]?
    • 不可以,是固定读写端

双向通信实现方案:

pipe();
fork();if(pid>0) {read(file,);write(fd[1]);
}if(0 == pid) {read(fd[0]);write(newfile);
}

3.3 有名管道

有名管道===》fifo==》有文件名称的管道

3.3.1 有名管道框架

创建有名管道 → 打开有名管道 → 读写管道 → 关闭管道 → 卸载有名管道

3.3.2 有名管道操作函数

  1. 创建:mkfifo
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

功能:创建权限为mode的有名管道文件
参数:

  • pathname:路径+名称
  • mode:8进制文件权限
    返回值:成功0,失败-1
  1. 打开:open
    注意事项:
  • 打开方式决定当前进程的读写方式
  • 只能是O_RDONLY(固定读端)或O_WRONLY(固定写端)
  • 不能使用O_RDWR或O_CREAT
  1. 读写:使用文件IO
  • 读:read(fd-read,buff,sizeof(buff))
  • 写:write(fd-write,buff,sizeof(buff))
  1. 关闭:close(fd)

  2. 卸载:unlink

int unlink(const char *pathname);

功能:卸载并删除有名管道文件
参数: ptahtname 要卸载的有名管道
返回值:成功0,失败-1

有名管道举例代码:
读操作代码:

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<time.h>
#include<unistd.h>
#include<errno.h>
int	main(int argc, char **argv)
{int ret = mkfifo("myfifo",0666);if(-1 == ret){perror("mkfifo");exit(1);}int fd =open("mkfifo",O_WRONLY);if(-1 == fd){perror("open");exit(1);}char buf[50] = {0};read(fd,buf,sizeof(buf));printf("fifo:%s\n",buf);close(fd);//remove("myfifo");//system("pause");return 0;
}

写操作代码:

#include <asm-generic/errno-base.h>
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<time.h>
#include<unistd.h>
#include<errno.h>
int	main(int argc, char **argv)
{int ret = mkfifo("myfifo",0666);if(-1 == ret){if(EEXIST!=errno){perror("mkfifo");exit(1);}}int fd =open("mkfifo",O_WRONLY);if(-1 == fd){perror("open");exit(1);}char buf[50] ="hello";write(fd,buf,strlen(buf));//printf("fifo:%s\n",buf);close(fd);//remove("myfifo");//system("pause");return 0;
}

3.3.3 有名管道注意事项

  1. 同步问题:
    • 必须有读写端同时存在
    • 如果有一端没有打开,open函数会阻塞
  2. 亲缘关系进程使用:
    • 可以在有亲缘关系的进程间使用
    • 注意启动次序可能导致阻塞
  3. 手工操作:
    • 读:cat fifoname
    • 写:echo “content” > fifoname
      1)明确点:是否需要同步?同步的位置
      读写端关闭,是否可以写,不能写的话,是什么原因,写端关闭,是否可以读。
      2)结论:有名管道执行过程中必须读写端同时存在,如果有一段没有打开,则默认在open函数部分阻塞
      3)、能否手工操作有名管道实现数据的传送。
      读: cat fifoname
      写: echo “asdfasdf” > fifoname
      strcat
      举例项目代码:可以实现信息的传递:
#include <errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<time.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
int	main(int argc, char **argv)
{int ret = mkfifo("myfifo",0666);if(-1 == ret){if(EEXIST != errno){perror("mkfifo");exit(1);}}int fd = open("myfifo",O_WRONLY);if(-1==fd){perror("open");exit(1);}int srcfd =open("/honme/linux/1.png",O_RDONLY);if(-1==srcfd){perror("open srcfd");exit(1);}while(1){char buf[4096] = {0};int ret = read(srcfd,buf,sizeof(buf));if(ret<= 0){break;}write(fd,buf,ret);}close(fd);close(srcfd);//system("pause");return 0;
}

读操作:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
int main(int argc, char **argv)
{int ret = mkfifo("myfifo", 0666);if (-1 == ret){//如果错误的原因是 不是fifo 文件已经存在,if (EEXIST != errno){perror("mkfifo");exit(1);}}int fd = open("myfifo", O_RDONLY);if (-1 == fd){perror("open");exit(1);}int dstfd = open("2.png", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (-1 == dstfd){perror("open dstfd");exit(1);}while (1){char buf[1024] = {0};int ret = read(fd, buf, sizeof(buf));if(ret<=0){break;}write(dstfd,buf,ret);}close(fd);close(dstfd);// remove("myfifo");// system("pause");return 0;
}

4. 信号通信

进程间通信 → 信号通信 signal
应用:异步通信、中断
信号范围:1~64(32用于应用编程)

4.1 信号发送端

  1. kill函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

功能:给pid进程发送sig信号
参数:

  • pid:接收进程pid
  • sig:信号编号(kill -l查看)
    返回值:成功0,失败-1
  1. raise函数
int raise(int sig);

等价于kill(getpid(),int sig)

  1. alarm函数
unsigned int alarm(unsigned int seconds);

功能:定时发送SIGALRM信号

  1. pause函数
int pause(void);

功能:进程暂停,直到收到信号

4.2 信号接收处理

4.2.1 信号处理方式

  1. 默认处理
  2. 忽略处理(9,19信号不能忽略)
  3. 自定义处理(9,19信号不能自定义)

4.2.2 信号注册函数

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

参数:

  • handler可以是:
    • SIG_DFL:默认处理
    • SIG_IGN:忽略处理
    • 自定义函数:void fun(int sig)

特殊信号:

  • 10 SIGUSR1
  • 12 SIGUSR2
    (预留给程序员使用的未定义信号)

5. 练习与作业

5.1 基础练习

  1. 编写信号处理函数,对SIGUSR1和SIGUSR2输出不同语句
  2. 验证信号反复注册的处理流程(最后注册的有效)

5.2 大作业

  1. 修改有名管道通信程序,添加信号处理:

    • 发送quit时,进程A发送10或12信号
    • 进程B收到信号后退出
  2. 创建多进程程序处理信号:

    • 子进程:
      • 收到10信号打印a.txt
      • 收到12信号打印b.txt
      • 收到13信号退出
    • 父进程:
      • 提示用户输入
      • 根据输入决定发送信号编号
http://www.dtcms.com/a/284473.html

相关文章:

  • Expr系列1(函数,表达式,约束系统)
  • Claude Code如何集成到VSCode、PyCharm IDE及使用技巧
  • 云手机的具体技术要求有什么?
  • Flutter:上传图片,选择相机或相册:wechat_assets_picker
  • docker 容器无法使用dns解析域名异常问题排查
  • 微服务的编程测评系统3-加密-日志-apifox-nacos-全局异常
  • Kubernetes (k8s)环境重启Pod方式总结
  • CPU架构、三级缓存以及内存优化屏障
  • Allure + JUnit5
  • 打造风险评估体系,筑牢城市安全基石
  • 用Python实现神经网络(二)
  • 解决hadoop常用到的问题
  • TRAE IDE** 下载、安装、开发、测试和部署 2048 小游戏的全流程指南
  • Linux内核内存管理相关的配置参数
  • 腾讯会议本地录屏转存失败解决办法
  • React 18 vs Vue3:状态管理方案深度对比
  • 如何实现微信小程序引导组件【添加到我的小程序】+ 附源码
  • 2025最新版Python 3.13.5安装使用指南
  • AI+医疗!VR和MR解剖学和针灸平台,智能时代如何重塑健康未来
  • wx小程序原生开发使用高德地图api
  • Qt初阶开发:QMediaPlayer的介绍和使用
  • 工业控制线路板是什么?工控PCB的核心原理和设计要点
  • git基本操作【GIT-2】
  • Ubuntu20.04 安装qt5.12.8
  • 用Python实现神经网络(一)
  • 基于FPGA的IIC控制EEPROM读写(2)
  • 解决 MyBatis/MyBatis-Plus 中 UUID 类型转换错误的最佳实践
  • OC—初识UIStackView
  • 线程安全集合——CopyOnWriteArrayList
  • FRP配置( CentOS 7 上安装 FRP教程 )