【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋

星强君_1026

星强君_1026

2025-06-19

787人浏览

原创

引言:

两个进程之间,可以进行“数据”的直接传送吗?不能!因为进程具有独立性!

进程为什么会有独立性,本质原因是:这两个进程都有自己的虚拟地址空间,但是他们的正文代码,堆,栈,共享区等被映射到了内存当中的不同的物理空间 ,所以在内存方面具有 独立性,不会互相影响

1、进程间通信基础介绍1.1为什么需要在进程之间通信?数据传输:一个进程需要将它的数据发送给另一个进程资源共享:多个进程之间共享同样的资源。通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

总结一句话就是,在OS中,往往需要多个进程协同,共同完成一些事情。

1.2进程间通信是什么?

一个进程把自己的数据,能够交给另一个进程。

1.3我们具体如何进行进程间的通信呢?a.一般规律:

进程间通信的本质:先让不同的进程,看到同一份资源(一般都是要有OS提供)

b.具体做法

OS提供的“空间”有不同的样式,就决定了有不同的通信的方式

管道(匿名管道和命名管道)共享内存消息队列信号量2.管道2.1什么是管道

【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋

这里的子进程并没有将父进程的struct file拷贝下来,由此可见子进程继承父进程的方式是浅拷贝,所以父进程和子进程指向的都是同一份资源!但是子进程会继承父进程的读写段。

因此基于文件的,让不同的进程看到同一份资源的通信方式,叫做管道!管道只能被设计成为单向通信。

【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋

为什么父进程最开始,就要将读写两端都开启的形式去打开同一个文件呢?

因为子进程会继承父进程的读写端,而管道的要求就是单向通信,因此父进程就需要将读写段同时打开,等子进程继承之后,会根据要求,各自关掉不用的描述符

2.2匿名管道:

匿名管道:可以(只能)进行具有血缘关系的进程之间进行进程间通信!(常用于父子)

那么匿名管道如何做到让不同的进程看到同一份资源呢?创建子进程,子进程会继承父进程的相关属性信息!

创建方法:pipe函数创建

参数 fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端,这是输出型参数,需要由这两个参数找到匿名管道(匿名文件)

默认fd[0]表示读端,fd[1]表示写端 返回值:成功返回0,失败返回错误代码

下面实现一个父进程读数据,然后通过管道让子进程读。

代码语言:javascript代码运行次数:0运行复制

#include <stdio.h>#include <string.h>#include <stdlib.h>#include <unistd.h>#include <sys>#include <sys>void writer(int wfd){    const char *str = "hello father, I am child";    char buffer[128];    int cnt = 0;    pid_t pid = getpid();    while(1)    {        snprintf(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);        write(wfd, buffer, strlen(buffer));        cnt++;        sleep(1);    }}void reader(int rfd){    char buffer[1024];    while(1)    {        ssize_t n = read(rfd, buffer, sizeof(buffer)-1);        (void)n;        printf("father get a message: %s", buffer);    }}int main(){    // 1.     int pipefd[2];    int n = pipe(pipefd);    if(n 2.3匿名管道的bug:<p>子进程继承父进程后,拷贝过父进程的文件描述表,后续子进程在创建的时候,会复制原先父进程的写端,就会导致管道有不止一个写端的BUG</p>
<figure class="">@@##@@</figure><p>比如这里的4就是子进程的4也是写端</p>
<p>我们的目的是让父进程和子进程之间只有一个写端一个读端,那如何修正这个问题呢?这就需要我们在子进程拷贝父进程的文件描述表的时候,将文件表的读写端口刷新!</p>3.匿名管道的读写规则4种情况管道内部没有数据&&子进程不关闭自己的写端文件fd,读端(富)就要阻塞等待,知道pipe有数据管道内部被写满&&父进程(读端不关闭自己的fd,写端(子))写满之后,就要阻塞等待对于写端而言:不写了&&关闭了pipe,读端会将pipe中的数据读完,最后就会读到返回值为0,表示读结束,类似读到了文件的结尾读端不读&&关闭,写端在写,OS会直接终止写入的进程(子进程),通过信号13信号杀掉进程5种特性:自带同步机制血缘关系进程进行通信,常见于父子pipe是面向字节流的父子退出,管道自动释放,文件的声明中周期是随进程的管道只能单向通信,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道<p>曾经我们学习的命令行管道(匿名),本质就是我们今天学习的pipe。</p>
<figure class="">@@##@@</figure>在父端写,子端读的情况下子进程的读端为什么都是3?<p>解释:刚开始父进程3是读端,4是写端,子进程会继承父进程的读写端口。下面是父端写,子端读的情况,那么就需要将父端的读关掉,就是将3关掉,然后将子进程的写端关掉,也就是将子进程的4关掉。父进程会循环创建管道,创建子进程。</p>
<p>所以父进程就会分配两个端口给这个管道进行读写,此时的3就被分配给了读端,5分配给了写端</p>
<p>子进程会拷贝父进程的文件描述表,所以3也是子进程的读端,5就是写端。这样父亲又开始写了,所以父进程的读端3就会被丢掉,子进程要写,那么就把5丢掉了。那么以此类推,所以子进程的读端一直都是3</p>4.命名管道4.1.管道的本质<p>让不同的进程之间可以通信,让不同的进程看到同一份资源(文件)!</p>
<p>那么你怎么保证两个不同的进程打开的是同一个文件?如何让不同的进程看到同一份资源呢?</p>
<p>找到文件:文件的路径 + 文件名</p>
<p>匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。</p>
<p>如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。</p>
<p>注意命名管道是一种特殊类型的文件!</p>4.2创建一个命名管道<p>命名管道可以从命令行上创建,命令行方法是使用下面这个命令:</p><div class="aritcle_card flexRow artxards">
											<div class="artcardd flexRow">
												<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill10918" title="Linux Security Scanner"><img
														src="https://img.php.cn/upload/skill/001/246/273/6adaf20cf0281539.jpg" alt="Linux Security Scanner" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
												<div class="aritcle_card_info flexColumn">
													<a rel="nofollow" href="/xiazai/skill10918" title="Linux Security Scanner" class="overflowclass">Linux Security Scanner</a>
													<p class="overflowclass">Linux 安全审计工具,检查 SSH 配置、开放/监听端口、防火墙规则(ufw/iptables/nftables)、失败登录尝试、sudoers 权限...</p>
												</div>
												<a rel="nofollow" href="/xiazai/skill10918" title="Linux Security Scanner" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
												</a>
											</div>
										</div>
<p>命名管道也可以从程序里创建,相关函数有</p>
<p>头文件:</p>
<figure class="">@@##@@</figure><p>返回值</p>
<figure class="">@@##@@</figure><p>程序里面创建命名管道</p>
<p>生成多个可执行程序的方法</p>
<figure class="">@@##@@</figure>4.3.匿名管道与命名管道的区别匿名管道由pipe函数创建并打开。命名管道由mkfifo函数创建,打开用openFIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。命名管道和匿名管道之间的主要差异就是,命名管道可以让两个没有任何关系的进程进行通信4.4用命名管道实现server&client通信<p>PipeClient.cc</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="brush:php;toolbar:false;">#include "Comm.hpp"int main(){    int wfd = open(Path, O_WRONLY);    if (wfd <p>Comm.hpp</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="brush:php;toolbar:false;">#ifndef __COMM_HPP__#define __COMM_HPP__#include <iostream>#include <string>#include <cerrno>#include <cstring>#include <sys>#include <sys>#include <unistd.h>#include <fcntl.h>using namespace std;#define Mode 0666#define Path "./fifo"class Fifo{public:    Fifo(const string &path) : _path(path)    {        umask(0);        int n = mkfifo(_path.c_str(), Mode);        if (n == 0)        {            cout <p>PipeServer.cc </p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="brush:php;toolbar:false;">#include "Comm.hpp"#include <unistd.h>// 基于命名管道,创建一个进程池呢???int main(){    Fifo fifo(Path);    int rfd = open(Path, O_RDONLY);    if (rfd  0)        {            buffer[n] = 0;            cout 5.共享内存方式通信:<p>上面我们讲述的是关于管道通信方式,下面我们来讲解共享内存的通信方式。</p>
<figure class="">@@##@@</figure>5.1共享内存如何实现让不同的进程看到同一份资源呢?我们首先在内存中开辟一段空间,是由操作系统开辟的接着让这份共享内存对不同的进程构建映射移除映射删除共享内存<p>共享内存在内核中同时可以存在很多个,OS必须要管理所有的共享内存。如何管理呢?先描述,再组织。系统中会存在很多共享内存!</p>
<p>那么我们怎么保证,两个或者多个不同的进程看到的是同一个共享内存呢?所以我们要给共享内存提供唯一性的标识!</p>5.2.关于共享内存的函数shmget函数<p>参数key:共享内存在内核中的唯一性标识 size:共享内存大小 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的 返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1</p>
<p>shmflg标志讲解:</p>
<figure class="">@@##@@</figure>问题1:这个key是怎么形成的?意义是什么?<figure class="">@@##@@</figure><p>使用函数ftok来生成key,并且这个函数的参数可以随便传;key的值是多少并不重要,只要能够标识唯一性即可。</p>问题2:在调用shmget函数时,为什么要让用户传入key值呢?<p>因为当我们一个进程使用ftok函数有了唯一性的标识后,我们两个不同的进程只需要输入相同的key值就可以看见共享内存了!这个key值相当于一个桥梁,沟通了两个不同的进程去看见了同一份共享内存!!!让OS传,OS做不到!</p> shmctl函数删除共享内存<figure class="">@@##@@</figure><figure class="">@@##@@</figure>shmat函数挂接共享内存<figure class="">@@##@@</figure>shmdt函数去关联<figure class="">@@##@@</figure>5.3共享内存的大小<p>在内核中,共享内存的大小是以4kb为基本单位的,即便自己想要4kb—+1内存的空间,但是内核里创建的是8kb的内存,但只给你使用4kb+1的空间,所以其余字节被浪费掉了。建议共享内存申请大小是4kb的整数倍。</p>5.4共享内存的原理<p>使用共享内存通信,一定是一个进程创建新的共享内存,另一个直接获取共享内存即可。</p>
<p>共享内存,如果进程结束,我们没有主动释放它,则共享内存一直存在(除非重启系统)。这也就意味着共享内存的生命周期是随系统内核的!</p>5.5.key和shmid到底有什么区别<p>我们可以使用ipcs -m指令来查看系统中指定用户创建的共享内存。</p>
<p>发现共享内存有以下属性:</p>
<figure class="">@@##@@</figure>key:在内核角度,区分共享内存的唯一性!shmid:在用户角度,无论是指令级还是代码级,最后对共享内存进行控制,用的都是shmid!5.6.共享内存的优缺点优点:共享内存是所有进程间通信中速度最快的<p>为什么共享内存是所有进程间通信中速度最快的?</p>
<p>因为假如有两个进程A、B,当进程A把数据放到共享内存当中时,进程B能够直接看到,因为共享内存使用的是用户空间。</p>
<p>而管道通信,进程A如果需要将数据传输到进程B时,必须经过系统调用,将数据拷贝到管道中,而进程B再从管道中拷贝数据到进程B,所以管道通信经历了很多次拷贝,通信速度自然就慢了!</p>
<p>所以此时的系统调用函数write、read函数本质其实就是拷贝资源!</p>缺点:<p>共享内存不提供进程间协同的任何机制,所以就会造成数据不一致的情况。</p>
<p>所以我们怎么解决这个问题呢?我们采用信号量或者用管道进行处理(管道本身是有同步属性的)</p>
<p>用管道来实现两个进程的信息的协同。</p>6、信号量6.1.关于信号量的理论知识临界资源:被保护起来的,任何时刻只允许一个线程执行访问的公共资源临界区:访问临界资源的代码,临界区是代码,而临界资源是代码管理的资源原子性:操作对象的时候,只有两种状态:要么还没开始,要么已经结束。对于共享资源进行保护,是一个多执行流场景下,一个比较常见和重要的话题互斥:在访问一部分共享资源的时候,任何时刻只有我一个人访问,就叫做互斥同比:访问资源在安全的前提下,具有一定的顺序性6.2.信号量的原理<p>为什么信号量也属于进程间通信的范畴呢?</p>
<p>我们首先要明白信号量本质是一个计数器,描述临界资源数量的计数器。</p>在多进程场景下,int全局变量是不能实现计数器的效果呢?<p>不能,有两个原因:</p>无法在进程间共享count++,count--不是原子的!<p>count需要先从内存中读取数据放在CPU上,然后CPU进行加法或者减法操作,最后再将数据放在内存当中。因此就不是原子性的。</p>
<p>为什么多进程还要考虑原子性的问题呢?因为如果不是原子性,加减不能立刻生效,就会对别的进程的数据产生影响。</p>
<figure class="">@@##@@</figure><p>在多进程场景下,int全局变量是不能实现计数器的效果的,因为无法在进程间共享!进程之间是独立的!</p>
<p>因此就需要我们的信号量充当计数器,如何充当呢?让不同的进程先看到同一份资源——计数器资源!!因此信号量本质也是跟进程间通信相关的!</p>
<p>所有的进程,访问临界资源,都必须先申请信号量——所有的进程都能看到同一个信号量——信号量本身就是共享资源(信号量++和--操作必须是原子的!)</p></unistd.h>
【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

linux 同步机制

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2023.11.20

3588

5

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

2025.09.18

4985

12

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

2025.09.18

2090

13

c++ 全局变量
c++ 全局变量

本专题整合了c++全局变量的使用、定义、作用域等等内容,阅读专题下面的文章了解更多详细内容。

2026.03.17

199

17

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

7019

3

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.29

3365

6

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.29

4388

10

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

2025.08.29

2985

10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

2023.07.18

6487

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Docker Desktop故障排查手册
Docker Desktop故障排查手册

共0课时 | 0人学习

Docker Compose使用手册
Docker Compose使用手册

共0课时 | 0人学习

Docker官方入门手册
Docker官方入门手册

共0课时 | 0人学习