每个内核中的IPC结构(消息队列、信号量和共享内存)都用一个非负整数的标识符(identifier)加以引用。
也就是说,一个非负整数代表一个IPC结构。
比如,为了对一个消息队列发送或者取消息,只需要知道其队列标识符,与文件描述符不同,IPC标识符不是小的正数。当一个IPC结构被创建,以后又被删除,与这种结构相关的标识符连续加1,直到达到整型数的最大正直,然后又回到0。
对于同一个IPC结构,为了使多个进程都能获取标识符ID指向这个IPC结构,需要大家都能访问到文件路径和项目id来创建一个东西,拿这个东西来生成IPC的结构,这个东西就是:
键(key)。
举个例子,不同的人都想进同一个带锁的房间交流,是不是都需要相同钥匙打开?
1.键(key)的创建(使用ftok函数)
每次创建IPC结构,都必须指定一个键,键的数据类型是基本系统数据类型key_t,通常在头文件<sys/types.h>中,定位为整型(《Unix环境高级编程》说是长整型,但我编译时发现是整型)。键由内核变换成标识符。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
ftok根据提供的路径pathname和项目proj_id生成一个键,如果返回-1,则出错。
值得一提的是,ftok可能会返回一个负数,只要不是-1就是有效的key。
ftok函数只使用proj_id的低8位(0到255的值),也就是说proj_id不管值多大,也只取最低8位。
pathname必须是一个有效的路径及可访问的。
生成的key是留给semget,msgget,shmget使用的。
一个key和一个整型的flag,满足以下条件之一,就创建一个新的IPC结构:
(1)key是IPC_PRIVATE
(2)key当前未与特定类型的IPC结构相结合,并且flag中指定了IPC_CREAT位
如果希望创建一个新的IPC结构,而且要确保不是引用具有同一标识符的一个现已存在的IPC结构,那么必须在flag中同时指定IPC_CREAT和IPC_EXCL位,加上这两个标志位后,如果IPC结构已经存在,就是导致创建出错,返回EEXIST。
如果访问一个现存的IPC结构,就必须知道与IPC结构相结合的标识符ID,通过ID去访问,
并且创建的键值不能 IPC_PRIVATE,因为 IPC_PRIVATE是拿来创建一个key的。
2.自己实现一个键的创建
ftok创建的键一般由以下方式构成:按给定的路径名取其stat结构,从该结构中取出部分st_dev和st_ino字段,然后再与项目ID组合起来,如果两个路径名引用不同的文件,那么对这两个路径名调用ftok通常返回不同的键。但是因为i节点号和键通常都放在长整型中,于是创建键时可能会丢失信息。这意味着如果使用同一项目ID,那么对于不同文件的两个路径名可能产生相同的键。
以下程序展示了键的创建原理:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#define ProgramId (0x11&0x00ff)
int main()
{
key_t ftok_key=-1;
struct stat stuStat;
ftok_key = ftok("/share/1.tmp",ProgramId);
printf("\n ftok_key[0x%x] errno=%d [%s]\n",ftok_key,errno,strerror(errno));
memset(&stuStat,0,sizeof(struct stat));
stat("/share/1.tmp",&stuStat);
printf("\n st_dev[0x%x] st_ino[0x%x] ProgramId[0x%x] \n",
(unsigned int )stuStat.st_dev,(unsigned int )stuStat.st_ino,ProgramId);
return 0;
}
运行结果:
root@ubuntu:/share# gcc test_ftok.c
root@ubuntu:/share#
root@ubuntu:/share#
root@ubuntu:/share#
root@ubuntu:/share# ./a.out
ftok_key[0x11014d87] errno=0 [Success]
st_dev[0x801] st_ino[0x64d87] ProgramId[0x11]
root@ubuntu:/share#
从结果来看,函数ftok产生的键值就是取ProgramId的低8bit,st_dev的后8bit,st_ino的16bit,组成一个32位的整数。
附带提下stat函数和结构体:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* time of last access */
struct timespec st_mtim; /* time of last modification */
struct timespec st_ctim; /* time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
st_dev属于long long unsigned int类型
st_ino属于long unsigned int类型
3 使用的ftok注意防范隐患
由于ftok函数是根据路径的i节点和设备id、项目id来生成键值的,所以在ftok创建的键值,生成的进程ipc结构标识符后,不要去删除路径,然后再次生成路径,因为i节点可能会发生变化,导致另外进程生成的键值不一样,无法进行消息队列通信、共享内存、信号量等,这是非常大的隐患,如果产生bug,将会非常难查。