1.system v引入了三种高级进程间的通信机制:消息队列、共享内寸和信号量
ipc对象(消息队列、共享内存和信号量)存在于内核中而不是文件系统中,由用户控制释放,不像管道的释放由内核控制
ipc对象通过其标识符来引用和访问,所有ipc对象在内核空间有唯一性标志id,在用户空间的唯一性标识符称为key
linux ipc 继承自system ipc
2. system v ipc对象的访问
ipc对象时全局对象,可用ipcs,ipcrm等命令查看或删除
ipcs -q: 只显示消息队列。
ipcs -s: 只显示信号量。
ipcs -m: 只显示共享内存。
ipcs –help: 其他的参数
3.头文件及函数介绍
头文件:
在使用三种ipc机制的时候,我们肯定是通过系统调用,而这些函数所需要的头文件需要首先搞清楚。system v的ipc操作要用到的头文件有:
#include//公共头文件,声明了key_t类型 #include //公共头文件 #include //消息队列函数的头文件 #include //信号量函数的头文件 #include //共享内存函数的头文件
这里用到的头文件都是在 sys目录下的。前面两个是公共的头文件,也就是说三种ipc机制都有用到,而后面三个是和具体的ipc机制相关的,通过头文件的名称我们能发现它们同样满足前面所说的缩写。 ftok()函数: 三个ipc机制会用到大量的函数,不同ipc所用到的函数不同但是有一个是相同的——ftok() unix系统中有个重要的概念叫做:万物皆文件。在很多ipc机制中的操作都是针对文件描述符(简称 fd)的,然而system v却不同,它没有对fd进行操作,而是针对 ipc对象的id来操作的,而这个id(标识符)又是通过key(键)来生成的。 三种ipc有各自的函数来生成id,但是它们所利用的key却都由函数ftok()生成,看一下函数声明:
#include#include key_t ftok(const char *pathname, int proj_id);
ftok的英文可以理解为 file to key 的缩写。即将文件转换成key。 参数pathname是文件路径名(该文件必须存在,通常用当前路径名 “.”);proj_id被称作子id,自己指定一个整型。 注意如果两个进程要通过system v的ipc通信,那么它们的ftok函数的两个参数必须相同,这样才能生成同样的key,从而产出同样的id。 返回值类型key_t在
- key的31~24位为ftok函数第二个参数proj_id的低8位。
- key的23~16位为该文件stat结构中st_dev属性的低8位。
- key的15~0位为该文件stat结构中st_ino属性的低16位。
ipc_perm 这是一个结构体。他的英文含义是: ipc permission(ipc权限)
struct ipc_perm { key_t __key; /* key */ uid_t uid; /* 所有者的有效用户id */ gid_t gid; /* 所有者的有效组id */ uid_t cuid; /* 创造者的有效用户id */ gid_t cgid; /* 创造者的有效组id */ unsigned short mode; /* 权限 */ unsigned short __seq; /* 可忽略 */ };
三种ipc机制都有对应的结构体,这些结构体中有一个 共同的成员就是这个ipc_perm,用来标识ipc对象的权限。 ipc对象函数创建
不同ipc机制之中的很多函数之间有着异曲同工之妙,学会分类,找到各自的相同点和不同点。
分类 | 创建函数 | 控制函数 | 独立函数 |
---|---|---|---|
消息队列 | msgget | msgctl | msgsnd,msgrcv |
信号量 | semget | semctl | semop |
共享内存 | shmget | shmctl | shmat,shmdt |
横着看。可以清楚的看到同一行的函数名都有同一个头。这个头就是ipc机制的缩写:msg、sem和shm。 竖着看。我把每种ipc函数都分成三类:创建函数、控制函数和独立函数。创建函数和控制函数是三种ipc都有的,而独立函数指的是与具体ipc机制特性相关的函数。
- 创建函数(get函数)创建的是ipc对象的标识符(id),它们以ftok生成的键(key)为参数(以及其他参数)生成。
- 控制函数(ctl函数)控制的是对应ipc数据结构的成员属性,从而改变ipc对象的状态。
实际上key和id都能唯一地标识一个ipc对象,但是之所以没有直接对key操作,而是拐弯对id进行操作,是因为id除了能唯一标识ipc对象之外,还包含其他信息(比如权限)。因此通过get函数生成的id,可以类比 文件描述符(fd),而get函数在功能上来说可以类比 open函数。 只能说ipc的id可以类比文件描述符fd,实际上它并不是fd的一种。不信你可以写个程序创建一个消息队列,然后进入死循环,去/proc/进程id/fd/目录下面看看有没有这个id值。fd是进程相关的,进程终止之后fd被释放,而ipc对象在进程结束之前如果没有显示的删除,那么及时进程结束了,它还独立存在。
int msgget(key_t key, int flag); int semget(key_t key, int nsems, int flag); int shmget(key_t key, size_t size, int flag);
这个函数都有一个flag参数(由逻辑或组成),该参数也可类比open函数的flag参数,虽然取值不尽相同。这三个函数的flag取值是一样的。
- ipc_creat 如果key不存在,则创建(类似open函数的o_creat)
- ipc_excl 如果key存在,则返回失败(类似open函数的o_excl)
- ipc_nowait 如果需要等待,则直接返回错误
通常的用法是 ipc_creat|ipc_excl ,如果不存在key则创建它,如果已存在则返回失败(eexist)。 上面讲得是一个 类比的记忆与学习方法。另外我还提到了一个 印证,指的是和shell的命令相印证,linux中有三个命令是和system v的三个ipc相关的:
- ipcmk
- ipcrm
- ipcs
其中ipcmk命令用于创建ipc对象,来看一下它的三个主要选项:
选项 | 描述 |
---|---|
-q | 创建一个消息队列 |
-s | 创建信号量,后跟一参数指明数量 |
-m | 创建共享内存,后跟一参数指明大小 |
可知创建消息队列的时候选项后面是没有参数的,而创建信号量和共享内存的时候选项后面还有一参数(用于指明数量或大小)。正好信号量和共享内存的get函数也比消息队列多一个。
ctl函数
int msgctl(int msqid, int cmd, struct msqid_ds *buf); int semctl(int semid, int semnum, int cmd, ...); //有三参数和四参数两种,根据cmd的不同而不同 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
三个ctl控制函数其实是在操作三种ipc机制对应的三种数据结构:
- msqid_ds
- semid_ds
- shmid_ds
它们有共同的后缀—— id_ds。ds就是 data structure(数据结构)的意思。 此处要注意的是消息队列的对应的结构体名称,其前缀为msq而非msg(这个缩写有点违和,取了队列的首字母q)
这些结构体中有一个 共同的成员就是前面提到的 ipc_perm。具体每个结构体的成员有谁,这里篇幅有限,不赘述,大家自行百度谷歌,或者去 man一下其对应的的 控制函数。大家在学习过程中就要一层一层的抽丝剥茧,看到函数的参数是结构体,就要去探究结构体的成员,看到它的成员也是结构体,那么就要继续探究。
这三个函数都有一个cmd参数(控制参数),不同的ipc机制它们的控制参数是不一样的。但是由几个控制参数是公共的(定义在ipc.h中)。下面以消息队列为例(也适用于信号量和共享内存)
ipc_rmid | 删除消息队列。只能由其创建者或超级用户(root)来删除 |
ipc_set | 设置消息队列的属性。按照buf指向的结构中的值,来设置此ipc对象 |
ipc_stat | 读取消息队列的属性。取得此队列的msqid_ds结构,并存放在buf中 |
ipc_info | (只有linux有)返回系统级的限制,结果放在buf中 |