一、消息队列
1.mesgget,msgsnd,msgrcv
消息队列存放在内核中并由消息队列标识符标识。每个消息包含一个正长整型类型字段,一个非负长度以及实际数据字节(对应于长度),所有这些都在将消息添加到队列时,传送给msgsnd。msgrcv用于从队列中取消息。对于该类型的消息队列来说,它不是一个先进先出的队列,可以按消息的类型字段取消息,POSIX消息队列则不能按照消息类型字段提取消息。#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int flag) ;返回:若成功则为消息队列 ID,若出错则为 -1
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);返回:若成功则为 0,若出错则为 -1
int msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag) ;返回:若成功则为消息数据部分的长度,若出错则为 -1
1. mesgget用于创建一个新队列或打开一个现存的队列,当创建一个新队列时,初始化 msqid-ds结构的下列成员:
- ipc-perm结构。该结构中 mode按flag中的相应许可权位设置。
- msg_qnum,msg_lspid、msg_lrpid、msg_stime和msg_rtime都设置为0。
- msg_ctime设置为当前时间。
- msg_qbytes设置为系统限制值。
2. msgsnd用于将数据放到消息队列上。每个消息都由三部分组成,它们是:正长整型类型字段、非负长度(nbytes)以及实际数据字节(对应于长度)。消息总是放在队列尾端。
- ptr指向一个长整型数,它包含了正整型消息类型,在其后立即跟随了消息数据。它指向的数据结构类似如下的定义,数据结构并不一定要如此定义,只要满足一个长整形后边跟随了消息数据的要求即可:
struct mymesg {
long mtype; /* positive message type */
char mtext[512]; /* message data,of length nbytes * /
};
于是,ptr就是一个指向 mymesg结构的指针。接收者可以使用消息类型以非先进先出的次序取消息。
- nbytes给出了跟在长整形后边的消息部分的长度
- flag的值可以指定为IPC_NOWAIT。这类似于文件 I/O的非阻塞 I/O标志。若消息队列已满(或者是队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值),则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。如果没有指定 IPC_NOWAIT,则进程阻塞直到:
- 有空间可以容纳要发送的消息,或
- 从系统中删除了此队列,或
- 捕捉到一个信号,并从信号处理程序返回。
在第二种情况下,返回 EIDRM(“标志符被删除”),最后一种情况则返回 EINTR。由于消息队列没有相应的引用计数,所以删除一个队列使得仍在使用这一队列的进程在下次对队列进行操作时出错返回。
3.msgrcv从队列中取用消息
- ptr参数指向一个长整型数(返回的消息类型存放在其中),跟随其后的是存放实际消息数据的缓存。
- nbytes说明数据缓存的长度。若返回的消息大于nbytes,而且在flag中设置了MSG_NOERROR,则该消息被截短(在这种情况下,不通知我们消息截短了).如果没有设置这一标志,而消息又太长,则出错返回 E2BIG(消息仍留在队列中)。
- 参数type使我们可以指定想要哪一种消息 :
- type == 0 返回队列中的第一个消息。
- type >0 返回队列中消息类型为type的第一个消息。
- type <0 返回队列中消息类型值小于或等于 type绝对值,而且在这种消息中,其类型值又最小的消息。非0type用于以非先进先出次序读消息。例如,若应用程序对消息赋优先权,那么 type就可以是优先权值。如果一个消息队列由多个客户机和一个服务器使用,那么 type字段可以用来包含客户机进程ID。
- flag的值为IPC_NOWAIT,使操作不阻塞。这使得如果没有所指定类型的消息,则msgrcv出错返回ENOMSG。如果没有指定IPC_NOWAIT,则进程阻塞直至
- 有了指定类型的消息,或
- 从系统中删除了此队列(出错返回 EIDRM),或
- 捕捉到一个信号并从信号处理程序返回(出错返回EINTR)。
2.msqid_ds结构
每个队列都有一个msqid_ds结构与其相关。此结构规定了队列的当前状态。两个指针msg-first和msg-last分别指向相应消息在内核中的存放位置,所以它们对用户进程而言是无价值的。结构的其他成员是自定义的。struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue */
struct msg *msg_last; /* last message in queue */
time_t msg_stime; /* last msgsnd time */
time_t msg_rtime; /* last msgrcv time */
time_t msg_ctime; /* last change time */
struct wait_queue *wwait;
struct wait_queue *rwait;
ushort msg_cbytes;
ushort msg_qnum;
ushort msg_qbytes; /* max number of bytes on queue */
ushort msg_lspid; /* pid of last msgsnd */
ushort msg_lrpid; /* last receive pid */
};
3.影响消息队列的系统限制
存在一些影响消息队列的限制
名字 | 说明 | 典型值 |
MSGMAX | 可发送的最长消息的字节长度 | 2048 |
MSGMNB | 特定队列的最大字节长度 (亦即队列中所有消息之和 ) | 4096 |
MSGMNI | 系统中最大消息队列数 | 50 |
MSGTOL | 系统中最大消息数 | 50 |
4.msgctl
msgctl函数对队列执行多种操作。它以及另外两个与信号量和共享存储有关的函数 (semctl和shmctl)是系统V IPC的类似于ioctl的函数。#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);返回:若成功则为 0,出错则为 -1
cmd参数指定对于由msqid规定的队列要执行的命令:
- IPC_STAT 取此队列的msqid_ds结构,并将其存放在 buf指向的结构中。
- IPC_SET 按由b u f指向的结构中的值,设置与此队列相关的结构中的下列四个字段:msg_perm.uid、msg_perm.gid、msg_perm;mode和msg_qbytes。此命令只能由下列两种进程执行:一种是其有效用户 ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。只有超级用户才能增加 msg_qbytes的值
- IPC_RMID 从系统中删除该消息队列以及仍在该队列上的所有数据。这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列进行操作时,将出错返回EIDRM。此命令只能由下列两种进程执行:一种是其有效用户ID 等于 msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。这三条命令(IPC_STAT、IPC_SET和IPC_RMID)也可用于信号量和共享存储。
二、信号量
1.基础概念及基本数据结构
信号量是一个计数器,用于多进程对共享数据对象的存取。为了获得共享资源,进程需要执行下列操作:- 测试控制该资源的信号量。
- 若此信号量的值为正,则进程可以使用该资源。进程将信号量值减 1,表示它使用了一个资源单位。
- 若此信号量的值为 0,则进程进入睡眠状态,直至信号量值大于 0。若进程被唤醒后,它返回至(第(1)步)。
常用的信号量形式被称之为二进制号量(binary semaphore)。它控制单个资源,其初始值为1。但是,一般而言,信号量的初值可以是任一正值,该值说明有多少个共享资源单位可供共享应用。
但是系统V的信号量与此相比要复杂得多。三种特性造成了这种并非必要的复杂性:
- 信号量并非是一个非负值,而必需将信号量定义为含有一个或多个信号量值的集合。当创建一个信号量时,要指定该集合中的各个值
- 创建信息量(semget)与对其赋初值(semctl)分开。这是一个致命的弱点,因为不能原子地创建一个信号量集合,并且对该集合中的所有值赋初值。
- 即使没有进程正在使用各种形式的系统 V IPC,它们仍然是存在的,所以不得不为这种程序担心,它在终止时并没有释放已经分配给它的信号量。
1.semid_ds结构
内核为每个信号量设置了一个 semid_ds结构。
struct semid_ds {struct ipc_perm sem_perm;
struct sem sem_base; /* ptr to first semaphore in set */
ushort sem_nsems; /* #of semaphores in set */
time_t sem_otime; /* last-semop() time */
time_t sem_ctime; /* last-change time */
};
对用户而言, sem_base指针是没有价值的,它指向内核中的 sem结构数组,该数组中包含了sem_nsems个元素,每个元素各对应于集合中的一个信号量值。
struct sem {
ushort semval; /*semaphore value ,always >= 0 */
pid_t sempid; /* pid for last operarion */
ushort semncnt ;/* # processes awaiting semval > current value */
ushort semzcnt ;/* # processes awaiting semval = 0 */
};
2.semget
semget函数用以获得一个信号量ID。#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag) ;返回:若成功则返回信号量 I D,若出错则为 - 1
创建一个新集合时,对 semid_ds结构的下列成员赋初值:
- 对 ipc_perm结构赋初值。该结构中的 mode被设置为flag中的相应许可权位。
- sem_otime设置为0。
- sem_ctime设置为当前时间。
- sem_nsems设置为nsems。
3.影响信号量集合的系统限制
系统中存在一些影响信号量的限制
名字 | 说明 | 典型值 |
SEMVMX | 任一信号量的最大值 | 32767 |
SEMAEM | 任一信号量的最大终止时调整值 | 16384 |
SEMMNI | 系统中信号量集的最大数 | 10 |
SEMMNS | 系统中信号量集的最大数 | 60 |
SEMMSL | 每个信号量集中的最大信号量数 | 25 |
SEMMNU | 系统中undo结构的最大数 | 30 |
SEMUME | 每个undo结构中的最大 undo项数 | 10 |
SEMOPM | 每个semop调用所包含的最大操作数 | 10 |
4.semctl
该函数包含了多种信号量操作。#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);
最后一个参数是个联合( union),而非指向一个联合的指针。
union semun {
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
ushort *array; /* for GETALL and SETALL */
};
cmd参数指定下列十种命令中的一种,使其在 semid指定的信号量集合上执行此命令。其中有五条命令是针对一个特定的信号量值的,它们用 semnum指定该集合中的一个成员。semnum值在0和nsems-1之间(包括0和nsems-1)
。
- IPC_STAT 对此集合取semid_ds结构,并存放在由 arg.buf指向的结构中。
- IPC_SET 按由arg.buf指向的结构中的值设置与此集合相关结构中的下列三个字段值:sem_perm.uid,sem_perm.gid和sem_perm.mode。此命令只能由下列两种进程执行:一种是其有效用户ID等于sem_perm.cuid或sem_perm.uid的进程;另一种是具有超级用户特权的进程。
- IPC_RMID 从系统中删除该信号量集合。这种删除是立即的。仍在使用此信号量的其他进程在它们下次意图对此信号量进行操作时,将出错返回 EIDRM。此命令只能由下列两种进程执行:一种是具有效用户 ID等于sem_perm.cuid或sem_perm.uid的进程;另一种是具有超级用户特权的进程。
- GETVAL 返回成员semnum的semval值。
- SETVAL 设置成员semnum的semval值。该值由arg.val指定。
- GETPID 返回成员semnum的sempid值。
- GETNCNT 返回成员semnum的semncnt值。
- GETZCNT 返回成员semnum的semzcnt值。
- GETALL 取该集合中所有信号量的值,并将它们存放在由arg.array指向的数组中。
- SETALL 按arg.array指向的数组中的值设置该集合中所有信号量的值。
5.semop
该函数自动执行信号量集合上的操作数组。#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[ ] , size_t nops);返回:若成功则为 0,若出错则为 -1
semoparray是一个指针,它指向一个信号量操作数组。
struct sembuf {
ushort sem_num;/ * member # in set ( 0 , ..., nsems - 1 */
short sem_op;/* operation */
short sem_flg;/* 0 IPC_NOWAIT, SEM_UNDO */
};
nops规定该数组中操作的数量(元素数)。
对集合中每个成员的操作由相应的 sem_op规定。此值可以是负值、0或正值。
- sem_op为正。这对应于返回进程占用的资源。 sem_op值加到信号量的值上。如果指定了undo标志,则也从该进程的此信号量调整值中减去 sem_op。
- 若sem_op为负,则表示要获取由该信号量控制的资源。如若该信号量的值大于或等于 sem_op的绝对值(具有所需的资源),则从信号量值中减去sem_op的绝对值。这保证信号量的结果值大于或等于 0。如果指定了 undo标志,则sem_op的绝对值也加到该进程的此信号量调整值上。如果信号量值小于sem_op的绝对值(资源不能满足要求),则:
- 若指定了IPC_NOWAIT,则出错返回EAGAIN;
- 若未指定IPC_NOWAIT,则该信号量的 semncnt值加1(因为将进入睡眠状态),然后调用进程被挂起直至下列事件之一发生:
- 此信号量变成大于或等于 sem_op的绝对值(即某个进程已释放了某些资源)。此信号量的semncnt值减1(因为已结束等待) ,并且从信号量值中减去sem_op的绝对值。如果指定了undo标志,则sem_op的绝对值也加到该进程的此信号量调整值上。
- 从系统中删除了此信号量。在此情况下,函数出错返回 ERMID。
- 进程捕捉到一个信号,并从信号处理程序返回,在此情况下,此信号量的 semncnt值减1(因为不再等待),并且函数出错返回EINTR.
- 若sem_op为0,这表示希望等待到该信号量值变成 0。如果信号量值当前是 0,则此函数立即返回。如果信号量值非0,则:
- 若指定了IPC_NOWAIT,则出错返回EAGAIN;
- 若未指定IPC_NOWAIT,则该信号量的 semncnt值加1(因为将进入睡眠状态),然后调用进程被挂起,直至下列事件之一发生:
- 此信号量值变成0。此信号量的semzcnt值减1(因为已结束等待)。
- 从系统中删除了此信号量。在此情况下,函数出错返回 ERMID。
- 进程捕捉到一个信号,并从信号处理程序返回。在此情况下,此信号量的 semzcnt值减1(因为不再等待),并且函数出错返回EINTR.semop具有原子性,因为它或者执行数组中的所有操作,或者一个也不做。exit时的信号量调整正如前面提到的,如果在进程终止时,它占用了经由信号量分配的资源,那么就会成为一个问题。无论何时只要为信号量操作指定了 SEM_UNDO标志,然后分配资源 (sem_op值小于0),那么内核就会记住对于该特定信号量,分配给我们多少资源( sem_op的绝对值)。当该进程终止时,不论自愿或者不自愿,内核都将检验该进程是否还有尚未处理的信号量调整值,如果有,则按调整值对相应量值进行调整。如果用带SETVAL或SETALL命令的semctl设置一信号量的值,则在所有进程中,对于该信号量的调整值都设置为0。
三、共享内存
共享存储允许两个或多个进程共享一给定的存储区。因为数据不需要在客户机和服务器之间复制,所以这是最快的一种 IPC。使用共享内存唯一需要注意的就是协同进程之间需要同步和互斥1.shmget
shmget用于获得一个共享存储标识符#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int flag);返回:若成功则为共享内存 I D,若出错则为 -1
当创建一个新共享内存时,初始化 shmid_ds结构的下列成员:
- ipc_perm结构。该结构中的 mode按flag中的相应许可权位设置。
- shm_lpid、shm_nattach、shm_atime、以及shm_dtime都设置为0。
- shm_ctime设置为当前时间。
2.shmid_ds结构
内核为每个共享内存设置了一个 shmid_ds结构。struct shmid_ds{
struct ipc_perm shm_perm;
int shm_segsz;
u_short shm_lpid;
u_short shm_cpid;
u_short shm_nattch;
time_t shm_atime;
time_t shm_dtime;
time_t shm_ctime;
};
3.影响共享存储的系统限制
系统中存在影响共享存储的一些限制
名字 | 说明 | 典型值 |
SHMMAX | 共享内存的最大字节数 | 131072 |
SHMMIN | 共享内存的最小字节数 | 1 |
SHMMNI | 系统中共享内存的最大段数 | 100 |
SHMSEG | 每个进程,共享内存的最大段数 | 6 |
4.shmctl
该函数对共享内存执行多种操作。#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf); 返回:若成功则为 0,若出错则为 -1
cmd参数指定下列5种命令中一种,使其在shmid指定的段上执行。
- IPC_STAT 获取该共享内存的shmid_ds结构信息,并存放在由buf指向的结构中。
- IPC_SET按 buf指向的结构中的值设置与此共享内存相关结构中的下列三个字段:shm_perm.uid、shm_perm.gid以及shm_perm.mode。此命令只能由下列两种进程执行:一种是其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程;另一种是具有超级用户特权的进程。
- IPC_RMID从系统中删除该共享内存。因为每个共享内存有一个连接计数(shm_nattch在shmid_ds结构中) ,所以除非使用该共享内存的最后一个进程终止或与该共享内存断开连接,否则不会实际上删除该共享内存。不管此共享内存是否仍在使用,该共享内存标识符立即被删除,所以不能再用shmat与该共享内存连接。此命令只能由下列两种进程执行 :一种是其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程;另一种是具有超级用户特权的进程。
- SHM_LOCK 锁住共享内存。此命令只能由超级用户执行。
- SHM_UNLOCK 解锁共享内存。此命令只能由超级用户执行。
5.shmat,shmdt
该函数用于将创建的共享内存连接到进程自己的地址空间中。#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void * shmat(int shmid, void *addr, int flag); 返回:若成功则为指向共享内存的指针,若出错则为 -1
int shmdt(void *addr) ;*返回:若成功则为 0,若出错则为 - 1
共享内存连接到调用进程的哪个地址上与addr参数以及在flag中是否指定SHM_RND位有关。
- 如果addr为0,则此共享内存连接到由内核选择的第一个可用地址上。
- 如果addr非0,并且没有指定SHM_RND,则此共享内存连接到addr所指定的地址上。
- 如果addr非0,并且指定了 SHM_RND,则此共享内存连接到( addr-(addr mod SHMLBA))所表示的地址上。SHM_RND命令的意思是:取整。 SHMLBA的意思是:低边界地址倍数,它总是2的乘方。该算式是将地址向下取最近 1个SHMLBA的倍数。除非只计划在一种硬件平台上运行应用程序,否则不用指定共享内存所连接到的地址。所以一般应指定 addr为0,以便由内核选择地址。如果在flag中指定了SHM_RDONLY位,则以只读方式连接此共享内存。否则以读写方式连接此段。shmat的返回值是该共享内存所连接的实际地址,如果出错则返回-1。
addr参数是以前调用shmat时的返回值。