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

进程之IPC通信一

IPC通信

1.IPC通信 简介

IPCInternal Procces Communication 进程间通信
实质:信息(数据)的交换(通信)
在父进程内定义一个全局变量A,然后再给A进行赋值。然后再让子进程去读取,不就实现通信了吗。
这个方案:纯属扯淡。**原因:**两个进程的地址空间是独立的(物理地址)。

#include <iostream>
#include  <unistd.h>
#include <sys/types.h>
#include <wait.h>
using namespace std;int main()
{int *p =  new int();*p = 100;pid_t pid = fork();if(pid  < 0){perror("pid_t");}if(pid  == 0){sleep(1);cout << "子进程读取p的值:" << *p << "   p的地址:"<< p << endl;}else if(pid  > 0){*p = 200;cout << "父进程读取p的值:" << *p << "   p的地址:"<< p << endl;wait(nullptr);}return 0;
}

结果是:

父进程读取p的值:200   p的地址:0x648db8855eb0
子进程读取p的值:100   p的地址:0x648db8855eb0

由此可见父进程修改成200之后子进程p还是100,虽然他们的地址一样还是资源不能通信共享,因为这个地址是虚拟地址,他们再实际的物理地址上面还是独自的地址,所有不能通信

如果两个进程要进行通信,必须要把数据放到一个大家都可以访问的地方。
<文件>:可以支持进程间的通信,大家都可以访问。

#include <iostream>
#include  <unistd.h>
#include <sys/types.h>
#include <wait.h>
#include <fcntl.h>
using namespace std;int main()
{pid_t pid = fork();if(pid  < 0){perror("pid_t");}if(pid  == 0){FILE * fptr = fopen("1.txt",  "a+");if(fptr == nullptr){perror("打开失败");}sleep(1);char a = '\0';fread(&a, 1, 1,fptr );cout << a << endl;fclose(fptr);}else if(pid  > 0){FILE * fptr = fopen("1.txt",  "a+");if(fptr == nullptr){perror("打开失败");}fwrite("a", 1, 1, fptr);fclose(fptr);wait(nullptr);}return 0;
}

可以通信,但有一个很大的问题:速度太慢了。

有没有其他方式?
在操作系统内核中开辟一段空间(某种机制),进程去访问它。这个内核空间对于进程而言是共享的。

1.1 IPC方式

  • 管道:
    • pipe无名管道
    • fifo 有名管道
  • 信号:
    • signal
  • 消息队列
    • System V 消息队列
    • POSIX消息队列
  • 信号量
    • System V 信号量
    • POSIX 信号量
  • 共享内存
    • System V 共享内存
    • POSIX 共享内存
  • socket 通信
    • unix 域协议

2.管道

在很久以前,进程间通信方式,都是通过文件。这种方式有个缺点:效率(速度)太低了。但是这种方
式有一个天大的好处:简单,不需要额外API 函数支持(直接利用文件系统的API 操作)
因为弊端所有需要改进,首先问题存在于:文件内容是在外设上面,文件系统上====>访问效率低。

管道:管道文件,它的文件内容是在内核/内存中。

2.1 无名管道:pipe

它在文件系统中没有名字( inode ),它的内容存放在内核中,访问pipe的方式是通过文件系统的 APIread/write)。

它不能用open,但是read/write有需要一个文件描述符。所以创建这个pipe的时候,就必须要返回 一个文件描述符!!!!

pipe在创建的时候,在内核中开辟一块区域作为缓冲区,作为pipe的文件的内容区域的存储空间,同 时返回两个文件描述符**(一个是用来读,一个用来写)**

它有如下特点:

  • pipe有两端,一端用于写,一端用于读 按顺序读,
  • 不支持lseek 光标偏移
  • 内容读走,就莫有了
  • pipe(无名管道)随内核持续性

在这里插入图片描述

pipe的API

  • 创建pipe管道

    Alpha、IA-64、MIPS、SuperH 和 SPARC/SPARC64 架构上使用的 pipe 系统调用的声明

      /* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64; see NOTES */struct fd_pair {long fd[2];};struct fd_pair pipe();/* On all other architectures */
    

    其他在其他架构上使用pipe接口

    • 函数原型
    #include <unistd.h>
    int pipe(int pipefd[2]);
    
    • 描述
      • 创建pipe管道
      • pipe用于在内核中创建一个无名管道
      • pipefd用来保存创建好的无名管道的文件描述符,pipe创建管道,默认“阻塞方式
    • 参数 @pipefd
      • int类型数组,表示管道文件的描述符
      • pipefd[0] 表示读端文件描述符
      • pipefd[1] 表示写端文件描述符
    • 返回值
      • 成功返回0
      • 失败返回-1,同时errno被设置。

示例

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>
using namespace std;
int main()
{// 定义一个无名管道int pipefd[2] = {0};pipe(pipefd);// 定义一个进程pid_t pid = fork();// 失败if (pid < 0){perror("创建进程失败");}// 父进程if (pid > 0){// 关闭写端,父进程只负责读close(pipefd[1]);// 父进程负责读for (int i = 0; i < 10; i++){char c = '0';read(pipefd[0], &c, 1);cout << c << endl;}// 等待子进程写端关闭再关闭读端usleep(100);// pipefd[0]就是文件指针close(pipefd[0]);// 等待子进程结束wait(nullptr);}// 子进程if (pid == 0){// 关闭读端,子进程只负责写close(pipefd[0]);// 子进程负责写for (int i = 0; i < 10; i++){char c = '0' + i;write(pipefd[1], &c, 1);}// 写完立刻关闭读端// pipefd[1]就是文件指针close(pipefd[1]);}return 0;
}

结果

1
2
3
4
5
6
7
8
9

关闭pipe时候,需要注意,要先关闭写端,再关闭读端。

pipe(无名管道)只能用于有亲缘关系的进程间通信,它为什么有这个限制?

  • 因为它没有名字
  • 假设它如果在文件系统中有个一个名字 “ inode ”它就可以用于任意进程间的通信

2.2 有名管道:fifo

fifo是在pipe基础上,给它在文件系统中创建一个inode(它会在文件系统中有一个名字),但是
fifo文件的内容却是在内核中!!!!

  • fifo的文件名随文件系统持续性
  • fifo的文件内容存在于内核,随内核持续性

fifopipe一样,除了fifo在文件系统中有一个文件名外。

fifo 的API

创建fifo文件

  • 函数原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
  • 描述:

在文件系统中创建一个fifo(有名管道)文件

  • 参数

    • pathname :需要创建的fifo文件的名字

    • modefifo文件的权限

      • (1)宏值设置:

        • S_IRUSR

        • S_IWUSR

        • S_IXUSR

      • (2)八进制:

        • 0777

        • 0774

        • 0665

  • 返回值 @return:

    • 成功返回0,

    • 失败返回-1,同时errno被设置。

注意FIFO特殊文件(命名管道)类似于管道,只是它是作为文件系统的一部分访问的。它可以通过 多个进程打开以进行读取或写入。当进程通过FIFO交换数据时内核在内部传递所有数据而不将其写 入文件系统(交换只在内核处理,不会进入到磁盘里面)。

因此,FIFO特殊文件没有内容在文件系统上;文件系统条目只是作为一个参考点,这样进程就可以访
问管道使用文件系统中的名称。内核为至少一个进程打开的每个FIFO特殊文件保留一个管道对象。
这个必须先打开FIFO的两端(读取和写入)然后才能传递数据。通常,打开FIFO一端直到另一端也
被打开。进程可以在非阻塞模式下打开FIFO。在Linux下,打开FIFO进行读写操作在阻塞和非阻塞模
式下都会成功。

  • 阻塞的读/写

    • 读的时候,如果没有数据,则read会阻塞
    • 写的时候,如果没有空间,则write会阻塞
  • 非阻塞的读/写

    • 读的时候,如果没有数据,则立即返回,设置对应错误码

    • 写的时候,如果没有空间,则立即返回,设置对应错误码

在使用open的时候,默认是阻塞方式,非阻塞则需要或上一个宏:NONBLOCK

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <wait.h>
#include <fcntl.h>
using namespace std;
int main()
{// 创建mkfifo文件mkfifo("fifo", S_IXUSR| 0777) ;// 打开文件int fifo = open("fifo", O_RDWR);if(fifo == -1){perror("fifo"); return -1;}// 创建一个子进程pid_t pid = fork();if(pid < 0){perror("pid");close(fifo);return -1;}// 父进程 读if(pid > 0){for(int i = 0; i < 10; i++){char c = '0';read(fifo,&c,1);cout << c << endl;}// 等待子进程写端关闭再关闭读端usleep(200);close(fifo);// 等待子进程结束wait(nullptr);}// 子进程 写if(pid ==  0){// 写入  for(int i = 0; i < 10; i++){char c = '0' + i;write(fifo,&c,1);}close(fifo);}return 0;
}

3.信号

信号是进程间通信一种方式,这个种方式没有传输数据。只是在内核中传一个信号(整数),信号的表 示是一个整数,不同信号值,代表不同含义,当然用户可以自定义信号,以及自定义信号的含义解释权 归用户所有。

Signal     Value     Action   Comment──────────────────────────────────────────────────────────────────────SIGHUP        1       Term    Hangup detected on controlling terminalor death of 
controlling process/*控制终端的挂起操作,或者是控制进程死亡时,控制终端上的所有
进程都会收到SIGHUP信号*/SIGINT        2       Term    Interrupt from keyboard/*从键盘接收一个中断信号,如:ctrl + c*/SIGQUIT       3       Core    Quit from keyboard/*Core:输出信息,然后中止如:ctrl + zctrl + d*/SIGILL        4       Core    Illegal Instruction/*非法指令*/SIGABRT       6       Core    Abort signal from abort(3)/*调用abort函数时候,进程会收到SIGABRT这个信号。*/SIGFPE        8       Core    Floating-point exception/*浮点运算异常的时候,产生SIGFPE信号*/SIGKILL       9       Term    Kill signal/*杀死信号,中止*/SIGSEGV      11       Core    Invalid memory reference/*非法内存引用的时候,会收到SIGSEGV信号*/SIGPIPE      13       Term    Broken pipe: write to pipe with noreaders; see 
pipe(7)/*当往一个管道中写入数据的时候,没有读端进程的时候就会产生
SIGPIPE信号*/SIGALRM      14       Term    Timer signal from alarm(2)/*定时信号,进程在调用alarm的时候,会在超时的时候产生
SIGALRM信号*/SIGTERM      15       Term    Termination signalSIGUSR1   30,10,16    Term    User-defined signal 1/*用户自定义信号1*/SIGUSR2   31,12,17    Term    User-defined signal 2/*用户自定义信号2*/SIGCHLD   20,17,18    Ign     Child stopped or terminated/*当子进程停止或者中止的时候,父进程会收到SIGCHLD信号。*/SIGCONT   19,18,25    Cont    Continue if stoppedSIGSTOP   17,19,23    Stop    Stop processSIGTSTP   18,20,24    Stop    Stop typed at terminalSIGTTIN   21,21,26    Stop    Terminal input for background processSIGTTOU   22,22,27    Stop    Terminal output for background process

进程在收到一个信号的时候,通常会有三种处理方式:

捕捉信号:

  • 把一个信号 与 用户自定义的信号处理函数关联起来

  • 那么在收到该信号的时候,就会自动调用该函数处理

  • 默认行为:

    • 收到一个信号的时候,采用操作系统默认的行为

    • 大部分信号的默认行为,是会直接干掉进程。

    • 只有一个信号SIGCHLD 是被忽略的。

      忽略该信号

3.1 信号的处理过程

通过 “ 软件中断/软中断 ” 来实现,信号处理函数起始在 “ 中断上下文 ” 执行,信号处理函数----->" 软中断服务函数 "
进程上下文:

  • 进程在大环境下 , “ 时间片轮转 ”

  • 一个进程的执行状态又分为:

    • 用户态:执行用户自己的代码的时候

    • 内核态:进入操作系统执行内核代码的时候

  • 在状态切换的时候,要保存用户自己代码的运行到的位置,保存的这个玩意就是上下文。

  • 文简单来说,就是当前任务(进程或线程)执行时的“状态”和“环境”。

    它包含以下关键内容:

    • 程序计数器(PC):指示下一条要执行的指令的地址。
    • 寄存器内容:像通用寄存器、状态寄存器等,用于存储临时数据和指令执行状态。
    • 栈指针:指向当前任务的栈顶位置。

    当发生中断或任务切换时,系统会保存当前上下文信息,以便之后能准确恢复并继续执行任务。

  • 什么是中断?

    • 中断是一种计算机系统中的机制,用于让 CPU 暂停当前正在执行的程序,转而处理其他紧急或重要的事件
  • 中断与函数区别总结如下:

    • 定义

      • 中断:异步事件处理机制,由硬件或软件触发,使 CPU 暂停当前程序,处理重要事件。
      • 函数:程序中的代码块,实现特定功能,由程序员定义和调用。
    • 触发方式

      • 中断:外部设备或软件指令触发,随机性强。
      • 函数:程序调用语句触发,顺序确定。
    • 执行流程

      • 中断:CPU 暂停进程,执行中断处理程序,完成后恢复进程。
      • 函数:按调用顺序执行,完成后返回调用处。
    • 作用及目的

      • 中断:处理外部事件、多任务、提升响应速度。
      • 函数:组织代码、复用代码、提高可读性。
    • 与进程的关系

      • 中断:可打断进程正常执行。
      • 函数:在进程控制下执行,不主动打断进程。

3.2 linux下信号相关的API

3.2.1 发送信号
  • kill函数原型
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
  • 描述:

    • 给指定的进程发送一个指定的信号
  • 参数

    • pid: 指定信号接收者(可能是多个进程)
      • pid > 0 :pid 表示接收者进程 一对一发送
      • pid = 0 :发送信号给与调用者同组的所有进程 一对同组多个
      • pid =-1 :发送信号给所有进程(有权限发送的所有进程) 一对所有
      • *pid <-1* :发送信号给组id等于``pid绝对值的所有进程。 一对指定组多个
    • *sig*
      • 要发送的信号
  • 返回值 @return:

    • 成功返回0,(表示至少有一个进程成功接收到了信号)
    • 失败返回-1,同时errno被设置
  • raisel函数原型

#include <signal.h>int raise(int sig);
  • 作用
    • 发送信号给自己
  • 参数
    • sig:信号值
  • raise(SIGINT) <=====> kill(getpid(),SIGINT)
3.2.2 捕获信号
  • 函数原型
 #include <signal.h>typedef void (*sighandler_t)(int);
  • 函数作用

    • 类型重定义语句

    • 将指向一个无返回值,带有一个int类型参数的函数指针类型重定义成新类型,类型名sighandler_t

  • 函数原型

 sighandler_t signal(int signum, sighandler_t handler);
  • 作用:

​ 捕获一个信号,执行用户自定义的处理函数

  • 参数:

    • signum:要捕获那个信号值

    • handler: 信号的处理方式,(用户捕获到那个信号之后,应该要执行的函数)

      • (1**)自定义函数**
        void my_handler_function(int sig){// 写你想干的事情}
      • ( 2)SIG_IGN 忽略该信号

      • ( 3)SIG_DEL:default

        • 采用系统默认的处理方式

  • 返回值 @return:
    • 成功返回该信号的上一次处理方式(函数指针)
    • 失败返回SIG_ERR,同时errno被设置

示例 中断信号

#include <iostream>
#include <signal.h>
using namespace std;
void mySingalHandler(int sig)
{static int count = 0;if(count < 10){cout<< "你ctrl + c是没有办法杀死我的" << endl;count++;}else{cout << "好吧,你赢了" << endl;exit(0);}
}int main()
{//捕获信号 中断信号signal(SIGINT, mySingalHandler);while(1);return 0;
}

示例 内存访问违规信号

#include <iostream>
#include <signal.h>
using namespace std;// 自定义信号处理函数
void mySingalHandler(int sig)
{cout << "非法内存访问" << endl;//非法内存访问信息exit(0);}int main()
{//捕获信号 内存访问违规信号 signal(SIGSEGV,mySingalHandler );// 访问非法内存内存int *p =nullptr;*p = 1;while(1);return 0;
}
3.2.3 设置闹钟信息
  • 函数原型
#include <unistd.h>unsigned int alarm(unsigned int seconds);
  • 作用

    • 设置一个闹钟信号,在多少秒之后发送一个闹钟信号
  • 返回值

    • **seconds:**多少秒之后,发送一个闹钟信号
  • 返回值 @return:

    • 返回上一个闹钟的剩余秒数

如:

 alarm(5);int r = alarm(10); // 10秒之后接收一个 闹钟信号
// 前面的闹钟会被替换/取消 r就是表示上一个闹钟的剩余秒数
....alarm(0); // 取消闹钟

alarm :定时发送一个闹钟信号给本进程,“ 闹钟 ” 每一个进程都有一个属于自己的 “ 闹钟 ”。闹钟的时
间到了,进程就会收到一个SIGALRM 的信号,但是同一时刻一个进程只有一个 “ 闹钟 ”生效。
大部分的信号的默认行为,是把收到信号的进程Kill掉。那么如果要改变接收到信号之后的行为:捕获信
号。

示例

#include <iostream>
#include <signal.h>
using namespace std;// 自定义信号处理函数
void mySingalHandler(int sig)
{cout << "非法内存访问" << endl; // 非法内存访问信息alarm(2);
}int main()
{// 捕获信号 内存访问违规信号signal(SIGALRM, mySingalHandler);// 设置一个2秒的闹钟,闹钟到期时会触发SIGALRM信号alarm(2);while (1);return 0;
}
3.2.4 等待信号
  • 函数原型
#include <unistd.h>int pause(void);
  • pause函数描述:

    • 让进程停在那里等待一个信号的到来
  • 作用:

    • 等待一个信号的到来
  • 返回值 @return:

    • 成功返回信号标号
    • 失败返回-1。

    示例

    #include <iostream>
    #include <signal.h>
    using namespace std;// 自定义信号处理函数
    void mySingalHandler(int sig)
    {cout << "非法内存访问" << endl; // 非法内存访问信息alarm(2);
    }int main()
    {// 捕获信号 内存访问违规信号signal(SIGALRM, mySingalHandler);// 设置一个2秒的闹钟,闹钟到期时会触发SIGALRM信号alarm(2);//让进程停在那里等待一个信号的到来pause();return 0;
    }
    

相关文章:

  • 51单片机编程学习笔记——无源蜂鸣器演奏《祝你生日快乐》
  • 大模型服务如何实现高并发与低延迟
  • SAR ADC 比较器寄生电容对性能的影响
  • OSError: [WinError 193] %1 不是有效的 Win32 应用程序。
  • [特殊字符] jQuery 响应式瀑布流布局插件推荐!
  • 王树森推荐系统公开课 排序04:视频播放建模
  • Mybatis面向接口编程
  • Conda环境管理:确保Python项目精准复现
  • 基于Qwen3-7B FP8与基石智算打造高性能本地智能体解决方案
  • 【Java高阶面经:微服务篇】1.微服务架构核心:服务注册与发现之AP vs CP选型全攻略
  • C++:STL
  • 2025华为OD机试真题+全流程解析+备考攻略+经验分享+Java/python/JavaScript/C++/C/GO六种语言最佳实现
  • lasticsearch 报错 Document contains at least one immense term 的解决方案
  • 大模型预训练、微调、强化学习、评估指导实践
  • Token的组成详解:解密数字身份凭证的构造艺术
  • ragas precision计算的坑
  • JavaScript计时器详解:setTimeout与setInterval的使用与注意事项
  • 初步认识HarmonyOS NEXT端云一体化开发
  • 活到老学到老-Spring参数校验注解Validated /Valid
  • 工单派单应用:5 大核心功能提升协作效率
  • 每日475.52元!最高检公布最新侵犯公民人身自由的赔偿金标准
  • 国家发改委:城市更新项目必须建立多元化多层级资金投入机制
  • 上海银行副行长汪明履新上海农商银行党委副书记
  • 以军在加沙北部和南部展开大规模地面行动
  • 2025全球城市科技传播能力指数出炉,上海位列第六
  • 玛丽亚·凯莉虹口连唱两夜,舞台绽放唤醒三代人青春记忆