前言
接下讨论的IPC机制,它们最初由System V版本的Unix引入。由于这些机制都出现在同一个版本中并且有着相似的编程接口,所以它们被称为System V IPC机制。接下来的内容包括:
信号量:用于管理对资源的访问。
共享内存:用于在程序之间高效地共享数据。
消息队列:在程序之间传递数据。
操作系统中的同步和异步:https://blog.csdn.net/qq_38289815/article/details/81012826
进程间通信:管道和命名管道(FIFO) https://blog.csdn.net/qq_38289815/article/details/104742682
进程间通信:信号量 https://blog.csdn.net/qq_38289815/article/details/104762940
进程间通信:共享内存 https://blog.csdn.net/qq_38289815/article/details/104776076
消息队列
消息队列与命名管道有许多相似之处,但少了在打开和关闭管道方面的复杂性。但使用消息队列并未解决我们在使用命名管道时遇到的一些问题,如管道满时的阻塞问题。消息队列提供了一种在两个不相关的进程之间传递数据的简单且有效的方法。与命名管道相比,消息队列的优势在于,它独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难。
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。优点:我们可以通过发送消息来避免命名管道的同步和阻塞问题;可以用一些方法来提前查看紧急消息。缺点:消息队列与命名管道一样,每个数据块都有一个最大长度的限制,系统中所有队列所包含的全部数据块的总长度也有一个上限。Linux系统用宏定义MSGMAX和MSGMNB来表示一条消息的最大长度和一个队列的最大长度。
消息队列函数的定义如下:
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msgid_ds *buf);
int msgget(key_t key, int msgflg);
int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgget()函数
msgget函数用来创建和访问一个消息队列。它的原型为:
int msgget(key_t, key, int msgflg);
成功时返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1。
与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
msgsnd()函数
msgsnd函数用来把消息添加到消息队列中。它的原型为:
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
成功时函数返回0,失败时返回-1。如果调用成功,消息数据的一份副本将被放到消息队列中。
msgid是由msgget函数返回的消息队列标识符。
msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体(还有长度必须小于系统规定的上限),接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:
struct my_message {
long int message_type;
/* The data you wish to transfer */
};
msg_sz是msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。
msgflg 用于控制在消息队列满或队列消息达到系统范围的限制时将要发生的事情。如果msgflg中设置IPC_NOWAIT状态,函数将立刻返回,不发送消息并且返回值为-1。如果msgflg中IPC_NOWAIT标志被清除,则发送进程将挂起以等待队列中腾出可用空间。
msgrcv()
msgrcv函数用来从一个消息队列获取消息,它的原型为:
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。
msgid, msg_ptr, msg_st 的作用和函数msgsnd函数的一样。
msgtype 可以实现一种简单的接收优先级。如果msgtype的值为0,就获取队列中的第一个可用消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。
msgflg 用于控制当队列中没有相应类型的消息可以接收时将发生的事情。如果msgflg中设置IPC_NOWAIT状态,函数将立刻返回,返回值为-1。如果msgflg中IPC_NOWAIT标志被清除,则发送进程将挂起以等待队列中腾出可用空间。
msgctl()
msgctl函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
int msgctl(int msgid, int command, struct msgid_ds *buf);
成功时返回0,失败时返回-1。
msgid是由msgget返回的消息队列标识符。
command是将要采取的动作,它可以取3个值:
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列
buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:
struct msgid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
使用消息队列完成进程间通信
由于可以让不相关的进程完成通信,所以我们在这里将编写两个程序,msg1.c用于接收消息,msg2.c用于发送消息。允许两个程序都可以创建消息队列,但只有接收者在接收完最后一个消息之后删除它。
#include <stdio.h> //msg1.c
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
struct msg_st
{
long int my_msg_type;
char text[BUFSIZ];
};
int main(int argc, char **argv)
{
int msgid;
struct msg_st data;
long int msg_to_receive = 0;
// 建立消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if (msgid == -1)
{
fprintf(stderr, "msgget failed width error: %d\n", errno);
exit(EXIT_FAILURE);
}
// 从队列中获取消息,直到遇到end消息为止
while (1)
{
if (msgrcv(msgid, (void *)&data, BUFSIZ, msg_to_receive, 0) == -1)
{
fprintf(stderr, "msgrcv failed width erro: %d", errno);
}
printf("You wrote: %s", data.text);
// 遇到end结束
if (strncmp(data.text, "end", 3) == 0)
{
break;
}
}
// 删除消息队列
if (msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
}
exit(EXIT_SUCCESS);
}
#include <stdlib.h> //msg2.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <errno.h>
#define MAX_TEXT 512
struct msg_st
{
long int my_msg_type;
char text[MAX_TEXT];
};
int main(int argc, char **argv)
{
struct msg_st data;
char buffer[BUFSIZ];
int msgid = -1;
// 建立消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if (msgid == -1)
{
fprintf(stderr, "msgget failed error: %d\n", errno);
exit(EXIT_FAILURE);
}
// 向消息队里中写消息,直到写入end
while (1)
{
printf("Enter some text: \n");
fgets(buffer, BUFSIZ, stdin);
data.my_msg_type = 1;
strcpy(data.text, buffer);
// 向队列里发送数据
if (msgsnd(msgid, (void *)&data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsnd failed\n");
exit(EXIT_FAILURE);
}
// 输入end结束输入
if (strncmp(buffer, "end", 3) == 0)
{
break;
}
sleep(1);
}
exit(EXIT_SUCCESS);
}
实验解析
发送者程序通过msgget来创建一个消息队列,然后用msgsnd向队列中增加消息。接收者用msgget获得消息队列标识符,然后开始接收消息,直到接收到特殊的“end”为止。然后它用msgctl来删除消息队列以完成清理工作。