【Linux进程通信】四、System V IPC
Ⅰ. 前言
这里我们介绍的这种通信方式也就是 system V IPC
在我们后面的使用和日常见到的其实并不多,但是包括其中的共享内存、消息队列、信号量,我们如果了解共享内存其原理的话,能够更好的帮助我们了解之前我们学过的进程地址空间的概念!
至于信号量,我们后面讲多线程的时候会再次讲,我们只引入一些概念如互斥等等,而消息队列我们就只说说其原理,不会细讲!
Ⅱ. 认识共享内存
一、共享内存的原理
之前我们学过管道通信,分为匿名管道和命名管道,匿名管道通过父子进程的属性继承原理来完成父子进程看到同一份资源的目的,而命名管道则是通过路径与文件名来唯一标识管道文件,来让不同的进程之间进行通信!
而共享内存也是一样,我们得让不同的进程看到同一份资源,但是这次我们不是使用继承还是文件名路径来标识,而是通过在内存中的一段空间:共享内存区中申请一段空间,并且进程可以通过获得一个唯一的标识 ID 来获得这段共享内存的位置,当多个进程同时获得这段共享内存的时候,我们就称它们通过共享内存看到了同一份资源!这里面有许多的细节,我们一一来解释!
在 Linux
中,首先我们假设这里有两个进程分别被调度,那么它们就有各自对应的进程控制块 PCB
和地址空间 mm_struct
并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元 MMU
进行管理,由于两个进程拥有独立的数据结构,所以我们可以知道其是有独立性的!如下图所示:
但是这里就要一个问题了!既然两个进程的之间的数据结构是相互独立的,我们学过进程地址空间也知道,如果它们指向了同一段物理空间,那么其中一方进行修改,是会发生写时拷贝的,并不会影响另一方,那么这样子我们如何进行通信呢 ❓❓❓
所以为了让两个毫不相干的进程能看到同一份资源,操作系统会做以下几个工作:
- 在物理内存当中申请一段共享内存空间
- 将创建好的共享内存空间通过页表映射到进程的进程地址空间(这个过程叫做挂接)
- 不同的进程通过操作各自的进程地址空间中的该段共享内存空间的虚拟地址,来操作共享内存
- 如果某个进程不想通信了,那么就将该进程与共享内存的映射取消掉(去关联),如果需要的话再将共享内存释放掉(看是否其他进程还在通信)
那么就会有人问,调用 malloc
函数不也能在内存中开辟一段空间并且和进程之间映射起来吗 ❓❓❓
答案肯定是不行的!因为我们 malloc
出来的空间,只是属于某个进程的,而进程之间具有独立性,所以其他进程是压根看不到这份资源的,就算看到了,那么也不能进行通信,因为存在写时拷贝,而共享内存是特殊的,是运行不同进程之间共同操作的一段空间!
二、linux中共享内存的数据结构
在 linux
中,共享内存也是需要被管理的,就像我们的进程控制块、文件描述符等等都是遵循一个原则:先描述、再组织!
在 Linux
内核中,每个共享内存都由一个名为 struct shmid_kernel
的结构体来管理 (shmid
表示共享内存的 id
),而且 Linux
限制了系统最大能创建的共享内存为 128
个。通过类型为 struct shmid_kernel
结构的数组来管理,其中 struct shmid_ds
结构体用于管理共享内存的属性信息,而 shm_segs数组
用于管理系统中所有的共享内存。
另外 struct shmid_ds
结构体中存在另一个结构体 struct ipc_perm
,它存储确定执行 IPC
操作的权限所需的信息!
这几个结构体如下:
代码语言:javascript代码运行次数:0运行复制struct shmid_kernel
{
struct shmid_ds u;
/* the following are private */
unsigned long shm_npages; /* size of segment (pages) */ // 表示共享内存使用了多少个内存页
pte_t *shm_pages; /* array of ptrs to frames -> SHMMAX */ // 指向了共享内存映射的虚拟内存页表项数组
struct vm_area_struct *attaches; /* descriptors for attaches */ //
};
static struct shmid_kernel *shm_segs[SHMMNI]; // SHMMNI等于128
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */ // 该空间的所有权等属性,其中包括了用来唯一标识的key
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 */ // 创建该空间的进程pid
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */ // 最后一个关联或者挂接该空间的进程pid
unsigned short shm_nattch; /* no. of current attaches */ // 当前空间的挂接数
......
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */ // 这个就是用来标识shmid唯一性的key
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effevtive GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
}
在我们讲接口之前看到这些可能会疑惑,但是在后面讲完接口调用的时候,这里对我们来说比较重要的就是这个 shmid_ds
以及 ipc_perm
中的 __key
!
三、理解共享内存的概念
从上面的讲解我们可以得到:通过让不同的进程,看到同一个内存块且进行通信的方式,叫做共享内存!
下面我们提出几个概念,为了后面我们引出共享内存申请、调用等等的内容做铺垫:
- 进程间通信,是程序员们专门设计来实现
IPC
的,这和malloc
虽然理论和思想上差不多,但是却有完全不一样的作用,进程间通信主要是为了解决进程间共享的内存问题。 - 共享内存是一种通信方式,所有想通信的进程都可以使用。
- 操作系统中可能会同时存在很多的共享内存,就像我们以前每家每户都有一部专属电话来沟通一样。
那么第一个概念其实就是想说,既然专门设计了进程间通信,那么肯定底层会有对应的接口供我们使用!
而后面两个概念就是为了帮助我们理解这些接口参数的概念!下面我们来介绍一下这些接口!
Ⅲ. 共享内存的使用
首先我们得知道共享内存的一些特性:
- 共享内存是以 覆盖写 的方式
- 共享内存的 声明周期随操作系统,而不是随进程(也就是说不需要使用的时候需要手动释放,不然会造成内存耗尽问题,如何释放我们下面会说)
接下来我们先来看看在 linux
下如何查看对应的 共享内存空间
、消息队列
以及 信号量
!方便我们调用接口的时候查看!
⭐ 共享内存的查看 – ipcs指令
ipcs
指令的作用是报告进程间通信设施的状态,包括共享内存、消息队列以及信号量等等!
下面我们来看一下 ipcs
的指令选项,这里只使用几个比较常见的,其他的选项可以参考下面 ipcs -help
中的!
① 列表说明
key
:用来传递给 shmid 的编号msqid
:消息队列的编号shmid
:共享内存段的编号semid
:信号量数组的编号owner
:创建该空间的用户perms
:权限nattch
:挂接数,也就是连接到共享内存的进程个数status
:共享内存段的状态(是否有进程关联等等)bytes
:创建的大小used-bytes
:消息队列已使用的大小nsems
:对应信号量数组中信号量的个数
② 查看帮助:ipcs -help
③ 三类资源查看方式
ipcs -m
:单独查看共享内存段ipcs -q
:单独查看消息队列ipcs -s
: 单独查看信号量数组ipcs -a
或ipcs
: 查看所有的资源(设施)
④ 资源选项和输出选项可以搭配使用
这里以 -c
显示创建者和拥有者为例:ipcs -c
和 ipcs -c -s
,其它选项如 -t
、-p
、-l
、-u
、-b
也是同理的!
⑤ 通过选项 -i 打印资源的详细信息
使用 -i
选项的时候要配合 semid
或者 shmid
一起使用,而不能一起单独使用!
⭐ 共享内存段的删除 – ipcrm指令
本来这个共享内存段的删除内容是要在后面说的,这个顺序会比较合理一点,但是还是觉得不卖关子,我们先把删除解决了,并且要提一下为什么要进行删除操作!
之前我们说过一个进程如果不想使用该共享内存段了,那么就得将该进程与该共享内存的映射去掉,也就是去关联,这是为了防止我们后面做了不当的操作影响到该共享内存中的其它进程通信!
除此之外,共享内存段的生命周期是随操作系统的,而不是随进程的(System V
版本的通信生命周期都是随操作系统的),也就是说就算没有进程指向该共享内存段,这个内存段也是会存在的,那么如果我们不手动对其释放,那么就要等到操作系统关闭的时候才能关闭,而共享内存段是占有内存大小的,所以这极有可能成为内存耗尽的隐患!所以当我们不想使用该段空间的时候,我们就得手动将其释放掉,而这就要借助到 ipcrm
指令了!
ipcrm -m shmid编号
注意共享内存只有在当前映射链接数为0时才会被删除释放!
那么这里可能会有人问,为什么不是删除对应的 key
编号而是 shmid
编号呢 ❓❓❓
这里我们就得搞清楚 key
和 shmid
的关系:
key
只是用来在OS
层面上唯一标识的,不能用来管理共享内存的;shmid
是OS
给用户用来标识共享内存段的id
,用来在用户层进行共享内存段的管理的!
因为我们是用户,所以我们是在用户层进行操作,只能通过 shmid
编号来进行删除!
发布者:admin,转转请注明出处:http://www.yc00.com/web/1748194787a4745752.html
评论列表(0条)