linux进程间通信–消息队列(POSIX 版本)
参考文件:man mq_overview
。
由于System V
消息存在一些缺点。因此POSIX标准又重新定义了一套消息队列接口。下面将详细介绍POSIX接口下的消息队列。
消息队列有如下的特点:
- 适用于任何进程将数据交互。
- 以消息为单位进行数据交互。
- 可以使用异步的信号通知。
- POSIX消息队列的每个消息都可以设置优先级。范围为0 ~ MQ_PRIO_MAX-1。值越大优先级越高。
消息队列与系统相关的一些杂项说明:
- 系统会限制所有的消息队列最多可消耗的系统空间。由RLIMIT_MSGQUEUE宏决定。可通过getrlimit()函数获取。
- 有关消息队列的相关参数可以查看
/proc/sys/fs/mqueue/
目录下的文件。如cat msg_max
.- msg_default:默认的消息数。当mq_open()时attr参数等于NULL,则mq_attr.mq_maxmsg属性使用此值。默认为10.
- msg_max:消息队列最大数量的上限值。即mq_attr.mq_maxmsg的最大值
- msgsize_default:消息队列中每个消息的默认消息大小。当mq_open()时attr参数等于NULL,则mq_attr.mq_msgsize属性使用此值。默认为8192.
- msgsize_max:消息队列中每个消息的最大值。mq_attr.mq_msgsize的最大值。
- queues_max:系统可创建的消息队列上限值。默认256.
- 消息队列默认创建的路径为
/dev/mqueue
.如果没有,可以使用下面的命令挂载(需要root权限):mkdir /dev/mqueue mount -t mqueue none /dev/mqueue
消息队列属性结构体介绍:在mq_open(),mq_getattr(),mq_setattr()等函数都会用到。
//消息队列属性结构体,
struct mq_attr {
long mq_flags; //0 or O_NONBLOCK
long mq_maxmsg; //最大的消息数
long mq_msgsize; //每个消息最大的字节数
long mq_curmsgs; //队列中的当前消息数
};
消息队列相关函数介绍:
-
mqd_t mq_open(const char *mqName, int oflags, ...)
- 创建或打开一个消息队列。
- mqName:消息队列名称.格式如
/xxx
。由/
开始,并且只能有一个/
。如/mq_test
. - oflags:表示打开的方式.可以是下面的各种组合
- O_RDONLY.只读
- O_WRONLY.只写
- O_RDWR. 读写
- O_CREAT. 如果不存在则创建
- O_EXCL. 配合O_CREAT使用,只有不存在时才创建。存在的返回失败
- O_NONBLOCK. 不阻塞
- 可选参数:当
oflags & O_CREAT == true
时使用- mode:权限。如
0666
- attr:指向用于初始化的
mq_attr
的指针。用于设置mq_maxmsg属性和mq_msgsize属性。其他属性忽略。
- mode:权限。如
- 注意:在创建消息队列时,mq_maxmsg和mq_msgsize属性会被确定,后期无法通过mq_setattr()设置。
- return:消息队列描述符或-1(错误)
-
int mq_close(mqd_t mqdes)
- 关闭一个消息队列.和文件的close类型一样,关闭后,消息队列并不从系统中删除。一个进程结束,会自动调用关闭打开着的消息队列。
- mqdes:队列描述符
- return:if true:0,if false:-1.
-
int mq_unlink(const char *mqName)
- 删除一个消息队列。
- return:if true:0,if false:-1.
-
int mq_send(mqd_t mqdes, const void *msg, size_t msglen, int prio)
- 发送一个消息msg到消息队列mqdes。如果消息队列已满,并且消息队列没有设置O_NONBLOCK,则mq_send()将阻塞,直到消息队列中有了可用的空间。如果消息队列已满且设置了O_NONBLOCK,则不会对消息进行排队并返回ERROR。
- msg:消息内容
- msglen:消息内容大小(byte)。最大为mq_getattr()->mq_msgsize。
- prio:优先级.范围为0 ~ MQ_PRIO_MAX-1。值越大优先级越高。
- return:if true:0,if false:-1.errno如下:
- EAGAIN:队列非阻塞,并且已满
- EMSGSIZE:消息长度大于最大长度。
- EINTR:由信号中断返回。
- EPERM:没有权限
-
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len,unsigned msg_prio, const struct timespec *abstime);
- 功能类似mq_send().只是在没有设置O_NONBLOCK,当队列满后超过等待时间则返回。
- return:if true:0,if false:-1.errno除了mq_send()提到的,还有如下:
- ETIMEDOUT:超时返回
-
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio)
- 从消息队列mqdes接收消息。如果消息队列为空,并且消息队列没有设置O_NONBLOCK,则mq_receive()将阻塞,直到消息队列中有了可用的空间。如果消息队列为空且设置了O_NONBLOCK,则直接返回ERROR。
- msg_ptr:要接收消息的buff.msg_len:为buff的长度。如果这个长度小于消息本身的长度,则返回错误。
- msg_prio:接收消息本身的优先级。
- return:if true:接收到的消息大小,if false:-1.errno类似mq_send()。
-
ssize_t mq_timedreceive(mqd_t mqdes, char *restrict msg_ptr,size_t msg_len, unsigned *restrict msg_prio,const struct timespec *restrict abstime)
- 功能类似mq_receive().只是在没有设置O_NONBLOCK,当队列空后超过等待时间则返回。
- abstime:绝对时间
- return:类似mq_receive()。
-
int mq_getattr(mqd_t mqdes, struct mq_attr *mqstat)
- 获取息队列关联的状态信息和属性。
- mqstat:获取信息的buff.
- return:if true:0,if false:-1.
-
int mq_setattr(mqd_t mqdes, const struct mq_attr *restrict mqstat,struct mq_attr *restrict omqstat)
- 设置消息队列的属性。只能更改mq_flags的O_NONBLOCK位。其他的属性会被忽略
- mqdes:消息队列描述符
- mqstat:要设置的值buff.
- omqstat:如果不为NULL,则将先前的存在此buff中。
- return:if true:0,if false:-1.
消息队列的异步通知
当一个消息队列从空变成非空时,调用进程可以接收一个通知信号。从而达到异步触发的效果。mq_notify()函数用于注册通知到消息队列。需要注意:
- 一个消息队列只能被一个进程注册通知. 如果之前调用进程或者其他进程已经注册过了,那么随后调用本函数注册通知会返回失败.如果mq_notify()函数的参数notification是NULL并且这个进程之前注册过了通知函数, 那么这个注册会被删除,其他进程就可以在这个消息队列上注册通知.
- 当一个消息队列从空变成非空,并且这个消息队列注册了通知,并且一些线程被函数mq_receive()或者mq_timedreceive() 阻塞了,此时到达的消息会发送给mq_receive()或者mq_timedreceive(),但不会发送通知(结果就像消息队列仍然是空的).
消息通知使用到的函数如下:
int mq_notify(mqd_t mqdes, const struct sigevent *notification)
- 一个消息队列从空变成非空时,通知进程可以接收一条消息.如果参数notification不为空, 这个函数会给调用进程注册一个异步通知函数,通知注册进程空的消息队列中有消息到达了.当消息队列从空到非空状态变化时,通知就会发送到注册的进程.
- notification: 用来注册通知,当该参数被设置为NULL时,会取消调用进程之前注册的通知。结构体介绍见下面:
- return:if true:0,if false:-1.errno如下:
- EBADF参数mqdes不是有效的消息队列描述符.
- EBUSY:一个进程已经在这个消息队列上注册了通知函数.
- EINVAL:参数notification 是NULL 并且 当前进程没有注册通知函数.
消息队列的异步通知结构体如下:
struct sigevent {
int sigev_notify; //通知类型
int sigev_signo; //Signal number.
union sigval sigev_value; //Signal value.
void (*sigev_notify_function)(union sigval); //Notification function.
pthread_attr_t *sigev_notify_attributes; //Notification attributes.
};
union sigval{
int sival_int;
void *sival_ptr;
};
结构体的各个字段介绍如下:
- sigev_notify:通知类型,有如下几种类型
- SIGEV_NONE:事件发生时什么也不做。
- SIGEV_SIGNAL:事件发生时,将sigev_signo 指定的信号(A queued signal)发送给指定的进程.
- SIGEV_THREAD:事件发生时,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,并且让它执行sigev_notify_function,传入sigev_value作为为一个参数.
- sigev_signo:在sigev_notify = SIGEV_SIGNAL 时使用,指定信号的种别(number).
- sigev_value:在sigev_notify = SIGEV_THREAD 时使用,作为sigev_notify_function的参数.结构如下
- (*sigev_notify_function)(union sigval):函数指针(指向通知执行函数),在sigev_notify = SIGEV_THREAD 时使用, 其他情况下置为NULL.
- sigev_notify_attributes:指向线程属性的指针,在sigev_notify = SIGEV_THREAD 时使用,指定创建线程的属性, 其他情况下置为NULL.
消息队列的使用例程如下。编译时需要用到-lrt
库。运行时需要root权限。
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <mqueue.h>
#include <string.h>
//共享数据结构体
struct shm_data{
int count;
char buff[32];
};
//消息队列写进程
void process_write(void)
{
struct shm_data data_obj;
struct mq_attr attr;
//1. 消息队列属性设置
attr.mq_maxmsg = 16;
attr.mq_msgsize = sizeof(data_obj);
//2. 创建一个消息队列
mqd_t mq_id = mq_open("/mq_test", O_CREAT | O_RDWR, 0666, &attr);
if(mq_id == -1){
printf("mq_open error(%d)\n",errno);
exit(1);
}
int count = 10;
while(count--){
data_obj.count = count;
sprintf((char *)data_obj.buff,"hello %02d\n",count);
//3. 消息对了发送
int res = mq_send(mq_id, (char *)&data_obj, sizeof(data_obj), 0);
if(res == 0){//send true
printf("mq_send %d\n",count);
sleep(1);
}
else{
int errno_is = errno;
printf("mq_send error %d\n",errno_is);
if(errno_is == EINTR){//信号中断
}
else if(errno_is == EAGAIN){队列已满
}
}
}
//4. 关闭消息队列
if(mq_close(mq_id) == -1){
printf("mq_close error(%d)\n",errno);
exit(1);
}
exit(0);
}
//消息队列读进程
void process_read(void)
{
struct shm_data data_obj;
//1. 打开消息队列
mqd_t mq_id = mq_open("/mq_test", O_RDONLY, 0, NULL);
if(mq_id == -1){
printf("mq_open read error(%d)\n",errno);
exit(1);
}
//2. 获取消息队列相关属性
struct mq_attr attr;
if(mq_getattr(mq_id, &attr) == -1){
printf("mq_getattr error(%d)\n",errno);
exit(1);
}
printf("attr->mq_msgsize(%ld)\n",attr.mq_msgsize);
printf("attr->mq_maxmsg(%ld)\n",attr.mq_maxmsg);
printf("attr->mq_curmsgs(%ld)\n",attr.mq_curmsgs);
while(1){
//3. 等待接收
ssize_t num = mq_receive(mq_id, (char *)&data_obj, sizeof(data_obj), NULL);
if(num >= 0){//receive true
printf("mq_receive:count: %d,%s",data_obj.count, data_obj.buff);
if(data_obj.count == 0){
break;
}
}
else{
int errno_is = errno;
if(errno_is == EINTR){//信号中断
printf("queue interrupt\n");
}
else{
printf("mq_receive error %d:%s\n",errno_is,strerror(errno_is));
break;
}
}
}
//4. 关闭消息队列
if(mq_close(mq_id) == -1){
printf("mq_close error(%d)\n",errno);
exit(1);
}
//5. 删除消息队列
if(mq_unlink("/mq_test") == -1){
printf("mq_unlink error(%d)\n",errno);
exit(1);
}
exit(0);
}
void main(void)
{
pid_t pid = fork();
if(pid == 0){//子进程
printf("child process runing(%d)\n",getpid());
sleep(2);
process_read();
}
else{//父进程
printf("parent process runing(%d)\n",getpid());
process_write();
}
}
关于技术交流
此处后的文字已经和题目内容无关,可以不看。
qq群:825695030
微信公众号:嵌入式的日常
如果上面的文章对你有用,欢迎打赏、点赞、评论。