信号量、共享内存和消息队列
IPC(Inter-Process Communication,进程间通信)机制
信号量: 用于关联对资源的访问
共享内存: 用于在程序之间高效地共享数据
消息队列: 在程序之间传递数据的一种简单方法
IPC信号量
用于管理对资源的访问。为了防止出现多个程序同时访问一个共享资源而引发的问题,我们需要有一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。
IPC中的信号量和POSIX中的信号量
在12章所讨论的,信号量是一个特殊的变量,它只取正整数值,并且程序对其访问都是原子操作。在本章中,我们将对这个较早的简化定义做出进一步的解释。
信号量的一个更正式的定义是:它是一个特殊变量,只允许对它进行P操作和V操作,其中
- P(sv):如果sv的值大于零,就给它减1;如果等于零,就挂起该进程的执行状态。
- V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行;如果没有进程因为等待sv而被挂起,就给它加1
理论操作:
两个进程共享信号量变量sv。一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区域(独占式的执行代码区域,在这个区域中只有一个执行进程)。而第二个进程将被阻止进入临界区域,因为当它试图进行P(sv)操作时将被挂起,等待第一个进程离开临界区域并执行V(sv)操作释放信号量。
三个函数:
- semget用于创建一个新的信号量或取得一个已有信号的键。
int semget(key_t key, int nsems, int semflg);
键,信号量个数,创建类型向创建文件一样,0666|IPC_CREAT semop用于改变信号量的值
int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf { unsigned short sem_num; /* semaphore number */ 除非操作一组信号量,否则为0 short sem_op; /* semaphore operation */ 操作值 short sem_flg; /* operation flags */ IPC_NOWAIT and SEM_UNDO. };
semctl用于初始化或删除信号量
int semctl(int semid, int semnum, int cmd, ...);
id,个数,参数4依赖于,参数三。例如:
/*添加信号量*/
union semun sem_union;
sem_union.val = 1;
semctl(sem_id,0,SETVAL,sem_union)
/*删除信号量*/
union semun sem_union;
semctl(sem_id,0,IPC_RMID,sem_union)
- 我们需要特别小心,不要在无意之中在程序执行结束后还留下信号量未删除。它可以会在你下次运行次程序时引发问题,而且信号量也是一种有限的资源,需要节约使用。
感觉:信号量一脸懵逼。NO,信号量就是向系统申请构建一个资源,用于不同的进程的共享。然后通过操作这个资源的标记,来判断资源归属那个进程调用。
共享内存
共享内存是3个IPC机制只的第二个,它允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方式。但是它未提供同步机制,所有我们通常需要用其它的机制来同步对共享内存的访问。
四个函数:
- shmget用于创建一个共享内存
int shmget(key_t key, size_t size, int shmflg);
- 第一次创建共享内存段时,它不能被任何进程访问。要想启用对共享内存的访问,必须将其连接到一个进程的地址空间中,这项工作由shmat函数来完成。
void *shmat(int shmid, const void *shmaddr, int shmflg);
共享地址id,共享内存地址(如果NULL,则有系统分配),共享内存选项。成功返回地址,失败返回(void*)-1,并设置errno - shmdt函数的作用是将共享内存从当前进程中分离 ,但并未删除,只是使共享内存对当前进程不可在用。
int shmdt(const void *shmaddr);
成功返回0,失败-1,并设置errno - shmctl共享内存控制函数,可以用于共享内存的删除。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
共享内存id,cmd:如IPC_RMID,最后参数是一个指针,它指向包含共享内存模式和访问权限的结构。NULL
消息队列
什么是消息队列
消息队列是一种进程间通信或同一进程的不同线程之间的通信方式,消息队列提供一种机制,使得一个进程可以发送消息到一个队列中,然后另一个进程从此队列中取出数据,与管道不同的时,消息队列依附于内核,即可以让两个进程不必同时存在——在一个进程发送一个消息后退出,另外一个进程可以在数天之后获得。前提:共享内存资源不被destroy。优缺点
消息队列本身是异步的,它允许接收者在消息发送很长时间后在取回数据。但消息队列的异步特点,也造成了一个缺点,就是接收者必须轮询消息队列,才能收到最近的消息。而共享内存则是会接受到最新的消息,但没有异步机制,和信号相比,消息队列能够传递更多的信息。与管道相比,消息队列提供了有格式的数据,这可以减少开放人员的工作量。但消息队列仍然有大小限制。使用
msgget函数来创建和访问一个消息队列:int msgget(key_t key, int msgflg);
访问同一个消息队列的key值,msgflg为文件的权限标志在加一个IPC_CREAT标志,成功返回正整数,即队列标志符,失败返回-1.
msgsnd函数用来将消息添加到消息队列中:int msgsnd(int msgid, const void *msg_str, size_t msg_sz, int msgflg);
消息的结构收到两方面的约束。首先,它的长度必须小于系统上限;其次,它必须以一个长整形成员变量开始,接收函数将用这个成员变量来确定消息的类型。建议将消息类型定义为:
struct my_message { long int message_type; /* message type, must be > 0*/ /* content */ };
参数的分析:msgid,msgget的返回值;msg_str是准备发送消息的指针;msg_gz消息长度,不包括长整形变量的大小;msgflg控制在当前消息队列满或队列消息到达系统范围的限制时将要发送的事情。成功返回0失败返回-1并设置errno
msgrcv函数从一个消息队列中获取消息:int msgrcv(int msgid, void *msg_ptr, size_t msg_size, long int msgtype, int msgflg)
前三个函数和msgsnd相同,msgtype参数表明要接受队列中的和msgtype类型相同的消息。如果为0则读取队列中的第一个可用消息,如果这个值大于0,则获取具有相同数据类型值的第一个消息。如果小于0,将获得消息类型等于或小于msgtype的绝对值的第一个消息。msgflg,用于控制队列中没有相同类型的消息可以接收时而发生的事情。
msgctl函数,作用与共享内存的控制函数非常相似:int msgctl(int msgid, int command ,struct msgid_ds *buf);
arg1:msgget的返回值;command是将要采取的动作:IPC_RMID 删除消息队列;arg3:定义了当前消息队列的有关信息。如:消息队列的权限,发送时间,接受时间等等。
在编写消息队列时遇到的问题:
1. 权限不足
查看了自己的参数是正确,最后换了一个key_t的值,问题解决。原因为了方便,直接使用的自定义数字。
2. 非法参数。
- 在msgsnd时,Invalid argument,查看参数,long mtype的值必须大于0,消息的长度为实际发送数据的大小,不包含long type的大小。
- 在msgrcv时,同样消息的长度要确定。最后一个参数不知道怎么用就不要使用,补0
- 数值交互
使用sigsnd发送指定类型数据时,需要指定long type的值,且值必须大于0.
使用sigrcv接受指定类型数据时,也需要指定long type的值。值任任意。可正可负,可为0 - 最后一点,当消息队列被删除时,数据也就清空了,所以消息队列只用于短时间的数据交流
IPC状态命令
IPC机制一个让人烦恼的问题:编写错误的程序或因为某些原因而执行失败的程序将把它的IPC资源遗留在系统中,并且这些资源在程序结束后很长时间仍留在系统。这将导致对程序的新调用执行失败,因为程序期望以一个干净的系统来启动。IPC状态命令(ipcs)和删除命令(ipcrm)提供了一种检测和情理IPC机制的方法。
- 使用ipcs
会输出共享内存,信号量,消息队列的全部资源的状态
- 对应选项的-s -m -q
分别对应着信号量,共享内存,消息队列的输出。
- 还有一个选项-p
输出资源对应的PID
- 输出共享资源对应的执行程序
ps -p `ipcs -mp | tr -s ' ' | cut -d ' ' -f 3| sort | uniq | tr -d 'A-z' | tr '\n' ' '`