通过管道和共享存储映射实现进程通信

news2024/11/18 19:49:40

1.IPC方法

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。

父子进程间只有PCB是处在同一块物理内存,但虚拟内存上的用户空间却不是对应同一块物理内存,各自有各自的

在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:

① 管道 (使用最简单)

② 信号 (开销最小)

③ 共享映射区 (无血缘关系)

④ 本地套接字 (最稳定)

2.管道

2.1 管道的概念

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

1. 其本质是一个伪文件(实为内核缓冲区)

  • 像创建的文件、目录、软链接这些都是文件,是真正存在硬盘当中的;而像字符设备、管道、套接字、块设备这些是伪文件,不是真正文件,并不占用磁盘的物理空间,真正占用的是内存(缓冲区)

2. 由两个文件描述符引用,一个表示读端,一个表示写端。

3. 规定数据从管道的写端流入管道,从读端流出。

管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

管道的局限性:

  • 数据不能进程自己写,自己读。
  • 管道中数据不可反复读取。一旦读走,管道中不再存在。
    • 由于是借助环形队列机制,因此不像正常的缓冲区那样可以反复读
  • 采用半双工通信方式,数据只能在单方向上流动。
  • 只能在有公共祖先的进程间使用管道。

常见的通信方式有,单工通信、半双工通信、全双工通信

2.2 pipe函数

创建并且打开管道
    int pipe(int pipefd[2]);
    成功:0;失败:-1,设置errno
函数调用成功返回r/w两个文件描述符。
无需open,但需手动close。
规定:
pipefd[0] → r; 
pipefd[1] → w,就像0对应标准输入,1对应标准输出一样。
向管道文件读写数据其实是在读写内核缓冲区。

管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:

  1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。(父子进程PCB映射在同一块物理内存上)
  2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
  3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

练习:父子进程使用管道通信,父写入字符串,子进程读出并,打印到屏幕。【pipe.c】

思考:为甚么,程序中没有使用sleep函数,但依然能保证子进程运行时一定会读到数据呢?

2.3 管道的读写行为

使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):

  1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
  2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
  3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。
  4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

总结:

读管道:

  • 管道中有数据,read返回实际读到的字节数。
  • 管道中无数据:
    • 管道写端被全部关闭,read返回0 (好像读到文件结尾)
    • 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)

写管道:

  • 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
  • 管道读端没有全部关闭:
    • 管道已满,write阻塞。
    • 管道未满,write将数据写入,并返回实际写入的字节数。

练习:使用管道实现父子进程间通信,完成:ls | wc –l。假定父进程实现ls,子进程实现wc。(pipe,dup2,execlp)

ls命令正常会将结果集写出到stdout,但现在会写入管道的写端;wc –l 正常应该从stdin读取数据,但此时会从管道的读端读。

程序执行,发现程序执行结束,shell还在阻塞等待用户输入。这是因为,shell → fork → ./pipe1, 程序pipe1的子进程将stdin重定向给管道,父进程执行的ls会将结果集通过管道写给子进程。若父进程在子进程打印wc的结果到屏幕之前被shell调用wait回收,shell就会先输出$提示符。---- 要么关闭写端,然后读端自动关闭;要么直接关闭读端使子进程顺利被回收,写端自动关闭

但这里父进程先执行写入资源,子进程一直阻塞等待资源无法被父进程回收,使得父进程无法被隐式回收关闭到写端(子进程先回收后父进程回收);父进程中也没法去关闭写端使得子进程的读端自动结束,因为执行execlp代码全被替换了,在execlp后加上close(fd[1])也没用,执行不到,同理子进程也因为execlp无法去添加close(fd[0])来解决子进程阻塞等待

可以将父子进程的工作调换一下,让父进程作为读端先去执行,子进程作为写端后执行。读端打开等待读取资源 --- > 写端打开后输入资源自动关闭,子进程被系统回收 ---> 父进程输出结果,检测到写端全部关闭使读端返回0自动关闭


练习:使用管道实现兄弟进程间通信。 兄:ls 弟: wc -l 父:等待回收子进程。

要求,使用“循环创建N个子进程”模型创建兄弟进程,使用循环因子i标示。注意管道读写行为。


测试:是否允许,一个pipe有一个写端,多个读端呢? 是否允许有一个读端多个写端呢?

2.4 管道缓冲区大小

可以使用 ulimit –a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。通常为:

pipe size            (512 bytes, -p) 8

也可以使用fpathconf函数,借助参数选项来查看。使用该宏应引入头文件<unistd.h>

long fpathconf(int fd, int name);
成功:返回管道的大小失败:-1,设置errno

2.5 管道的优劣

优点:简单,相比信号,套接字实现进程间通信,简单很多。

缺点:1. 只能单向通信,双向通信需建立两个管道。

           2. 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决。

3.FIFO

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据

FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。用户空间 --- 系统调用进入内核 --- FIFO文件桥梁,数据交换

创建方式:

1. 命令:mkfifo 管道名

2. 库函数:

int mkfifo(const char *pathname,  mode_t mode); 
成功:0; 失败:-1

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。

4.共享存储映射

4.1 文件进程间通信

使用文件也可以完成IPC,理论依据是,fork后,父子进程共享文件描述符(共享PCB和MMU映射区 -- MMU映射区包含PCB,PCB中包含文件描述符表)。也就共享打开的文件。

练习:编程测试,父子进程共享打开的文件。借助文件进行进程间通信。 【fork_shared_fd.c】

更改:

这里还会读到长度么??

并不会,注意是管道、套接字、设备文件这些才有读写行为,这里打开是普通文件,read打开后是并不阻塞等待wirte输入东西后将其读走的,因此并没有读到东西,buf的长度是为0

思考,无血缘关系的进程可以打开同一个文件进行通信吗?为什么?

可以,打开同一个文件的话,获取的文件描述符是相同的,在内核的缓冲区是同一块的。

4.2 存储映射I/O

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。

使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。

如果是在磁盘只能通过open来wirte和read来操作数据,但是如果映射到内存上,就可以用指针等来进行操作,通过修改内存来改变磁盘文件

mmap函数

创建共享内存映射区
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset); 
返回:成功:返回创建的映射区首地址;失败:MAP_FAILED宏
参数:
addr: 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
length:欲创建映射区的大小
prot:映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags:标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
        标志共享内存的共享属性
MAP_SHARED:  会将映射区所做的操作反映到物理设备(磁盘)上。
MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
fd: 用来建立映射区的文件描述符(对应在磁盘上的那个文件,该文件需要有实际大小,用于实现映射)
offset:映射文件的偏移(4k == 4096Byte的整数倍);默认0,表示映射文件全部

ftruncate实现文件大小扩展(里面的数据是文件空洞,打开的文件需要有写的权限,不然无法扩展),利用lseek获取文件扩展后的大小,mmap后利用p写入数据,输入多少就替换掉多少文件空洞,因此文件扩展的大小一定要足够,欲建立的映射区的大小length不能超过文件fd的扩展的大小

munmap函数

同malloc函数申请内存空间类似的,mmap建立的映射区在使用结束后也应调用类似free的函数来释放。
int munmap(void *addr, size_t length);
成功:0; 失败:-1
addr:mmap的返回值
lenght:大小

借鉴malloc和free函数原型,尝试装自定义函数smalloc,sfree来完成映射区的建立和释放。思考函数接口该如何设计?

4.3 mmap注意事项

思考:

1. 可以open的时候O_CREAT一个新文件来创建映射区吗?

2. 如果open时O_RDONLY,mmap时PROT参数指定PROT_READ|PROT_WRITE会怎样?

3. 文件描述符先关闭,对mmap映射有没有影响?

4. 如果文件偏移量为1000会怎样?

5. 对mem越界操作会怎样?

6. 如果mem++,munmap可否成功?

7. mmap什么情况下会调用失败?

8. 如果不检测mmap的返回值,会怎样?

1. 特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!!mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。如,400字节大小的文件,在建立映射区时offset 4096字节,则会报出总线错误。

  • 用于创建映射区的文件大小为 0,实际指定非0大小创建映射区,出“总线错误”
  • 用于创建映射区的文件大小为 0,实际制定0大小创建映射区, 出“无效参数”。

2. 用于创建映射区的文件(open)读写属性为 只读 ,映射区(mmap)属性为 读、写,出现“无效参数”。文件的打开必须要有读,因为创建映射区的过程中,隐含着一次对映射文件的读操作。权限:mmap <= open(至少要有读且标志位参数为MAP_SHARED)

  • 读(文件) -- 读(映射):只进行读操作,进行写会出现段错误
  • 读写 -- 读、读写、写
  • 写 -- 写:无效参数,无法隐式读下文件

3. 文件描述符fd,在mmap创建映射区完成后即可关闭,后续访问文件,用映射区的地址访问即可

4. offset必须是4096(Byte == 4k)的整数倍,否则会出现无效参数的错误。(MMU映射的最小单位是4K,创建映射区就是MMU在做的)

5. 如果对mem越界操作,如果映射区内存空间富裕的话可以不会出错,如果空间没那么多就会出现段错误(核心存储已满),使用的内存不是你当初申请的那块映射的内存,最好不要越界访问

6. munmap释放的地址必须是mmap申请返回的地址。mmap后得到的指针p不能进程自增或自减操作,不然munmap在释放p的时候会报错:无效参数。p+1可以当不能p++或是p = p + 10之类的操作,不能改变本身

7. mmap调用失败主要是参数,像欲建立的映射区大小length大于文件的大小、映射区的权限prot和文件权限不符、标志共享内存的共享属性flags、文件描述符fd错误、映射文件的偏移量offset不是4k的整数倍

8. mmap的返回值必须检查,由于mmap函数的参数很多,只要一个参数出错mmap就会调用失败,mmap函数返回值是检查是否调用成功的重要一步

9. 映射区标志参数flags,如果为私有MAP_PRIVATE,对内存所做的所有修改,只在内存有效,不反应到物理磁盘上

因此这里基础文件打开即使没有写的权限,而映射区有写和读的权限,mmap权限比文件权限大,执行后也不会报错,因为映射区标志位参数为私有,即使对内存写也不会写入到文件

4.4 mmap父子进程通信

父子等有血缘关系的进程之间也可以通过mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时候指定对应的标志位参数flags:

  • MAP_PRIVATE: (私有映射) 父子进程各自独占映射区
  • MAP_SHARED: (共享映射) 父子进程共享映射区

练习:父进程创建映射区,然后fork子进程,子进程修改映射区内容,而后,父进程读取映射区内容,查验是否共享。 【fork_mmap.c】

结论:父子进程共享:1. 打开的文件 2. mmap建立的映射区(但必须要使用MAP_SHARED)

4.5 匿名映射

通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。 可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。

使用MAP_ANONYMOUS (或MAP_ANON), 如: 
int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); 
"4"随意举例,该位置表大小,可依实际需要填写。

需注意的是,MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。在类Unix系统中如无该宏定义,可使用如下两步来完成匿名映射区的建立。

① fd = open("/dev/zero", O_RDWR);
② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

4.6 mmap无血缘关系进程间通信

实质上mmap是内核借助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程间也可以使用mmap来完成通信。只要设置相应的标志位参数flags即可。若想实现共享,当然应该使用MAP_SHARED了。

值得注意的是:MAP_ANON和 /dev/zero 都不能应用于非血缘关系进程间通信。只能用于亲子进程间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2173784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

物联网智能项目全面解析

目录 引言 一、物联网概述 1.1 什么是物联网 1.2 物联网的历史与发展 二、物联网智能项目分类 三、关键组件与技术 3.1 传感器和执行器 3.2 连接技术 3.3 数据处理与分析 3.4 用户界面 四、物联网智能项目案例分析 4.1 智能家居 4.2 智慧城市 4.3 工业物联网 4.4…

孩子英语不好,能学编程吗?

随着编程教育的普及&#xff0c;越来越多的家长开始关注孩子的编程学习。然而&#xff0c;不少家长提出了这样的疑问&#xff1a;“孩子的英语不好&#xff0c;是否还能学编程&#xff1f;”毕竟&#xff0c;编程语言是基于英语的&#xff0c;代码中也充斥着大量的英语单词和短…

数据结构-3.6.队列的链式实现

队列可以理解为单链表的阉割版&#xff0c;相比单链表而言&#xff0c;队列只有在添加和删除元素上和单链表有区别 一.队列的链式实现&#xff1a; 1.图解&#xff1a; 2.代码&#xff1a; #include<stdio.h> ​ typedef struct LinkNode //链式队列结点 {int data;st…

【韩顺平Java笔记】第1章

0-1可以看视频&#xff0c;下面记录主要内容 2. 就业方向 Java基础又叫JavaSE&#xff0c;Java有三个主要的就业方向&#xff1a; JavaEE软件工程师&#xff1a;电商&#xff0c;团购&#xff0c;众筹&#xff0c;sns&#xff08;社交网络&#xff09;&#xff0c;教育&…

纠删码参数自适应匹配问题ECP-AMP实验方案(一)

摘要 关键词&#xff1a;动态参数&#xff1b;多属性决策&#xff1b;critic权重法&#xff1b;DBSCA聚类分析 引言 云服务存储系统是一种基于互联网的数据存储服务&#xff0c;它可以为用户提供大规模、低成本、高可靠的数据存储空间。云服务存储系统的核心技术之一是数据容…

winsoft公司Utils组件功能简介

Winsoft Utils Library 2.3 是一个为 Delphi 和 C Builder&#xff08;版本 7 到 12 Athens&#xff09;设计的实用工具库。它提供了一系列组件和类&#xff0c;旨在简化和增强开发过程。以下是一些主要功能和特点&#xff1a; 1.组件集合&#xff1a;包含多种实用组件&#x…

AB plc设备数据 转profinet IO项目案例

目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 网关采集AB PLC数据 2 5 用PROFINET IO协议转发数据 4 6 案例总结 7 1 案例说明 设置网关采集AB PLC数据把采集的数据转成profinet IO协议转发给其他系统。 2 VFBOX网关工作原理 VFBOX网关是协议转换网关&#xff0…

如果再回到从前——备忘录模式

文章目录 如果再回到从前——备忘录模式如果再给我一次机会……游戏存进度备忘录模式备忘录模式基本代码游戏进度备忘 如果再回到从前——备忘录模式 如果再给我一次机会…… 时间&#xff1a;5月6日18点  地点&#xff1a;小菜、大鸟住所的客厅  人物&#xff1a;小菜、…

Sharding-JDBC笔记03-分库分表代码示例

文章目录 一、水平分库1. 将原有order_db库拆分为order_db_1、order_db_22. 分片规则修改分片策略standardcomplexinlinehintnone 3. 插入测试4. 查询测试5. 使用分库分片键查询测试总结 二、公共表1. 创建数据库2. 在Sharding-JDBC规则中修改3. 数据操作4. 字典操作测试5. 字典…

Linux线程-POSIX信号量与锁以及条件变量

POSIX信号量 POSIX没有元素这个概念相比于SYSTEM-V更简洁&#xff0c;POSIX不一定适用老版本&#xff1b;二者都是系统范畴&#xff0c;都需要手动删除&#xff0c;POSIX相关函数属于线程库&#xff0c;所有编译时需要末尾加上-lpthread选项 POSIX POSIX有名信号量 主要用于进…

华为 HCIP-Datacom H12-821 题库 (29)

&#x1f423;博客最下方微信公众号回复题库,领取题库和教学资源 &#x1f424;诚挚欢迎IT交流有兴趣的公众号回复交流群 &#x1f998;公众号会持续更新网络小知识&#x1f63c; 1.BFD 为确保两端系统都知道状态的变化&#xff0c;在BFD 状态机的建立和拆除时都采用三次握手…

【LeetCode:75. 颜色分类 + 快速排序】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

图像超分辨率(SR)

图像超分辨率&#xff08;Image Super-Resolution, SR&#xff09;是一种图像处理技术&#xff0c;旨在从低分辨率&#xff08;LR&#xff09;图像中恢复出高分辨率&#xff08;HR&#xff09;图像。这种技术通过增加图像中的细节和清晰度来提高图像的视觉质量&#xff0c;从而…

微信支付准备工作之内网穿透2024/9/28

微信支付部分好像做不了&#xff0c;但是跟着写了点东西。 做项目的微信支付功能时&#xff0c;微信后台需要调用到商户管理系统&#xff0c;但是我们的管理系统写在自己的电脑里。微信后台如何调用到商户系统&#xff1f;内网穿透。 还有如何保证调用过程数据安全&#xff1…

PowerDesigner 16.5安装教程 + 轻松解决软件证书过期导致的无法使用问题

目录 背景: 安装教程 &#xff1a; 证书过期的解决方法&#xff1a; 背景: PowerDesigner16.5凭借强大的功能集成、广泛的兼容性和高效的模型驱动开发方法成为了企业级建模于设计领域的佼佼者。无论是在企业信息化建设、软件开发项目还是数据库设计于管理等领域它都能提有利…

【步联科技身份证】 身份证读取与解析———未来之窗行业应用跨平台架构

一、身份证解析代码 C# function 身份证数据解析_湖南步联科技(wzxx) {var result {};result[xm] wzxx.substr(0, 15);result[xbdm] wzxx.substr(15, 1);result[mzdm] wzxx.substr(16, 2);result[csrq] wzxx.substr(18, 8);result[dzmc] wzxx.substr(26, 35);result[gms…

论文阅读 | HiDDeN网络架构

ECCV 2018 斯坦福-李飞飞团队 一、问题描述 受以下启发&#xff1a; 对抗性例子的发现&#xff1a;深度学习模型在图像识别任务中对微小的、难以察觉的输入扰动非常敏感&#xff0c;这些扰动可以导致模型错误分类图像。这一现象表明&#xff0c;神经网络可以在图像中编码信息&…

一文上手SpringSecurity【五】

对于前后端不分离的项目,我们可以采用一文上手SpringSecurity【四】当中的方式来自定义用户的登录页面和数据源,数据源目前采用的是模拟的方式来实现的,本篇内容主要介绍一下spring security对于前后端分离项目如何实现认证和授权的. 一、前后端分离的认证面对的问题 1.1 传统…

File systems

inode descriptor 文件系统中核心的数据结构就是inode和file descriptor。后者主要与用户进程进行交互。 inode&#xff0c;这是代表一个文件的对象&#xff0c;并且它不依赖于文件名。实际上&#xff0c;inode是通过自身的编号来进行区分的&#xff0c;这里的编号就是个整数…

修改 idea 的 Terminal 命令窗口使用 git-bash

修改配置方法 实际使用效果 &#xff08;END&#xff09;