Posix与System V IPC
- 一、Posix IPC
- 1.概述
- 2.IPC名字
- 3.px_ipc_name函数
- 3.创建与打开IPC通道
- 4.IPC权限
- 二、System V IPC
- 1.概述
- 2.key_t键和ftok函数
- 3.ipc_perm结构
- 4.创建与打开IPC通道
- 5.IPC权限
- 6.标识符重用
- 7.ipcs和ipcrm程序
- 8.内核限制
一、Posix IPC
1.概述
三种类型的IPC合称为"Posix IPC
":
-
Posix消息队列;
-
Posix信号量;
-
Posix共享内存区;
2.IPC名字
三种类型的Posix IPC都使用"Posix IPC名字
” 进行标识。mq_open、 sem_open和shm_open
这三个函数的第一个参数就是这样的一 个名字,它可能是某个文件系统中的一 个真正的路径名,也可能不是。
Posix.1描述Posix IPC名字的:
-
必须符合已有的路径名规则(必须最多由PATH_MAX个字节构成,包括结尾的空字节)。
-
若以斜杠符开头,那么对这些函数的不同调用将访问同 一 个队列。如果它不以斜杠符开头,那么效果取决于实现。
-
名字中额外的斜杠符的解释由实现定义。
由于移植性问题不能都兼容到,应该把Posix IPC名字的#define
行放在一个便于修改的头文件中,这样程序转移到另一个系统上,只需修改头文件。Posix.1定义了三个宏:
S_TYPEISMQ(buf);
S_TYPEISSEM(buf);
S_TYPEISSHM(buf);
- 参数buf是指向某
个stat
结构的指针,其内容由fstat、lstat或stat
这三个函数填入。 - 若所指定的lPC对象(消息队列、信号囊或共享内存区对象)是作为一种独特的文件类型实现的,而且参数所指向的stat结构访问这样的文件类型,那么这三个宏计算出 一 个非零值。否则,计算出的值为0。
- 项目3
3.px_ipc_name函数
自定义px_ipc_name
函数解决移植性问题的方法,定位Posix IPC名字而添加上之前的前缀目录。
char *px_ipc_name(const char *name);
//name不能有任何斜杠符,例:
px_ipc_name("test");
函数原型:
char * px_ipc_name(const char *name)
{char *dir, *dst, *slash;if ( (dst = malloc(PATH_MAX)) == NULL)return(NULL);//可以使用环境变量覆盖默认目录if ( (dir = getenv("PX_IPC_NAME")) == NULL) {
#ifdef POSIX_IPC_PREFIXdir = POSIX_IPC_PREFIX; /* 来自“config.h”*/
#elsedir = "/tmp/"; /* default */
#endif}/* dir必须以斜杠结尾 */slash = (dir[strlen(dir) - 1] == '/') ? "" : "/";snprintf(dst, PATH_MAX, "%s%s%s", dir, slash, name);return(dst); /* 调用方可以释放()此指针*/
}
3.创建与打开IPC通道
mq_open、sem_open和shm_open
这三个创建或打开个 IPC 对象的函数 ,它们的名为oflag
的第二个参数指定怎样打开所请求的对象。与标准open函数的第二个参数类似。下图给出了可组合构成该参数的各种常值。
-
前3行指定怎样打开对象:只读、只写或读写。
-
消息队列能以其中任何一种模式打开。
-
信号量的打开不指定任何模式(任意信号量操作,都需要读写访问权)。
-
共享内存区对象不能以只写模式打开。
-
O_CREAT
:若不存在则创建由函数第一个参数所指定名字的消息队列、信号量或共享内存区对象(同时检查O_EXCL标志)。 -
O_EXCL
:如果该标志和O_CREAT
一起指定,那么IPC函数只在所指定名字的消息队列、信号量或共享内存区对象不存在时才创建新的对象。若该对象已经存在,而且指定了O_CREATIO_EXCL
,那么返回一个EEXIST
错误。 -
O_NONBLOCK
:该标志使得一个消息队列在队列为空时的读或队列填满时的写不被阻塞 -
O_TRUNC
:若以读写模式打开了一个已存在的共享内存区对象,那么该标志将使得该对象的长度被截成0。
创建新的消息队列、信号量或共享内存对象时,至少需要另外一个称为mode的参数,该参数指定权限位,如下图常值按位或形成的。
下图展示了打开IPC对象的真正逻辑流程:
下图是展示上图的另一种方式:
4.IPC权限
新的消息队列、有名信号显或共享内存区对象是由其oflag
参数中含有O_CREAT
标志的mq_open、sem_open或shm_open
函数创建的。权限位与这些IPC类型的每个对象相关联,就像它们与每个Unix文件相关联一样。
当同样由这三个函数打开已存在的消息队列、信号拭或共享内存区对象时
(或者未指定O_CREAT
,或者指定了O_CREAT
但没有指定O_EXCL
,同时对象已经存在),将基于如下信息执行权限测试:
-
创建时赋予该IPC对象的权限位;
-
所请求的访间类型(
O_RDONLY、O_WRONLY或O_RDWR
); -
调用进程的有效用户ID、有效组ID以及各个辅助组ID(前提支持)。
大多数Unix内核按如下步骤执行权限测试:
-
若当前进程的有效用户ID为0(超级用户),那就允许访问。
-
在当前进程的有效用户ID等于该IPC对象的属主ID的前提下,若相应的用户访问权限位已设置,那就允许访问,否则拒绝访问。(相应的用户访问权限位:若当前进程为读访问而打开该IPC对象,那么用户读权限位必须设置;若当前进程为写访问而打开该IPC对象,那么用户写权限位必须设置。)
-
在当前进程的有效组ID或它的某个辅助组ID等于该IPC对象的组ID的前提下,若相应的组访问权限位已设置,那就允许访问,否则拒绝访问。
-
若相应的其他用户访间权限位已设置,那就允许访问,否则拒绝访问。
-
这4个步骤是按所列的顺序尝试的。因此,如果当前进程拥有该IPC对象,那么访问权的授予与拒绝只依赖于用户访问权限(组访问权限绝不会考虑)。
- 若当前进程不拥有该IPC对象,但它属于某个合适的组,那么访问权的授予与拒绝只依赖于组访问权限(其他用户访问权限绝不会考虑)。
二、System V IPC
1.概述
三种类型的IPC和称为"System V IPV
":
-
System V消息队列。
-
System V信号量。
-
System V共享内存区。
下图显示System V IPC函数:
2.key_t键和ftok函数
三种类型的System V IPC使用key_t
值作为它们的名字。头文件<sys/types.h>
把key_t
这个数据类型定义为 一个整数,通常至少32位的整数。这些整数值通常是由ftok函数赋予的。
函数ftok
把已存在的路径名和整数标识符转换成一 个key_t
值,称为IPC键。
//函数从pathname导出的信息与id的低序8位组合成一个整数IPC键。
#include<sys/ipc.h>
key_t ftok(const char *pathname,int fd);
-
该函数假定对于使用System V IPC的某个给定应用来说,客户和服务器同意使用对该应用有一 定意义的
pathnam e
。 -
pathname
可以是服务器守护程序的路径名、 服务器使用的某个公共数据文件的路径名或者系统上的某个其他路径名。 -
若客户和服务器之间只需单个IPC通道,那么可以使用譬如说值为1的id。
-
若需要多个IPC通道,譬如说从客户到服务器一个通道,从服务器到客户又一 个通道,那么作为一 个例子,一 个通道可使用值为1的id, 另一个通道可使用值为2的id 。
-
客户和服务器 一 旦在
pathname和id
上达成一致,双方就都能调用ftok
函数把pathname
和id
转换成同一个IPC键。
ftok的典型实现调用stat函数,然后组合以下三个值:
-
pathname
所在的文件系统的信息(stat结构的st_dev成员)。 -
该文件在本文件系统内的索引节点号(stat结构的st_ino成员)。
-
id的低序8位(不能为0)。
-
三个值的组合通常会产生32位键。不能保证两个不同的路径名与同一个id的组合产生不同的键,因为上面所列三个条目(文件系统标识符、索引节点、id)中的信息位数可能大于一个整数的信息位数。
-
若
pathname
不存在,或者对于调用进程不可访问,ftok返回-1。- 路径名用于产生键的文件不能是在服务器存活期间由服务器反复创建并删除的文件,因为该文件每次创建时由系统赋予的索引节点号很可能不 一 样,于是对下一 个调用者来说,由ftok返回的键也可能不同。
-
例:下列程序取一个作为命令行参数的路径名,调用stat,调用ftok,然后输出stat结构的st_dev和st_ino成员以及得出的IPC键。十六进制输出,从两个值以及id值0x57看出IPC键如何构造的。
int main(int argc,char **argv)
{struct stat stat;if(argc!=2)err_quit("usage:ftok<pathname>");stat[argv[1],&stat);printf("st_dev:%lx,st_ino:%lx,key:%x\n",(u_long)stat.st_dev,(u_long)stat.st_ino,ftok(argv[1],0x57));exit(0);
}
id
在IPC键的高序8位,st_dev
的低序12位IPC在键的接下来12位,st_ino
的低序12位则在IPC键的低序12位。
3.ipc_perm结构
内核每个IPC对象维护一个信息结构,其内容跟内核给文件维护的信息类似。
<sys/ipc.h>
struct ipc_perm{uid_t uid;gid_t gid;uid_t cuid;gid_t cgid;mode_t mode;ulong_t seq;key_t key;
};
4.创建与打开IPC通道
创建或打开一个IPC对象的三个get_XXX
函数的第一个参数为key是类型为key_t
的IPC键,返回值identifier
是一个整数标识符。该标识符不同于ftok函数的id参数,key有两种选择:
- 调用ftok,给它传递pathname和id。
- 指定key为
IPC_PRIBATE
,保证创建一个新的、唯一的IPC对象。
所有三个get_XXX
函数都有一个名为oflag
的参数,指定IPC对象的读写权限位(ipc_perm结构的mode成员
),并选择是创建一个新的IPC对象还是访问一个已存在的IPC对象.这种选择的规则如下:
-
指定key为
IPC_PRIVATE
能保证创建一个唯一的!PC对象。没有一对id和pathname
的组合会导致ftok产生IPC_PRIVATE
这个键值。 -
设置
oflag
参数的IPC_CREAT
位但不设置它的IPC_EXCL
位时,若所指定键的IPC对象不存在,那就创建一个新的对象,否则返回该对象。 -
同时设置oflag的
IPC_CREAT和IPC_EXCL
位时,若所指定键的IPC对象不存在,那就创建一个新的对象,否则返回EEXIST错误,因为该对象已存在。
-
对IPC对象来说,
IPC_CREAT和IPC_EXCL
的组合跟open函数的O_CREAT和O_EXCL
的组合类似。- 设置I
PC_EXCL
位但不设置IPC_CREAT
位是没有意义的。
- 设置I
下图展示打开IPC对象的逻辑图:
IPC对象的逻辑图l另一种形式:
-
中间只有
IPC_CREAT
而没有IPC_EXCL
标志的那一行,得不到一个指示以判别是创建了一个新对象,还是在引用一个已存在的对象。 -
大多数应用程序中,由服务器创建IPC对象并指定
IPC_CREAT
标志(如果它不关心该对象是否存在)或IPC_CREAT I IPC_EXCL
标志(如果它需要检查该对象是否已经存在)。 -
客户则不指定其中任何一个标志(前提服务器已经创建了对象)。
5.IPC权限
每当使用某个get_XXX
函数(指定IPC_CREAT标志)创建一个新的IPC对象时,以下信息就保存到该对象的ipc_perm
结构中。
-
oflag
参数中某些位初始化ipc_perm
结构的mode成员。下图展示System V三种不同的IPC机制的权限位(>>3右移3位)。 -
cuid和cgid
成员分别设置为调用进程的有效用户ID和有效组ID。这两个成员合称为创建者ID。 -
ipc_perm
结构的uid和gid
成员也分别设置为调用进程的有效用户ID和有效组ID。这两个成员合称为属主ID 。
尽管一个进程可通过调用相应IPC机制ctlXXX
函数(所用命令为IPC_SET
)修改属主ID,创建者ID却从不改变。三个ctlXXX
函数还允许一个进程修改某个IPC对象的mode成员。
每当有一个进程访问某个IPC对象时,IPC就执行两级检查,该IPC对象被打开时(getXXX函数)执行 一 次,以后每次使用该对象时执行一 次。
-
每当有进程以某个getXXX函数 建立访问某个已存在IPC 对象的通道时,IPC 就执行一次初始检查,验证调用者的oflag参数没有指定不在该对象
ipc_perm结构mode
成员中的任何访问位。1. 一个服务器进程可以把它的输入消息队列的mode成员设置成关掉组成员读和其他用户读这两个权限位。2. 任何进程调用针对该消息队列的msgget函数时,如果所指定的oflag参数包含这两位,那么该函数都将返回 一 个错误。3. 然而由`getXXX` 函数完成的这种测试并没有多大用处,它隐含假定调用者知道自己属于哪个权限范畴一用户组成员或其他用户。4. 若创建者特意关掉了某些权限位,而调用者却指定了这些位,那么getXXX函数将检测出这个错误。5. 然而任何进程都能够完全绕过这种检查,其办法是在得知该IPC对象已存在后,简单地指定 一 个值为0的oflag参数即可。
-
每次IPC操作都对使用该操作的进程执行一次权限测试。例:每当有一个进程试图使用
msgsnd
函数往某个消息队列放置 一 个消息时,msgsnd
函数将以下面所列的顺序执行 (多个)测试。一旦某个测试赋予了访问权,其后的测试就不再执行。1. 超级用户总是赋予访问权。2. 若当前进程的有效用户lD等于该IPC对象的u`id值或cuid值`,而且**相应的访问位**在该IPC对象的`mode`成员中是打开的,那么赋予访问权。(相应的访问位:若调用者想要在该IPC对象上执行 一 个读操作 ,那么读位必须设置,如果想要执行 一 个写操作,那么写位必须设置)。3. 若当前进程的有效组ID等于该IPC对象的`gid值或cgid值`,而且相应的访问位在该IPC对象的`mode`成员中是打开的,那么赋予访问权。4. 如果上面的测试没有一个为真那么相应的 “其他用户” 访问位在该IPC对象的mode成员中必须是打开的才能赋予访问权。
6.标识符重用
-
ipc_perm
结构含有seq
的变量,表示槽位使用情况序列号。该变量是由内核为系统中每个潜在的IPC对象维护的计数器。每当删除一 个IPC对象时,内核就递增相应的槽位号,若溢出则循环回0。 -
System V IPC标识符是系统范围的,而不是特定于进程的。
-
由于某个行为不端的进程可能尝试从另外某个应用的消息队列读消息,办法是尝试不同的小整数标识符,以期待找出一个当前在使用的允许大家读访问的消息队列。要是这些标识符取值是小整数,那么找到一个有效标识符的可能性为1:50。
-
避免这种问题,把IPC机制的标识符可能范围扩大到包含所有整数,而不是小整数。实现:每次重用一个IPC表项,把返回给调用进程的标识符增加一个IPC表项数。
递增槽位使用情况序列号的另 一 个原 因是:避免短时间内重用System VIP 示识符。这有助与确保过早终止的服务器重新启动后不会重用标识符。
例:返回前10标识符值。
int main(int argc,char **argv)
{int i,msqid;for(i=0;i<10;i++){msqid = msgget(IPC_PRIVATE,SVMSG_MODE|IPC_CREAT);printf("msqid=%d\n",msqid);msgctl(msqid,IPC_RMID,NULL);}exit(0);
}
7.ipcs和ipcrm程序
System V IPC的三种类型不是以文件系统中的路径名标识的,因此使用标准的ls和rm
程序无法看到它们,也无法删除它们。但实现了这些类型IPC的任何系统都提供两个特殊的程序:
-
ipcs
输出有关System VIPC特性的各种信息。 -
ipcrm
则删除一个SystemV消息队列、信号量集或共享内存区。
8.内核限制
-
System V IPC的多数实现有内在的内核限制,例如消息队列的最大数目、每个信号量集的最大信号量数,等等。
-
这些对象的大小被内核限制得往往太小,因为其中许多限制起源于在某个小地址空间系统完成的最初实现。但是多数系统允许管理员部分或完全修改这些默认限制。