Linux——进程间通信——管道与共享内存

Posted 一棵灬胡杨树

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux——进程间通信——管道与共享内存相关的知识,希望对你有一定的参考价值。

文章目录

进程间通信是什么?

操作系统为用户提供的用于实现进程之间进行通信的方式。

Notes:进程之间是无法直接通信的,因为每一个进程都有自己独立的虚拟地址空间,所以进程具有独立性,无法直接通信。

进程间通信方式种类:管道、共享内存、消息队列、信号量

一、管道

特性

  1. 半双工通信(可以选择方向的单向通信)
  2. 管道的生命周期随进程的,进程退出,管道的缓冲区随即被释放。

本质

管道的本质就是在内核中开辟的一块缓冲区(内核中的一块内存),内核的访问只能通过系统调用接口。每个用户都有一个独立的虚拟地址空间,但是内核是共享的。

原理

多个进程通过访问同一块内核中的缓冲区实现通信。

匿名管道

  1. 匿名管道:缓冲区没有标识符,也就是说匿名管道没有办法被其他进程找到,只能用于只有亲缘关系的进程间通信(父子进程),原因就是匿名管道么得标识符,也就没有办法被其他进程找到,只能通过子进程拷贝父进程的方式获取操作句柄来实现。

Notes:在Linux下,一切皆文件,所有的东西都是当作文件一样进行操作,通过IO操作完成对管道的访问。

  1. 匿名管道函数:

int pipe(pipefd[2]);这个函数形参列表是一个数组,因为管道是一个半双工通信,所以不能确定到底是读还是写,其中pipefd[0]是读,pipefd[1]用于写。这两个操作句柄不能同时使用。

返回值:成功返回0,失败返回-1。

管道的读写特性:

  1. 若管道中没有数据,read会阻塞;若管道中的数据写满了,则write会阻塞。
  2. 所有的管道的读端被关闭,则继续write的话会触发异常导致进程奔溃退出。
  3. 所有的管道的写端被关闭,则继续read则会读完数据后返回0(返回0就是告诉你,这个管道没有人会给你写入数据了,继续阻塞也没有什么意义)不再阻塞。
  4. 管道提供字节流传输服务:有序的、可靠的、基于连接的流式传输。

匿名管道的模拟实现

管道符号: | 连接两个命令,将前面命令的打印结果传输给后面的命令进行处理。模拟实现:ps -ef | grep ssh

上述命令中:ps -ef的含义是:获取所有的进程信息,然后输出到标准输出文件。grep ssh的含义是:从标准输入读区数据,进行字符串匹配过滤。如下图:

命名的运行都是创建子进程,让子进程程序替换实现程序功能:

  1. 在shell进程中,创建管道,创建两个子进程,一个子进程把标准输出重定向到管道写入端,一个程序替换ps;
  2. 一个子进程把标准输入重定向到管道读取端,替换grep,需要将ps进程数据传输给grep进程–匿名管道。


命名管道

  1. 命名管道缓冲区具有标识符,可用于同一主机上任意的进程间通信。
  2. 命名:mkfifo test.fifo
  3. 命名管道的标识符就是一个可见于文件系统的管道类型文件,多个进程通过打开同一个管道文件,访问同一块内核中的缓冲区实现通信。
  4. 命名操作:mkfifo + filename :创建一个命名管道文件(文件只是一个标识符,创建一个管道文件,不代表在内核中就有缓冲区,只有当进程中要访问这个缓冲区,才会被开辟)
  5. 函数操作:int mkfifo(const char *pathname, mode_t mode);
    Pathname:文件名称;Mode:创建权限;返回值:成功0,失败-1。
  6. 打开特性:
    若是以只读方式打开管道文件则会阻塞,直到管道被其他进程写的方式打开。
    若是以只写方式打开管道文件则会阻塞,直到管道被其他进程以读的方式打开。

总结:管道到底是什么?

答:本质:内核中的一块缓冲区。原理就是多个进程通过访问同一块缓冲区实现数据传输。
分类:匿名管道、命名管道。匿名管道只能适用于具有亲缘关系的进程间通信,命名管道可以用与同一主机上任意进程之间的通信。
特性:可以选择方向的单向通信。管道提供字节流传输服务(有序的可靠的基于连接的一种流式传输,可靠——>“先进先出”,基于连接——>所有读端关闭,write异常,所有写端关闭,则read返回0不再阻塞)。

Notes:
管道自带同步与互斥:
互斥:通过同一时间对临界资源(大家都能访问到的资源,可以理解为公共资源)的唯一访问实现访问操作安全。临界区:对临界资源访问的这段代码区域
同步:通过一些条件判断让进程对临界资源的访问更加合理有序。
互斥的体现:对管道进行写入操作大小不超过一个宏–>PIPE_BUF(4096个字节)保证操作的原子性。
同步的体现:若管道没有数据,则read阻塞;若管道数据满了,则write阻塞。
管道的生命周期随进程—>不人为干预,所有打开管道的进程退出了,则管道资源被释放。
匿名:Int pipe(int pipe[2]);
命名: int mkfifo(const char *pathname, mode_t mode);

二、共享内存:用来实现进程间的数据共享

本质就是一块物理内存

原理:开辟一块物理内存空间,多个进程将同一块内存通过页表映射到自己的虚拟地址空间,通过自己的虚拟地址空间直接进行访问。
操作流程:
1.创建或打开共享内存
2.将共享内存映射到进程的虚拟地址空间
3.通过映射的虚拟地址进行各种内存操作
4.解除映射关系
5.删除共享内存
共享内存的生命周期随内核,不与进程同进退。

操作接口:

#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
Key:标识符,多个进程通过相同的标识符打开同一块共享内存
Size:创建时候所开辟空间大小,如果已经内存存在,则被忽略。(以内存页为单位,默认是:4096个字节)
Shmflg:打开方式+创建权限(IPC_CREAT | IPC_EXCL | 0664)
返回值:成功:返回非负整数–>操作句柄;失败返回-1。

void *shmat(int shmid, const void shmaddr, int shmflg);
Shmid:shmget返回的操作句柄
Shmaddr:映射地址–通常为NULL;
Shmflg:映射成功后的访问方式:SHM_RDONLY-只读;0-读写
返回值:成功返回映射后的首地址,失败返回(void
)-1;

int shmdt(const void *shmaddr); 解除映射关系。Shmaddr:映射后的首地址。
返回值:成功0,失败-1;

int shmctl(int shmid, int cmd, struct shmid_ds *buf);删除共享内存
Shmid:shmget返回的操作句柄,cmd:操作类型–IPC_RMID–标记共享内存为被销毁状态。Buf:同于设置或获取共享内存信息,不使用则置null,返回值对于IPC_RMID则是成功0,失败-1;

共享内存总结

本质原理:开辟一块内存空间,多个进程将同一块内存映射到虚拟地址空间,通过自己虚拟地址进行访问,进而实现进程间通信。
特点:最快的进程间通信方式;共享内存的生命周期随内核
注意事项:共享内存是一个临界资源,对共享内存的操作需要注意安全问题。
共享内存通过虚拟地址直接访问虚拟内存实现数据共享,相对于其他方式需要将数据拷贝到内核,使用时拷贝到用户态,少了两次数据拷贝操作。

有个小练习:共享内存代码实现


运行上述两个代码的时候,可以实现使用共享内存实现进程间通信。

Notes:
ipcs 查看进程间通信资源/ipcrm 删除进程间通信资源
-m 针对共享内存的操作
-q 针对消息队列的操作
-s 针对消息队列的操作
-a 针对所有资源的操作

以上是关于Linux——进程间通信——管道与共享内存的主要内容,如果未能解决你的问题,请参考以下文章

Linux进程间通信

Linux进程间通信

Linux进程间通信

linux进程间通信之管道

Linux入门进程间的通信

Linux入门进程间的通信