【Linux】进程间通信(四)消息队列、信号量与内核管理ipc资源机制
文章目录
- 一、system V消息队列
- 消息队列接口介绍
- 二、system V信号量
- 并发编程,概念铺垫
- 信号量概念和用途
- 信号量操作
- 三、内核是如何组织管理IPC资源的
一、system V消息队列
1、这是我们继管道,共享内存后遇到的第三种ipc方式,它也遵循system V标准。
2、OS在内核提供了一种链式队列的结构用来进程间通信,我们知道共享内存是用一大块内存空间进行通信,而消息队列则是通过小块数据块的方式进行通信,当一个进程向另一个进程发送数据时,数据会以链式队列的形式组织起来。
3、消息队列支持两个进程互相通信,那么进程之间就需要识别队列中结点的所属情况,需要知道结点是自己发出去的还是其他进程发给自己的,所以每个结点除了数据之外还要有类似,用来标识结点的所属情况。
4、消息队列本质就是通过一个进程向另一个进程发送有类型数据块进行通信的方式。
消息队列接口介绍
由于消息队列也是遵循system V标准,所以一部分接口和共享内存极其相似,下面和共享内存相似的接口小编就一笔带过了.
先说指令操作
查看消息对列:ipcs -q
删除特定消息对列:ipcrm -q + msgid
代码操作
创建消息队列:

删除消息队列:

消息队列属性:

因为共享内存传输数据不需要系统调用,而消息队列需要借助内核中的链式队列结构实现通信,所以消息队列进行通信需要调用系统调用,具体细节如下图所示:

二、system V信号量
并发编程,概念铺垫
1、多个执⾏流(进程), 能看到的同⼀份公共资源叫做共享资源,被保护起来的资源叫做临界资源。
2、共享资源是 “能被多个进程 / 线程访问的资源”,临界资源是 “多个进程 / 线程访问时会引发冲突(比如数据错乱、操作混乱),必须互斥访问的共享资源”—— 临界资源一定是共享资源,但共享资源不一定是临界资源。
3、为什么要有共享资源呢?这是一条逻辑链:我们有多进程协同的需求->需要进程间通信->需要多个进程看到同一份资源,那么就需要共享资源的存在。
4、共享资源存在后就需要将共享资源保护好,如果不被保护好,当并发访问共享资源时,就会出现数据访问不一致的问题,比如一方写另一方也在写,后写的就有可能将先写的数据覆盖掉,再比如一方写一方读,写端还没写完读端就开始读了,就会读取到不完整的数据。
5、保护的常⻅⽅式:互斥与同步 任何时刻,只允许⼀个执⾏流访问资源,叫做互斥。
多个执⾏流,访问临界资源的时候,具有⼀定的顺序性,叫做同步。比如现实中我们在食堂排队打饭,或者比如管道通信,当写端不写时读端就会阻塞,只有当写端先写后读端才会开始读,读端不读且写端一直在写,当把管道写满后写端就会被阻塞,只有当读端先读取数据后写端才能继续写,这里就存在先后顺序。
6、系统中某些资源⼀次只允许⼀个进程使⽤,称这样的资源为临界资源或互斥资源。
7、在进程中涉及到互斥资源的程序段叫临界区。你写的代码=访问临界资源的代码(临界区)+不访问临界资源的代码(⾮临界区)
小编这里解释一下,需要被保护起来的共享资源本身如管道文件、共享内存、消息队列就是临界资源,访问资源需要通过代码访问,那么访问临界资源的代码如read、write、msgsnd、msgrcv就是临界区。
8、有了上面的认识我们就知道了对共享资源进⾏保护,本质是对访问共享资源的代码进⾏保护(如何保护?通过同步、互斥、加锁、解锁),只要保护了临界区,就相当于间接保护的临界资源。(资源是被动的,代码是主动的,控制主动的代码,就能保护被动的资源)

信号量概念和用途
1、信号量是一种内核级的描述临界资源数量,且操作具有原子性的计数器,它是一种对资源的预订机制。
2、我们可以通过看电影这一场景来理解信号量,未来所有进程要通过临界区访问临界资源,都要先申请信号量,就像我们看电影要先买票一样,我们买的票可以类比信号量,买票预订的电影院中的某个座位可以类比将来要访问的某块临界资源,我们买了票后就算将来人没去,这个位置也要给我留着,别人不许坐,就像我们申请信号量后对应的某块临界资源就算我们不访问,这块资源也为我预留着,不会让其他人访问,这就是所谓的预定。
3、临界资源本身可以被整体使用,也可以分块使用,这里也会出现两个新名词,二元信号量和多元信号量,二元信号量就对应资源被整体使用,这是计数器就只有两个状态:1和0,1表示资源未被预定,0就表示资源已被预定,其他进程想访问这块资源时就会被阻塞,这就可以实现互斥功能:任何时刻,只允许⼀个执⾏流访问资源。多元信号量对应资源被分块使用,计数器统计的就是全部小块资源,这样可以实现同步或互斥。
4、但是这里有个问题,未来所有进程要访问临界资源,都要先申请信号量,这就意味着要让所有进程都看到同一个信号量,也就意味着信号量本身就是共享资源了,那既然信号量本身也是共享资源,要如何对信号量本身进行保护呢?这里就要引入一组新的概念了,访问信号量的两种操作:P操作(对信号量计数器做–)、V操作(对信号量计数器做++)它们是原子的,也就是它们是不会被中断的,像我们代码中实现的count++、count–这类的操作一般会经过三个步骤:将内存中的数据读到CPU寄存器里,CPU对寄存器中的数据做计算,把计算结果拷贝回内存的对应位置,这就不是原子的。P、V操作的原子性就能保证信号量本身不会发生资源冲突。
5、linux开发者为了让不同的进程看到同一份信号量资源,所以也把信号量也归纳到IPC范畴了,并且遵循system V标准,只不过用途不一样,共享内存、消息队列是以传送数据为目的,而信号量是以支持多进程间进行协同为目的(如实现同步、互斥)。
6、信号量是用来保护共享资源的——常用于共享内存,因为管道和消息队列已经在内核层面做了同步、互斥。
信号量操作
信号量英文:semaphore
创建:

删除:

信号量属性:

其他代码接口我们在多线程部分再介绍。
指令操作
查看信号量: ipcs -s
删除信号量: ipcrm -s + semid
三、内核是如何组织管理IPC资源的
首先我们先明确一组概念:
IPC 是所有进程间通信方式(大家族);
System V IPC 是遵循 System V 标准的 3 种 IPC 机制(家族分支),包括共享内存、消息队列、信号量集,当然System V远不止只有System V IPC。
然后我们介绍一下system V IPC 成员的一些共性特点:
1、所有system V资源生命周期都随内核。
2、所有system V资源都要被OS管理起来。
下面来看内核是如何组织管理IPC资源的,先看下图:

1、我们知道System V IPC的ds结构体(图中红框)中第一个成员的类型都是kern_ipc_perm(typedef为ipc_perm)类型结构体,所以内核就以一个数组:kern_ipc_perm* ipc_id_ary[N] 来组织所有ipc资源,因为ipc资源的各种ds结构体和kern_ipc_perm结构体的起始地址是一样的,能拿到kern_ipc_perm结构体的地址,再进行特定强转就能访问整个ds结构体,如 (msg_queue*)ipc_id_ary[5]。
2、上面的组织管理形式本质就是C语言实现多态,kern_ipc_perm是基类,各种ds结构体就是子类。这样的好处就是可以统一通过一个数组来管理所有ipc资源。
3、我们通过系统调用:XXXid = XXXget()获取到的各种id变量其实就是ipc_id_ary的数组下标。
4、共享内存的结构体shmid_kernel中有一个struct file*类型成员shm_file,可以通过它拿到内核文件缓冲区,所以共享内存是通过下面的逻辑实现从物理内存中映射到虚拟地址空间的:
当我们创建共享内存时,首先在内核里把共享内存结构体shmid_kernel、文件对象(包含缓冲区)建立好,当我们其中一个进程挂接共享内存时就会创建vm_area_struct指向对应的文件,然后就可以将虚拟地址(vm_area_struct的start和end)和物理地址进行映射,此时就可以通过虚拟地址访问共享内存了。(动态库、new/malloc都是用的和共享内存一样的映射思路)
以上就是小编分享的全部内容了,如果觉得不错还请留下免费的赞和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

