Linux进程间通信

●个人主页:你帅你先说.
●欢迎点赞关注收藏
●既选择了远方,便只顾风雨兼程。
●欢迎大家有问题随时私信我!
●版权:本文由[你帅你先说.]原创,CSDN首发,侵权必究。

为您导航

  • 1.进程间通信介绍
    • 1.1进程间通信目的
    • 1.2进程间通信发展
    • 1.3进程间通信分类
  • 2.管道
    • 2.1什么是管道
    • 2.2站在文件描述符角度-深度理解管道
    • 2.3创建匿名管道
    • 2.4创建命名管道
    • 2.5System V进程间通信
    • 2.6system V信号量

1.进程间通信介绍

在系统中,进程之间可能会存在特定的协同工作的场景。一个进程要把自己的数据交付给另一个进程,让其进行处理。两个进程要互相通信其实并不容易,因为进程具有独立性,想要通信必须先看到一份公共资源(一段内存),而这个资源不可能属于任何一个进程,这样就违背独立原则,而是由操作系统管理,所以进程间通信的本质:其实是由OS参与,提供一份所有通信进程都能看到的公共资源。

1.1进程间通信目的

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

1.2进程间通信发展

  • 管道
  • System V进程间通信
  • POSIX进程间通信

1.3进程间通信分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

2.管道

2.1什么是管道

管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

在OS中,父进程创建出子进程,它们共享同一份代码和数据,直至发生写时拷贝,才各自拥有一份数据,不知道大家有没有想过,当父进程创建出一个文件时,子进程是否也会创建出一个文件,实际上并不会,因为父子进程是共享代码和数据,而文件不属于进程,所以父子进程会共同访问这个文件,当父进程向文件写入数据时,数据会保存在文件的内核缓冲区,此时子进程可以读取到这段数据,这样进程间就建立起了通信,这就是管道。

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

Linux进程间通信_第1张图片
Linux进程间通信_第2张图片
Linux进程间通信_第3张图片

图示的管道就是刚刚我们所说的文件的内核缓冲区。管道是一个只能单向通信的通信信道

2.3创建匿名管道

#include 
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

匿名管道特点

只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
管道提供流式服务。
一般而言,进程退出,管道释放,所以管道的生命周期随进程。
一般而言,内核会对管道操作进行同步与互斥。
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。

Linux进程间通信_第4张图片

管道是有大小的,是64KB
为什么写满64KB后,不能再写了,虽然我们写满了,但我们可以覆盖数据,这样可以继续写。因为我们需要给读的人来读,如果写满就覆盖了,那么读的人读到的数据就不完整了。当管道满了后,当读的人读了一定的数据后(在Linux下是4KB),管道会继续写入,所以管道有同步机制
Linux中是这样描述这种现象的

当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。

在使用管道进行读写操作时,我们通常会遇到以下四种情况:
1.读端不读或读的慢,写端要等读端
2.读端关闭,写端收到SIGPIPE信号直接终止
3.写端不写或写的慢,读端要等写端
4.写端关闭,读端读完pipe内部的数据然后再读,会读到0,表明读到文件结尾。

2.4创建命名管道

  • 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件
  • 命名管道的数据不会刷新到磁盘,为了效率

指令

mkfifo 管道名称

Linux进程间通信_第5张图片
成功创建则返回0,失败返回-1。
这是一个以p开头的文件
Linux进程间通信_第6张图片
我们之前说过,进程是具有独立性的,所以进程间通信的成本是比较高的,想要让进程间通信就必须让不同的进程先要看到同一份资源。
接下来我们用代码来实现进程间的通讯。
Linux进程间通信_第7张图片
Linux进程间通信_第8张图片
Linux进程间通信_第9张图片
这个时候就可以进行两个进程间的通讯了。
Linux进程间通信_第10张图片
学了匿名管道和命名管道,不知你是否有疑问为什么我们之前的pipe叫做匿名管道,为什么现在的fifo叫做命名管道呢?
为了保证不同的进程看到同一个文件,所以命名管道必须有名字。
而匿名管道是通过父子继承的方式,看到同一份资源,不需要名字来标识同一个资源。

2.5System V进程间通信

在OS层面专门为进程间通信设计的一个方案。
System V 进程通信,一定会存在专门用来通信的接口(system call)
System V的IPC资源的生命周期是随内核的。(即只能通过程序员手动释放或OS重启)

共享内存

1.通过某种调用,在内存中创建一份内存空间
2.通过某种调用,让参与通信的进程"挂接"到这份新开辟的内存空间上。
满足以上两个条件的就是共享内存

需要知道的是:
1.共享内存在系统中可能存在多份。
2.共享内存有标识唯一性的ID,方便让不同的进程能识别同一个共享内存资源。
创建共享内存
Linux进程间通信_第11张图片
Linux进程间通信_第12张图片

int shmget(key_t key,size_t size,int shmflg)
//size的大小建议是4KB
//若申请不到4KB,则分配4KB,若申请超过4KB,则向上取整。例如申请4097个字节,即4096(4KB)+1,操作系统会给你8KB的空间。

先来说说shmflg标志位
在这里插入图片描述
如果单独使用IPC_CREAT,或者flag为0:不存在则创建一个共享内存,若存在则直接返回当前已经创建好的共享内存。
IPC_EXCL(单独使用没有意义)
IPC_CREAT | IPC_EXCL:如果不存在共享内存,则创建。如果已经有了共享内存,则返回出错。(如果创建成功,我得到的一定是一个最新的,没有被使用过的共享内存)

再来说说参数key,我们刚刚说了共享内存有标识唯一性的ID让不同进程看到同一份资源,这个key就是这个ID,这个ID需要我们自己手动设置,但实际中我们不太方便自己去设定,而是用ftok函数帮我们去设定
Linux进程间通信_第13张图片

key_t ftok(const char* pathname,int proj_id);
//pathname:自定义路径名
//自定义项目ID

控制共享内存
Linux进程间通信_第14张图片

shmctl(int shmid,int cmd,struct shimd_ds * buf);
//cmd是控制的命令

Linux进程间通信_第15张图片

关联/取消关联共享内存
Linux进程间通信_第16张图片
若关联成功函数会返回一个地址,我们所学的所有的函数返回的地址指的都是虚拟地址。
shmdt并不是释放共享内存,而是取消当前进程和共享内存的关联。
删除共享内存

ipcrm -m shmid

:
key值只是用来在系统层面进行标识唯一性的,不能用来管理共享内存
shmid:是OS给用户返回的id,用来在用户层进行关系内存管理。
代码实现共享内存的过程
Linux进程间通信_第17张图片
Linux进程间通信_第18张图片
Linux进程间通信_第19张图片
Linux进程间通信_第20张图片
我们发现key值都是一样的,说明两个进程访问的都是同一块内存

我们刚刚在通过共享内存进行通信时并没有用到read()或write(),所以共享内存一旦建立好并映射进自己进程的地址空间,该进程就可以直接看到该共享内存,就如同malloc的空间一般,不需要任何系统调用接口。(共享内存是所有进程间通信中速度最快的)共享内存不提供任何同步或者互斥机制,需要程序员自行保证数据的安全。
我们来看看共享内存的内核数据结构
Linux进程间通信_第21张图片
我们来看看消息队列的内核数据结构
Linux进程间通信_第22张图片

再来看看信号量的内核数据结构
Linux进程间通信_第23张图片
你们会发现这三个的数据结构的第一个类型都是完全一样的。这是为什么?
因为所有的System V标准的ipc资源都是通过数组组织起来的,xxxid_ds 结构体的第一个成员都是struct ipc_perm类型的。
内核中会有一个struct ipc_perm* ipc_id_arr[64]的指针数组,有人会有疑惑,类型不一致怎么存?可以通过强制类型转换,如下
ipc_id_arr[0] = (struct ipc_perm*)&shmid_ds
若要访问这个结构体的其它内容,如下
(shmid_ds*)ipc_id_arr[0]->其它属性。
这个操作似乎有些熟悉,熟悉C++的人就会知道这有点像C++的切片技术。

2.6system V信号量

什么是信号量:
管道(匿名和命名)、共享内存、消息队列都是以传输数据为目的的,信号量不是以传输数据为目的的,而是通过共享"资源"的方式来达到多个进程的同步和互斥的目的。
信号量的本质:是一个计数器,类似于count++/--这样计数。
什么是临界资源:凡是被多个执行流同时能够访问的资源就是临界资源!同时向显示器打印、进程间通信的时候、管道、共享内存、消息队列等都是临界资源。
什么是临界区:进程的代码可是有很多的,其中,用来访问临界资源的代码,就叫做临界区。
什么是原子性:一件事情要么不做,要么就做到最好,没有中间态,就叫做原子性。

喜欢这篇文章的可以给个一键三连 点赞关注收藏

你可能感兴趣的:(Linux,运维,linux)