目录
🎞一、共享内存---shm
1.1shmget
①ftok得到一个key
②shmget得到shm
③shm的性质
1.2 shmctl
①ipcrm
②shmctl
1.3shmat&&shmdt
①shmat
②shmdt
1.4通过shm完成进程间通信
1.5shm的特点
①shm共享内存的优点
②shm的缺点
1.6shm的内核结构
①内核
②shm大小的问题
🎞二、消息队列&&信号量
2.1消息队列
2.2信号量
🎞一、共享内存---shm
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
共享内存的示意图:
共享内存是OS为了方便本地进程间进行通信而专门在物理内存开辟的一块供进程间通信的区域。
通过上图我们看到共享内存开辟一块空间后,两个进程得到它的地址,这样就能看到同一份资源,从而对资源进行操作。
1.1shmget
接口参数注释:
size: 想要申请的共享内存空间的大小。
shmflg:通常设置为两种选项。选项的本质是宏,通过位图的方式,传二进制的方式传进去的。类似于open接口的flags选项。
key:唯一性标识共享内存。以便进程找到相同的共享内存。
shmflg的常用选项是IPC_CREAT和IPC_EXCL
IPC_CREAT:如果想创建的指定的共享内存不存在,就创建;如果存在,就获取它。也就是说这个选项的作用在于前面创建了指定共享内存后就不必再创建,直接获取即可。
IPC_EXCL:这个选项无法单独使用。它是配合IPC_CREAT使用的。IPC_CREAT | IPC_EXCL;如果不存在就创建共享内存;如果存在就出错返回。
IPC_EXCL选项的意义在于如果创建成功了,那么一定是一个新的共享内存。如果出错了,说明已经存在一个共享内存(shm)。
key参数的作用是唯一性标识shm。为什么要用它来唯一性标识呢?因为首先它是让我们要进行通信的进程通过key找到相同的shm。而且这里还有一个概念就是OS中一定可能同时存在很多的shm。一个shm一个唯一的key不仅方便我们查找也方便OS管理shm。
①ftok得到一个key
借助ftok接口我们可以得到一个key。
这个函数的意义在于将一个路径名和项目的标识符转换成一个system V的IPC key。
传进来的字符串pathname和字符数据proj_id,通过算法转化出一个key。
那么我们只需要通过ftok()函数,传递相同的pathname和proj_id,那么就能得到相同的key,通过key就能找到相同的shm。
shm_client和shm_server调用同一函数,函数的参数都一样,所以生成的key值也是一样的,不过这个key值具体是多少不重要。关键是它能唯一性标识最重要。
那么得到key之后,我们就可以调用shmget()函数来得到shm了。
②shmget得到shm
因为是一个进程创建,另一个进程获取,我们调用shmget函数的前两个参数,key值和shm的大小都是一样的,无非就是第三个参数选项的不同。所以我们可以封装成函数。
shm_server创建shm,shm_client获取shm。运行成功后,我们得到一个shmid。这个shmid是该共享内存段的标识码。那么问题来了,我们之前生成过key来标识shm,这里的shmid再来标识是否多此一举呢?
③shm的性质
这里要回到OS层面来分析。OS是如何管理shm呢?先描述再组织。shm==物理内存块+相关属性。每申请一块shm,除了申请额定大小的空间,还要生成一个管理对象,记载它的相关属性以便管理。在OS层面,每个shm都有一个独特的key来标记,方便它管理。这个key是要设置进shm的描述属性中的。用来标识该shm在内核中的唯一性。
而shmid则是用户层面方便我们对shm进行管理的!这里shmid和key的服务对象不同但都是用来标识shm,这就类似于文件描述符fd和inode的关系!
那么谈完key的属性。我们再来看一下shm资源有什么属性:
当我们已经创建了shm,再次运行时,就会告诉我们文件已经存在。这里我们注意到进程已经结束,而IPC资源依旧被占用!
那么这里要介绍指令ipcs -m 查看shm资源。
我们发现OS中确实仍旧存在shm资源。shm的生命周期是随着OS的,而不是随着进程的!
这种特性是system V所特有的,我们的system V的共享内存,消息队列和信号量都有这样的特点。
ipcs指令用来查看system V资源:
其中消息队列查看加上-q选项。信号量查看加上-s选项。
1.2 shmctl
①ipcrm
那么既然进程结束仍旧存在,如何删除呢?
指令:ipcrm -m shmid
ipcrm -m指令加上指定资源的shmid即可删除。
②shmctl
上面是指令级的操作,代码层面要使用OS提供的接口shmctl
shmctl用来对shm进行控制,包括删除。
参数:
shmid:由shmget返回的共享内存标志码;
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构。
返回值:成功返回0,失败返回-1.
cmd具体的三个命令:
IPC_STAT:把shmid_ds结构中的数据设置为shm的当前关联值。
IPC_SET:在进程有足够权限的前提下,把shm的当前关联值设置为shmid_ds数据结构中给出的值。
IPC_RMID:删除shm
那么shmid_ds是什么结构呢?
shmid_ds内部维护着shm的相关属性。如果我们要得到shmid_ds结构维护的属性,需要使用IPC_STAT指令并把相关结构的指针给buf。通过buf即可访问。
删除shm:
这样我们的代码就变成了创建成功shm后,5s后删除它,可以通过shell窗口监视:
1.3shmat&&shmdt
①shmat
我们已经完成了shm的创建和删除,接下来使用shmat函数来让我们的进程得到共享内存的地址。
shmid:想和哪个shm关联。
shmaddr:想把shm映射到哪一个地址空间中,一般设置为nullptr
shmflg:默认读写,设置为0.
返回值:成功返回一个指针,指向shm的地址;失败则返回-1.
创建成功,5s后,挂接成功,再过5s,成功删除:
在演示中,我们看到,并没有如我们所愿挂接成功,而是报错:permisson deny。美没有权限。
所以,在创建时我们要给上shm的权限:
给上权限0600,只有拥有者有权限。
再来看一下:
可以看到attach成功,并且可以看到,挂接成功时的属性。
②shmdt
当不再使用的时候,不要直接删除而是去关联,去关联并不是删除shm,二而是把进程和shm的映射关系去除:
shmaddr:shmat()的返回值。
返回值:成功就是0,失败就是-1.
谁创建谁删除时是设计的一个理念。shm_server负责创建和删除。
通过演示,我们可以看到成功试验。
1.4通过shm完成进程间通信
1.5shm的特点
①shm共享内存的优点
shm的优点是它是所有进程间通信中,速度最快的!可以大大地减少数据的拷贝次数。
为什么shm可以减少拷贝次数呢?
上面同样的实现通信,pipe需要创建缓冲区,而shm可以不设缓冲区,直接对shm进行写入和读取,这样就减少了两次拷贝。在不考虑输入输出流的情况下,只有两次拷贝,相比于pipe的四次拷贝少了两次。
②shm的缺点
我们更改一下代码,通过演示更方便我们看出它的特点。
发送信息变成5s发送一次,读取变成每隔1s读取一次,换句话说就是发送慢于读取。
通过演示可以看到,server读取的信息编号都是1.
这一点和pipe大为不同,在pipe中当读取过pipe中的信息后,如果pipe没有新的消息写入,就不会读取而是等待输入端写入信息。而shm则是读取一次消息后,没有新的消息就会继续读取原来旧的消息。
所以说shm的缺点就是:
shm不对数据进行同步和互斥的操作,没有对数据做任何保护!
而我们要想避免这种现象,需要信号量或者互斥锁(多线程)
1.6shm的内核结构
①内核
通过接口shmctl()可以查看shm的内核数据结构。
我们看到内核数据结构中为什么没有key的信息呢?这是因为OS对它进行了封装:
我们可以验证一下是否ipc_perm结构中的__key就是我们的key呢?
我们看到确实就是我们初始传输的key。
②shm大小的问题
一般shm的大小,建议设置为4KB的整数倍。
因为系统分配shm是以4KB为单位的!---内存划分内存块的基本单位是4KB,一个4KB大小的空间,内存当中是一个Page。
那么如果我们申请空间4097个字节,那么内核申请的空间大小会向上取整,申请4KB*2大小的空间。那么我们试着申请4097B大小的空间。
但是发现shm的大小好像并不像我们所说的申请了8KB的 空间,而是4097.为什么呢?
这是因为内核虽然给我们申请了8KB的空间,但是我们之申请了4097B的空间大小,所以内核只给我们提供8KB中的4097B大小的空间供我们使用。
🎞二、消息队列&&信号量
2.1消息队列
因为system V版本因为它只能在本地进程间通信的特性,渐渐不被使用了。所以只简略介绍消息队列和信号量。
消息队列的使用模式是:写端以数据节点的方式将数据放到消息队列当中,而读端就可以从消息队列中拿走数据。而消息队列可以两端互为读写,因为是在同一个消息队列中,为了区分哪端为读哪端为写,有一个type类型,用来标识这个消息是由哪一端写入的。
比如由一端发送的数据都设为0,另一端发送的数据都设为1,这样标识数据是由哪一端发送的。
它的接口设计和shm的接口是十分相似的。
①msgget
②msgctl
③msgsnd
④msgrcv
用于读取消息队列。
2.2信号量
信号量的概念有点抽象。它的本质是一个计数器,通常用来表示公共资源中,资源数量有多少。
首先来说明一下什么是公共资源:
而被保护的公共资源称为临界资源。具体的保护涉及到了互斥和同步的原理。信号量用来表示公共资源中资源数量的多少,要完成进程间通信,不仅需要不同进程看到相同的公共资源,信号量本身也需要让不同的进程看到资源数量的多少。信号量也要打破进程间独立,所以它也被划分到进程间通信的范畴中!
为什么要有信号量呢?
我们平时想看电影,需要买票,买票的本质就是对放映厅中的作为进行预订。而我们想要某种资源时,也可以通过信号量预订。
①semget
sems:申请信号量的个数。
semflg:选项IPC_CREAT 和 IPC_EXCL
②semctl
semid:信号量的identifier。
semnum:信号量的下标。
我们看到IPC资源的组织方式,接口相似度非常高,尤其是获取与删除。
并且我们可以观察一下内核数据结构:
system V版本的进程间通信标准是非常统一的。其中都使用了ipc_perms的结构体来维护__key等属性信息。
那么这样做有什么好处呢?
我们可以创建一个指针数组,每一个成员都是ipc_perm结构体的地址。
因为结构体的第一个成员的地址,在数字上和结构体对象本身的地址数字是相等的!换句话说我们指针数字的每一个成员都是一个system V标准的内核数据结构体的地址!只不过大小只是ipc_perm结构体的大小。但是我们可以通过强转类型,比如将perms[0]强转为struct shmid_ds*类型。就可以通过指针数组的内容强转拿到内核数据结构体的地址,这样就可以访问它的其他内容!