进程间通信(IPC,Inter-Process Communication)指至少两个进程或线程间传送数据或信号的一些技术或方法。
一.System V IPC 的构成
1.system v消息队列
2.system v信号量
3.system v共享内存
我们把这三种工具统称为System V IPC的对象,每个对象都具有一个唯一的IPC标识符(identifier)。要保证不同的进程能够获取同一个IPC对象,必须提供一个IPC关键字(IPC key),内核负责把IPC关键字转换成IPC标识符。
汇总 | 消息队列 | 信号量 | 共享内存区| |
---|---|---|---|
头文件 | sys/msg.h | sys/sem.h | sys/shm.h |
创建或打开IPC | msgget | semget | shmget |
控制IPC操作函数 | mesgctl | semctl | shmctl |
IPC操作函数 | msgsnd/msgrcv | semop | shmat/shmdt |
**
二.Ftok函数
**
系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok原型如下: **key_t ftok( char * fname,int id )** fname就时你指定的文件名(该文件必须是存在而且可以访问的),**id是子序号,虽然为int,但是只有8个比特被使用(0-255)。** 当成功执行的时候,一个key_t值将会被返回,否则 -1 被返回。在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。如指定文件的索引节点号为65538,换算成16进制为 0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
三.创建与打开IPC通道
对于key值,应用程序可以有两种选择
1.调用ftok,给它传递pathname和id。
2.指定key为IPC_PRIVATE,这将保证会创建一个新的唯一的IPC对象(不推荐)。
四.创建通道-getXXX函数
通常我们使用getxxx函数来创建或者访问一个已知的通道。
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 如果需要等待,则直接返回错误
用一个图表更能体现其中的逻辑
oflag参数 | key不存在 | key存在 |
---|---|---|
无特殊标志 | 出错,errno = ENOENT | 成功,引用已存在的对象 |
IPC_CREAT | 成功,创建新对象 | 成功,引用新对象 |
IPC_CREAT|IPC_EXCL | 成功,创建新对象 | 出错,errno = EEXIST |
另外oflag中的参数也将初始化ipc_perm结构中的mode成员。其中权限有一个简单的记忆方法,就是和linux中的权限是一样的,比如说你给一个0644系统将识别为MSG_R|MSG_W|MSG_R>>3|MSG_R>>6
五.控制函数——xxxctl函数
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
int semctl(int semid,int semmun,.../*union semun arg */);
int shmctl(int shmid,struct shmid_ds *buf);
三个ctl控制函数其实是在操作三种IPC机制对应的三种数据结构:
msqid_ds semid_ds shmid_ds
它们有共同的后缀——id_ds。ds就是data structure(数据结构)的意思。
此处要注意的是消息队列的对应的结构体名称,其前缀为msq而非msg(这个缩写有点违和,取了队列的首字母q)
这些结构体中有一个共同的成员就是前面提到的ipc_perm。。
这三个函数都有一个cmd参数(控制参数),不同的IPC机制它们的控制参数是不一样的。但是由几个控制参数是公共的(定义在ipc.h中)。下面以消息队列为例(也适用于信号量和共享内存)
命令 | 功能 |
---|---|
IPC_RMID | 删除消息队列。只能由其创建者或超级用户(root)来删除 |
IPC_SET | 设置消息队列的属性。按照buf指向的结构中的值,来设置此IPC对象 |
IPC_STAT | 读取消息队列的属性。取得此队列的msqid_ds结构,并存放在buf中 |
semctl函数有更复杂的作用,我们在稍后再谈。
五.操作函数
操作函数在不同的体系中有着不一样的作用。
在消息队列中
int msgsnd(int msqid,const void *ptr,size_t length,int flag);
ssize_t msgrcv(int msqid,void *ptr,long type,int flag);
其中 ptr所指向的结构需要自己构造,通常是这样
struct msgbuf
{
long mtype;
char mtetx[MAX_LINE];
}
在msgsnd中 size_t 应该为发送的mtetx的长度可以用strlen(mtetx)+1
在msgrcb中 size_t 应该为ptr所指向的缓冲区的数据部分大小,是函数可以返回的最大数据量,可以用MAX_LIEN
在信号量中
int semop(int semid,struct sembuf *opsptr,size_t nops);
opsptr指向sembuf
struct sembuf
{
short sem_num;
short sem_op;
short sem_flag;
}
用于操作信号集中sem_num的信号量的增减,增减量由sem_op来决定。
在共享内存区中
int shmdt(const void *shmaddr);
void *shmat(int shmid,const void *shmaddr,int flag);
shmat函数是将共享内存区链接到调用的进程空。
其中当shmaddr 为NULL,系统替调用者选择地址(推荐)
当为一个非空的指针的时候,返回的地址取决与flag中的SHM_RND。没有指定者直接附接到shmaddr的地址上,指定了则附接到shmaddr向下舍入SHMLBA的常值所指向的地址上。
shmdt函数用于断接shmat附接的内存区。
六.小技巧-包裹函数
在UNIX网络编程中常常出现一些首字符大写的函数,这些是包裹函数,包裹了出错处理。有些函数不止。
这是我自己写的包裹函数,可以参考一下。
int Semop(int semid,size_t size)
{
int ret = semop(semid,opsptr,size);
if (ret == -1)
{
perror("semget:");
exit(0);
}
return ret;
}
这样可以大量节省编程时间和代码量。