1. 概述
有三种类型的IPC称为System V IPC:
- System V 消息队列
- System V 信号量
- System V共享内存
之所以称为System V IPC是因为这三种IPC机制都是来源于System V Unix的实现。
消息队列 | 信号量 | 共享内存 | |
---|---|---|---|
头文件 | <sys/msg.h> | <sys/sem.h> | <sys/shm.h> |
创建或者打开IPC函数 | msgget | semget | shmget |
控制IPC操作函数 | msgctl | semctl | shmctl |
IPC操作函数 | msgsnd/msgrcv | semop | shmat/shmdt |
key_t 键和ftok函数
三种System V IPC
都使用key_t
的类型作为它们的名字, 头文件定义在<sys/types.h>
中,定位为一个无符号的长整型数,可以 通过ftok()
函数获取,或者统一定义在一个头文件中(不能有重复的)。
//NAME
// ftok - convert a pathname and a project identifier to a System V IPC key
//SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
// success return System V IPC key, otherwise return -1
注意点:
pathname
指定的文件必须要存在,因为ftok
函数会使用指定的文件的st_dev
和st_ino
同proj_id
共同生成一个key
。proj_id
只会使用到低8位, 但是不能为0。
ftok
的典型实现调用stat
函数,然后组合以下三个值。
pathname
所在的文件系统的信息(stat
结构体的st_dev
信息)。- 该文件在文件系统中的索引节点号(
stat
结构体的st_ino
成员)。 proj_id
的低序8bit(不能为0)。
索引节点不能为0是因为IPC_PRIVATE定义为0.
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
struct stat st;
if (argc != 2) {
fprintf(stderr, "usage: ftok <pathname>\n");
return -1;
}
stat(argv[1], &st);
printf("st_dev: %lx\nst_ino: %lx\nkey: %x\n",
(unsigned long)st.st_dev, (unsigned long)st.st_ino, (unsigned int)ftok(argv[1], 0x57));
return 0;
}
st_dev: 805
st_ino: a5cd2
key: 57055cd2
由执行结果可知: ftok
计算key
值为proj_id
在高8位, st_dev中年12位IPC的键的接下来的12bit, st_ino在低12bit,
ipc_perm结构
内核给每一个IPC对象维护一个信息表,其内容跟内核给文件维护的信息类似。
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
该结构体以及System V IPC函数使用的较为明显的常值定义在<sys/ipc.h>
头文件中。
创建与打开IPC通道
创建或者打开一个IPC对象的三个getXXX
函数的第一个参数都是key_t
的IPC键值,返回值为identifier
是一个整型标识符。对于key
值,应用程序有两种选择:
- 调用
ftok
函数,给它传递一个pathname和id. - 指定key为IPC_PRIVATE, 这将保证会创建一个新的,唯一的IPC对象。
所有的getXXX
函数都有一个名为oflag
的参数,它指定了IPC对象的读写权限(ipc_perm
中的mode
域),并选择是创建一个新的IPC对象还是访问一个已经存在的IPC对象。选择规则如下:
- 指定
key
为IPC_PRIVATE
能保证创建一个IPC对象,没有一对id和pathname的组合导致ftok参数IPC_PRIVATE这个键值。 - 设置oflag参数的IPC_CREAT为但是不设置IPC_EXCL位时,如果所指定的键的IPC对象不存在则创建一个新的IPC对象,否则返回该IPC对象。
- 同时设置IPC_CREAT和IPC_EXCL位, 如果指定的IPC对象不存在,则创建一个新的IPC对象,如果存在,返回一个EEXIST错误,因为该IPC对象存在。
设置IPC_EXCL但是不设置IPC_CREAT是没有意义的,也就是说不能单独使用IPC_EXCL。
oflag | key不存在 | key已存在 |
---|---|---|
无特殊标志 | 出错 errno=ENOENT | 成功,引用存在对象 |
IPC_CREAT | 成功,创建IPC对象 | 成功,引用存在对象 |
IPC_CREAT or IPC_EXCL | 成功,创建IPC对象 | 失败, errno= EEXIST |
注意: IPC创建了自己的IPC_XX常值,不同于open函数的O_CREAT和O_EXCL.
IPC权限
当使用get_xxx
函数创建IPC对象时(使用IPC_CREAT),以下信息保存在该对象的ipc_perm
结构中。
-
oflag
中的某些位初始化ipc_perm中的mode成员。下标展示了System V 三种不同IPC机制的权限位:
-
cuid
和cgid
成为分别设置为调用进程的有效用户ID和有效组ID, 这两个成员合称为创建者ID(creator ID). -
ipc_perm
结构中的uid
和gid
成员也分别设置为调用进程的有效用户id和有效组id, 这两个成员合称为属主id(owner id)。
每当一个进程访问某个IPC对象时,IPC就执行两次检查,该IPC被打开时检查一次,以后每使用该对象就检查一次。
标识符重用
ipc_perm
结构还包含一个名为seq
的变量,它是一个槽位使用情况序列号,该变量是一个由内核为系统中每个潜在的IPC对象维护一个计数器,每当删除一个IPC对象时,内核就会递增相应的槽位标识,若溢出则循环回0.
ipcs和ipcrm程序
由于System V IPC三种类型不是以文件系统中的路径名标识,因此使用标准的ls和rm程序无法看到他们,也无法删除它们, 因此提供了两个特殊的程序: ipcs
和ipcrm
。
ipcs
: 查看各种IPC信息。
ipcrm
: 删除IPC对象。
总结
msgget/semget/shmget
第一个参数都是key
键值。- 如果指定
IPC_PRIVATE
则创建一个新的,唯一的IPC
对象。 - 需要区分
IPC_CREAT
和IPC_EXCL
的使用。 ipc_perm
和oflag
参数的关系。