0%

Linux IPC之IPC_PRIVATE和ftok的比较

Linux下的多种IPC都需要使用类似xxget函数,通过一个key_t类型的值获取IPC对象id,这个key参数的传入有两种方式,一个是通过ftok调用生成,一个是IPC_PRIVATE,二者在使用上有一些区别。

ftok函数

ftok函数能够通过一个路径和子序号产生一个键值,函数调用如下:

1
2
3
#include < sys/types.h>
#include < sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

其中:

  • 第一个参数pathname,是一个存在的文件或目录名,必须能够访问;

  • 第二个参数proj_id,是非0整数(一般用i节点号)

在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。如指定文件的索引节点号为65538,换算成16进制为 0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。

通过key值,IPC方法可以获取IPC对象的id,例如:

1
2
3
4
5
int shmget( key_t key, size_t size, int flag);   // 共享内存

int semget(key_t key, int nsems, int flag); // 信号量

int msgget(key_t key, int flag); // 消息队列

ftok使用有两个点需要注意:

  • 使用 ftok创建共享内存,毫无关系的进程,可以通过得到同样的key,来操作同一个共享内存,对共享内存进行读写时,需要利用信号量进行同步或互斥。

  • 如果将pathname表示的文件删除,然后重新建立,ftok生成的key值是可能会改变的,因为虽然新建立的文件路径和文件名(或者目录)相同,但是索引节点已经改变了,这是需要在使用的时候特别注意。

IPC_PRIVATE

使用IPC_PRIVATE可以直接用于xxget函数中生成IPC对象的id,而不用ftok先生成key。其实IPC_PRIVATE表示的key为0,所以这个key和IPC对象的编号就没有了对应关系。这样毫无关系的进程,就不能通过key值来得到IPC对象的编号(因为这种方式创建的IPC对象的key值都是0)。因此,这种方式产生的IPC对象,和无名管道类似,不能用于毫无关系的进程间通信。但也不是一点用处都没有,仍然可以用于有亲缘关系的进程间通信,最常见的是父子进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include < stdio.h>
#include < stdlib.h>
#include < errno.h>
#include < sys/ipc.h>
#include < sys/types.h>
#include < sys/shm.h>
#include < string.h>
#define MAXSIZE 1024
int main() {
int shmid;
char *p = NULL;
pid_t pid;

if ((shmid = shmget(IPC_PRIVATE, MAXSIZE, 0666)) == -1) {
perror("shmget");
exit(-1);
}
if ((pid = fork()) == -1) {
perror("fork");
exit(-1);
}
if (pid == 0) {
// 子进程
if ((p = shmat(shmid, NULL, 0)) == (void *)-1) {
perror("shmat");
exit(-1);
}
// 写入字符串
strcpy(p, "hello\n");
system("ipcs -m");
if (shmdt(p) == -1) {
perror("shmdt");
exit(-1);
}
system("ipcs -m");
}
else {
// 父进程
getchar();
if ((p = shmat(shmid, NULL, 0)) == (void *)-1) {
perror("shmat");
exit(-1);
}
// 读取字符串
printf("%s\n", (char *)p);
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("RM");
exit(-1);
}
}
return 0;
}
`

该程序中,父进程使用IPC_PRIVATE方式创建了共享内存,然后fork产生了子进程,由于子进程是复制父进程的方式产生的,因此,子进程也可以操作共享内存。子进程往共享内存里写了内容后,父进程可以读到。

需要注意的是,int shmID=shmget(IPC_PRIVATE,len,IPC_CREAT|0600); 需要在父子进程都可见的地方调用(即在创建子进程之前),这样父子进程中都有IPC对象,然后对IPC对象进行操作,否则不能实现内存的共享。