Linux---进程间通讯
1 进程间通讯概述
因为进程有着独立性的特点,所以进程间的通讯不能像线程那样方便,所以为了满足进程间通讯,才有这些方法的诞生
1-1 进程间通信目的
1-2 进程间通信发展
1-3 进程间通信分类
1-4 什么是进程间通讯
进程间通讯(IPC inter-Process Communication) 是运行在同一台计算机或不同计算机上的多个进程间进行数据交换和通讯的技术,这是因为每个进程都有自己的地址空间,它们无法直接访问彼此的数据,因此需要通过特定的机制来实现通讯
1-5 进程间怎么通讯
进程通讯的本质:是先让不同的进程,看到同一份资源("内存")。(然后才有通讯的条件)。通讯的资源不是由任何一个进程提供的,而是由OS设置同一的接口,提供的通过系统调用接口来体现。
2 管道
什么是管道
管道就是Unix中最古老的进程间通讯的方式,我们把从一个进程链接到另一个进程的一个数据流称为一个“管道”

3 匿名管道
3-1 系统调用接口

#include <unistd.h>
功能:
创建⼀⽆名管道
原型:
int pipe(int fd[2]);
参数:
fd:⽂件描述符数组,其中fd[0]表⽰读端, fd[1]表⽰写端
返回值:
成功返回0,失败返回错误代码

demo:
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
int fd[2];
pipe(fd);
int pid = fork();
if (pid == 0)
{
close(fd[1]);
while (true)
{
sleep(1);
int buf = 0;
read(fd[0], &buf, sizeof(buf));
cout << "子进程通过管道受到父进程的数据:" << buf << endl;
}
}
int cnt =1;
while (true)
{
close(fd[0]);
write(fd[1], &cnt, sizeof(cnt));
cnt++;
}
return 0;
}
3-2 用 fork 来共享管道原理

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

其实从这个原理就可以看到,父子之间通过看到同一个资源,就是基于文件系统特殊实现的管道文件,因为文件自带文件缓冲区,所以父子进程同时看到这个文件缓冲区,从而实现通讯,由此可以看出,匿名管道是复用了文件系统的那一套操作并且配上单独的系统调用来实现的
3-4 站在内核角度度-管道本质

但是匿名管道的inode是不会保存该管道的名字的那我们是怎么保证,两个进程打开的是同一个管道文件呢?子进程会继承父进程文件描述符表,这也在一定程度上体现了匿名管道的特性
3-6 管道读写规则
3-7 管道特点
1 任何一个时刻,一个发,一个收 --- 半双工

3-8 管道的 4 种情况
1 写满,读快 ---读端就要阻塞(进程)
2 写快,读慢 --- 满了的时候,写端就要阻塞
3 写关,继续读 --- read读到返回值为0,表示文件结尾
4 读关闭,写继续 --- 写端写入,没有任何意义,OS不会做没有意义的事情 ---OS会杀掉进程
4 命名管道
4-1 创建一个命名管道
4-1-1 系统命令
$ mkfifo filename

4-1-2 系统调用

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename,mode_t mode);
参数:
filename:管道文件的路径和名称
mode:创建管道文件的初始权限,注意一定要是8进制输入,否则会错乱
返回值:
0:创建成功
-1:创建失败
demo 1:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
using namespace std;
int main()
{
mkfifo("./pipe",0x666);
return 0;
}
注意:
mkfifo最后一个参数,设置的是文件初始权限,一定要是0开头的3位数字即8进制数据传入否则就会权限错乱
传入:0666
![]()
![]()
这个时候pipe的文件权限就出现错乱了
demo 2:
server:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
int main()
{
mkfifo("./pipe",0666);
int fd=open("./pipe",O_WRONLY);
int cnt =1;
while(true)
{
sleep(1);
write(fd,&cnt,sizeof(cnt));
cout<<"传输一个数据:"<<cnt<<endl;
cnt++;
}
close(fd);
return 0;
}
注意:
open的标志位不可以加O_TRUNC因为命名管道不支持,而且也可以不加O_APPEND因为没有什么意义可以不加
client:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
int main()
{
int fd=open("./pipe",O_RDONLY);
int cnt =0;
while(true)
{
sleep(1);
read(fd,&cnt,sizeof(cnt));
cout<<"接收到一个数据:"<<cnt<<endl;
}
close(fd);
return 0;
}
4-2 理解命名管道
命名管道是通过创建一个特殊的文件,让这个文件的内容来作为进程间看到的同一份资源进行通信

其实你会发现,匿名管道和命名管道都是复用了文件系统来实现的,只是他们一个是直接使用文件缓冲区,不会创建磁盘文件,但是命名管道是会创建磁盘文件的,但是它们都不会将数据刷新到磁盘
匿名管道是通过父子进程对于地址空间的拷贝找到同一个管道的,那命名管道呢?命名管道因为会在磁盘上创建文件,并且它的属性中有它自己的名字,所以这也可以复用文件系统路径唯一性来保证进程间访问的是同一个管道
4-3 匿名管道与命名管道的区别
4-3 命名管道的打开规则
1 如果当前打开操作是为读而打开FIFO时
4-4 命名管道的特性
特性匿名管道一样,但是有一点不同命名管道可以用来进行不相关的进程之间的通讯,其他特性这里就不再赘述了。但是注意open再打开管道文件的时候都是默认阻塞的只有如果只有读写端一端开启的话就会阻塞再open只有都打开才会成功返回
5 system V共享内存
5-1 什么是system V
system V是一种标准,Linux内核支持了这种标准,专门涉及了一个IPC通讯模块,通信的接口设计和原理具有相似性。
5-2 共享内存的原理
因为IPC通信的本质,是让不同的进程看见同一份"资源",而共享内存故名思意,就是让两个进程看到同一个物理空间,即共享内存

5-3 理解共享内存
1 这个物理内存通过平坦模式编址然后映射到各个进程的虚拟地址空间的用户共享区中,所以我们访问这个共享的物理只需要通过使用虚拟地址就可以通过页表转换访问真实共享的物理内存
2 开辟物理内存,将物理内存映射到进程的虚拟地址空间中,这些过程都是由OS来完成的,就是系统调用,我们用系统调用完成上述的操作
3 当我们取消关联关系的时候,由OS来释放内存
4 可能同时存在由多组进程,都在使用不同的共享内存来进行通信,所以OS内,有多个共享内存同时存在,所以我们需要也需要通过先描述,再组织,这个方式来管理的。所以我们可以砍一下共享内存管理的数据结构来理解一下他
5-4 共享内存的数据结构
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment(bytes) */
__kernel_time_t shm_atime; /* last attach time*/
__kernel_time_t shm_dtime; /* last detach time*/
__kernel_time_t shm_ctime; /* last change time*/
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of currentattaches */
unsigned short shm_unused; /* compatibility */
void shm_unused2; / ditto - used byDIPC */
void shm_unused3; / unused */
};
5-5 共享内存函数
shmget 函数

功能:⽤来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字,通过ftok这个函数来获取
size:共享内存⼤⼩
shmflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回。
返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1
key值我们一般是通过ftok这个函数来让系统生成的。

但是为什么要key呢?因为不同的进程,shm来进行通信,所以需要标识内存的唯一性,key来区分,不是内核直接形成的,而是在用户层,构建并传入OS的。为什么要这样呢?因为两个进程是相互独立的他们具有独立性,这就意味着,我们要访问同一个物理内存是需要标识,但是由于独立性,如果让OS来标识,那么注定会有进程拿不到这个id,所以让我们在用户层传入一个自定的key值,然后两个进程传给操作系统相同的key经过运算shmget可以返回相同的id,这样就让两个进程看到同一个物理内存了。


这些都是共享内存函数中常见的标志位,但是我们往往是组合使用的。
IPC_CREAT:创建共享内存,如果目标共享内存不存在,就创建,否则打开这个已经存在的共享内存并且返回(这个进经常用来获取已经存在的目标共享内存)
IPC_CREAT | IPC_EXCL | 权限位 : IPC_EXCL 单独使用往往没有意义,所以经常是和IPC_CREAT一起使用的。如果你要创建的shm不存在,就创建它,如果已经存在,shmget返回就会出错,这个机制注定了用这个标志位,shmget成功返回,一定是一个全新的共享内存。因为共享内存也有权限,并且在这个情况下,只会返回全新的共享内存,所以往往这些标志位都是和权限位例如:0666 (0表示8进制,详情请见上位对于权限位传入的理解)。(所以这个参数常常是用来创建全新的共享内存的)
shmat函数

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址,如果不指定地址设为NULL就可以
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY,一般直接设置为0默认设置就可以了
返回值:成功返回⼀个指针,指向共享内存第⼀个节;失败返回-1
其实这个函数和malloc函数很像,因为堆的使用其实和共享内存虚拟地址空间使用是类似的.因为读写共享内存,并没有用系统调用,共享区属于用户空间,可以让用户直接使用

说明:
shmdt函数

功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数

功能:⽤于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

注意:
进程结束了,如果没有进行删除共享内存,共享内存资源会一直存在,因为共享内存的资源,生命周期随内核!!如果没有显示的删除,即使进程退出了,IPC资源依旧不会被删除。
5-6 共享内存的优缺点
优点:
共享内存是进程间通信中 ,速度最快的方式:
1 映射之后,读写,直接被对方看到
2 不需要进行系统调用获取或者写入内容
缺点:
1 通信双方,没有所谓的“同步机制”,导致数据不一致
2 共享内存,没有保护机制
6 system V信号量
6-1 并发编程,概念铺垫

6-2 信号量
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)