Linux进程间通信之信号量(semaphore)、消息队列(Message Queue)和共享内存(Share Memory)
System V 进程通信方式:信号量(semaphore)、消息队列(Message Queue)和共享内存(Share Memory)
信号量
信号量(semaphore)实际是一个整数,它的值由多个进程进行测试(test)和设置(set)。就每个进程所关心的测试和设置操作而言,这两个操作是不可中断的,或称“原子”操作,即一旦开始直到两个操作全部完成。测试和设置操作的结果是:信号量的当前值和设置值相加,其和或者是正或者为负。根据测试和设置操作的结果,一个进程可能必须睡眠,直到有另一个进程改变信号量的值。
信号量可用来实现所谓的“临界区”的互斥使用,临界区指同一时刻只能有一个进程执行其中代码的代码段。为了进一步理解信号量的使用,下面我们举例说明。
假设你有很多相互协作的进程,它们正在读或写一个数据文件中的记录。你可能希望严格协调对这个文件的存取,于是你使用初始值为1的信号量,在这个信号量上实施两个操作,首先测试并且给信号量的值减1,然后测试并给信号量的值加1。当第一个进程存取文件时,它把信号量的值减1,并获得成功,信号量的值现在变为0,这个进程可以继续执行并存取数据文件。但是,如果另外一个进程也希望存取这个文件,那么它也把信号量的值减1,结果是不能存取这个文件,因为信号量的值变为-1。这个进程将被挂起,直到第一个进程完成对数据文件的存取。当第一个进程完成对数据文件的存取,它将增加信号量的值,使它重新变为1,现在,等待的进程被唤醒,它对信号量的减1操作将获得成功。
详细介绍见 http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html
消息队列
消息队列也称为报文队列,消息队列是随内核持续的,只有在内核重起或显示删除一个消息队列时,该消息队列才会真正删除 系统中记录消息队列的数据结构struct ipc_ids msg_ids位于内核中,系统中所有消息队列都可以在结构msg_ids中找到访问入口
消息队列其实就是一个消息的链表,每个消息队列有一个队列头,称为struct msg_queue,这个队列头描述了消息队列的key值,用户ID,组ID等信息,但它存于内核中而结构体struct msqid_ds能够返回或设置消息队列的信息,这个结构体位于用户空间中,与msg_queue结构相似消息队列允许一个或多个进程向它写入或读取消息,消息队列是消息的链表。
消息是按消息类型访问,进程必须指定消息类型来读取消息,同样,当向消息队列中写入消息时也必须给出消息的类型,如果读队列使用的消息类型为0,则读取队列中的第一条消息。
内核空间的结构体msg_queue描述了对应key值消息队列的情况,而对应于用户空间的msqid_ds这个结构体,因此,可以操作msgid_ds这个结构体来操作消息队列。
具体详见 http://www.cnblogs.com/biyeymyhjob/archive/2012/08/04/2623323.html
共享内存
共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。共享内存往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
函数原型说明见:http://www.cnblogs.com/biyeymyhjob/archive/2012/08/04/2623323.html
程序实例:参见http://blog.csdn.net/yangzhongxuan/article/details/7925750
#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h> #include <error.h> #define SIZE 1024 int main() { int shmid ; char *shmaddr ; struct shmid_ds buf ; int flag = 0 ; int pid ; //0(IPC_PRIVATE):会建立新共享内存对象,IPC_CREAT与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限 shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT|0600 ) ; if ( shmid < 0 ) { perror("get shm ipc_id error") ; return -1 ; } pid = fork() ; if ( pid == 0 ) { shmaddr = (char *)shmat( shmid, NULL, 0 ) ; //直接指定为NULL让内核自己决定一个合适的地址位置 if ( (int)shmaddr == -1 ) { perror("shmat addr error") ; return -1 ; } strcpy( shmaddr, "Hi, I am child process!\n") ; //与shmat函数相反,shmdt是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存 shmdt( shmaddr ) ; return 0; } else if ( pid > 0) { sleep(3 ) ; //完成对共享内存的控制,得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中 flag = shmctl( shmid, IPC_STAT, &buf) ; if ( flag == -1 ) { perror("shmctl shm error") ; return -1 ; } printf("shm_segsz =%d bytes\n", buf.shm_segsz ) ; printf("parent pid=%d, shm_cpid = %d \n", getpid(), buf.shm_cpid ) ; printf("chlid pid=%d, shm_lpid = %d \n",pid , buf.shm_lpid ) ; shmaddr = (char *) shmat(shmid, NULL, 0 ) ; if ( (int)shmaddr == -1 ) { perror("shmat addr error") ; return -1 ; } printf("%s", shmaddr) ; shmdt( shmaddr ) ; shmctl(shmid, IPC_RMID, NULL) ; //IPC_RMID:删除这片共享内存 } else { perror("fork error") ; shmctl(shmid, IPC_RMID, NULL) ; } return 0 ; }
PS:
系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值 。通常情况下,该id值通过ftok函数得到 。
ftok原型如下:
key_t ftok( char * fname, int id )
参数说明:
fname就时您指定的文档名 id是子序号。
返回值:
在一般的UNIX实现中,是将文档的索引节点号取出,前面加上子序号得到key_t的返回值
如指定文档的索引节点号为65538,换算成16进制为0x010002,而您指定的ID值为38,换算成16进制 为 0x26,则最后的key_t返回值为0x26010002。
(查询文档索引节点号的方法是: ls -i)