mq_send mq_receive
6.当调用mq_notify但是队列不为空时,通知不会发送;当队列变为空,并且有一个消息入队时,才发送通知
下面是使用内存映射实现的消息队列,消息队列的数据结构如下:
以下是UNPv2上的实现
/*
_______________
| mq_attr |
|_______________|
| mqh_head |
| mqh_free |
| mgh_nwait |
| mgh_pid |
|_______________ |
| mqh_enevt |
|_______________ |
| mqh_lock |
|______________ |
| mqh_hdr |
| 数据 |
|————————|
| mqh_hdr |
| 数据 |
|________________|
| .......... |
|_______________|
*/
#include
#include
#include
#include
#include
#include
//取得文件stat结构的最大尝试次数,
#define MAX_TRIES 10
//不同系统对mode_t类型有不同的定义,这里统一
#ifdef __bsdi__
#define va_mode_t int
#else
#define va_mode_t mode_t
#endif
typedef struct mq_info *mqd_t;
//定义消息的头部
struct msg_hdr{
//指示下一个消息的索引下标
long msg_next;
//消息的俄实际长度
ssize_t msg_len;
//消息的优先级
unsigned int msg_prio;
};
//定义消息队列属性
struct mq_attr {
//消息队列的属性(只实现了O_NONBLOCK)
long mq_flags;
//消息队列上的消息的最大容量
long mq_maxmsg;
//每个消息的大小
long mq_msgsize;
//队列上的当前消息数
long mq_curmsgs;
};
//定义消息队列的头部
struct mq_hdr{
//消息队列属性
struct mq_attr mqh_attr;
//第一个消息的下标索引
long mqh_head;
//队列中的第一个空闲消息的下标索引
long mqh_free;
//阻塞在mq_receive()的进程数
long mqh_nwait;
//注册当前进程的pid
pid_t mqh_pid;
//注册到队列上的事件
struct sigevent mqh_event;
//消息队列的互斥锁
pthread_mutex_t mqh_lock;
//消息队列的条件锁
pthread_cond_t mqh_wait;
};
//定义POSIX消息队列的描述符
struct mq_info{
//消息队列的头部指针,()即指向消息队列的首地址
struct mq_hdr *mqi_hdr;
//魔数
long mqi_magic;
//当前进程的打开标志
int mqi_flags;
};
#define MQI_MAGIC 0x23653125
//内存long对齐,使分配的字节数是long大小的整数倍,方便内存操作
#define MSGSIZE(i) ((((i)+sizeof(long)-1)/sizeof(long))%sizeof(long))
//定义默认属性
struct mq_attr defattr = { 0, 128, 1024, 0 };
//打开或创建一个消息队列
mqd_t mq_open(const char *pathname, int oflag, ...)
{
int i, fd, nonblock, created, save_errno;
long msgsize, filesize, index;
va_list ap;
mode_t mode;
int8_t *mptr;
struct stat statbuff;
struct mq_hdr *mqhdr;
struct msg_hdr *msghdr;
struct mq_attr *attr;
struct mq_info *mqinfo;
pthread_mutexattr_t mattr;
pthread_condattr_t cattr;
created = 0;
//加入非阻塞标志
nonblock = oflag & O_NONBLOCK;
oflag &= ~O_NONBLOCK;
//字节指针
mptr = (int8_t *) MAP_FAILED;
mqinfo = NULL;
again:
if (oflag & O_CREAT) {//指定了创建标志
va_start(ap, oflag);
//取消的模式中的用户执行权限标志S_IXUSR,为了避免竞争条件,规定指定S_IXUSR的进程负责初始化消息队列
mode = va_arg(ap, va_mode_t) & ~S_IXUSR;
attr = va_arg(ap, struct mq_attr *);
va_end(ap);
//在创建的时候指定S_IXUSR标志
fd = open(pathname, oflag | O_EXCL | O_RDWR, mode | S_IXUSR);
if (fd < 0) {
//需要打开的文件已存在,并且没有指定排他性标志(O_EXCL),则可以直接以O_RDWR方式打开
if (errno == EEXIST && (oflag & O_EXCL) == 0)
goto exists;
else
return((mqd_t) -1);
}
//标志文件已创建
created = 1;
//没有指定属性,则使用默认属性
if (attr == NULL)
attr = &defattr;
else {
//将查指定的消息队列属性
if (attr->mq_maxmsg <= 0 || attr->mq_msgsize <= 0) {
errno = EINVAL;
goto err;
}
}
//内存对齐
msgsize = MSGSIZE(attr->mq_msgsize);
//计算内存对齐以后需要创建文件的实际大小
filesize = sizeof(struct mq_hdr) + (attr->mq_maxmsg *
(sizeof(struct msg_hdr) + msgsize));
//定位文件,即是指定文件大小
if (lseek(fd, filesize - 1, SEEK_SET) == -1)
goto err;
if (write(fd, "", 1) == -1)
goto err;
//以读、写、共享方式作内存映射
mptr = mmap(NULL, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (mptr == MAP_FAILED)
goto err;
//为消息队列分配一个struct mq_info结构,并初始化
if ( (mqinfo = malloc(sizeof(struct mq_info))) == NULL)
goto err;
mqinfo->mqi_hdr = mqhdr = (struct mq_hdr *) mptr;
mqinfo->mqi_magic = MQI_MAGIC;
mqinfo->mqi_flags = nonblock;
//初始化消息队列的属性
mqhdr->mqh_attr.mq_flags = 0;
mqhdr->mqh_attr.mq_maxmsg = attr->mq_maxmsg;
//还是使用为对齐前,用户指定的大小来初始化
mqhdr->mqh_attr.mq_msgsize = attr->mq_msgsize;
mqhdr->mqh_attr.mq_curmsgs = 0;
//无进程阻塞在当前队列
mqhdr->mqh_nwait = 0;
mqhdr->mqh_pid = 0;
//当前队列无消息
mqhdr->mqh_head = 0;
//得到第一个消息的地址,(因为struct mq_hdr结构后面就是消息结构)
index = sizeof(struct mq_hdr);
mqhdr->mqh_free = index;
//将消息链起来
for (i = 0; i < attr->mq_maxmsg - 1; i++) {
msghdr = (struct msg_hdr *) &mptr[index];
index += sizeof(struct msg_hdr) + msgsize;
msghdr->msg_next = index;
}
msghdr = (struct msg_hdr *) &mptr[index];
//最后一个消息队列的next下标为0,即他就是最后一个
msghdr->msg_next = 0;
//初始化互斥锁属性
if ( (i = pthread_mutexattr_init(&mattr)) != 0)
goto pthreaderr;
//设置互斥锁为共享,之后不同进程之间就能使用这个锁了
pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
i = pthread_mutex_init(&mqhdr->mqh_lock, &mattr);
if (i != 0)
goto pthreaderr;
//和互斥锁的操作一样
if ( (i = pthread_condattr_init(&cattr)) != 0)
goto pthreaderr;
pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
i = pthread_cond_init(&mqhdr->mqh_wait, &cattr);
if (i != 0)
goto pthreaderr;
//此时初始化完成,恢复用户指定的打开文件模式
if (fchmod(fd, mode) == -1)
goto err;
//关闭文件,返回消息队列的描述符
close(fd);
return((mqd_t) mqinfo);
}
//对错误处理
//文件已存在
exists:
if ( (fd = open(pathname, O_RDWR)) < 0) {
//发生了竞争条件,如一个进程open文件后在lseek是阻塞,另一个进程open时发现文件已存在,到这里再次open,并且阻塞,第一个进程开始执行lseek出错,关闭文件并删除了文件,因此第二个进程执行时就会发生错误,如果本次用户执行的mq_open指定了O_CREAT标志,则回到前面再次执行
if (errno == ENOENT && (oflag & O_CREAT))
goto again;
goto err;
}
//这也有一个竞争条件,如果上面和下面执行之间有其他进程执行出错(如mmap出错)就会执行错误处理,删除文件,这里使用stat调用取得文件的属性,如果文件不存在会返回文件不存在的错误(ENOENT),可以判断这个错误,返回到前面重新执行
for (i = 0; i < MAX_TRIES; i++) {
if (stat(pathname, &statbuff) == -1) {
if (errno == ENOENT && (oflag & O_CREAT)) {
close(fd);
goto again;
}
goto err;
}
//如果没有指定文件的S_IXUSR标志,则说明他不负责初始化,消息队列已经初始化好了
if ((statbuff.st_mode & S_IXUSR) == 0)
break;//
//否则等待一秒,看是否初始化成功
sleep(1);
}
if (i == MAX_TRIES) {
errno = ETIMEDOUT;
goto err;
}
//将文件映射到自己进程空间
filesize = statbuff.st_size;
mptr = mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mptr == MAP_FAILED)
goto err;
close(fd);
//分配struct mq_info初始化并返回
if ( (mqinfo = malloc(sizeof(struct mq_info))) == NULL)
goto err;
mqinfo->mqi_hdr = (struct mq_hdr *) mptr;
mqinfo->mqi_magic = MQI_MAGIC;
mqinfo->mqi_flags = nonblock;
return((mqd_t) mqinfo);
pthreaderr:
errno = i;
err:
save_errno = errno;
if (created)
unlink(pathname);
if (mptr != MAP_FAILED)
munmap(mptr, filesize);
if (mqinfo != NULL)
free(mqinfo);
close(fd);
errno = save_errno;
return((mqd_t) -1);
}
//注册或删除消息队列的一个调用进程
int mq_notify(mqd_t mqd, const struct sigevent *notification)
{
int n;
pid_t pid;
struct mq_hdr *mqhdr;
struct mq_info *mqinfo;
mqinfo = mqd;
//检测麽数是否发生变化,如果发生变化,则这个消息队列已经损坏
if (mqinfo->mqi_magic != MQI_MAGIC) {
errno = EBADF;
return(-1);
}
//取得消息队列的头
mqhdr = mqinfo->mqi_hdr;
//获得消息队列的互斥锁
if ( (n = pthread_mutex_lock(&mqhdr->mqh_lock)) != 0) {
errno = n;
return(-1);
}
pid = getpid();
if (notification == NULL) {
if (mqhdr->mqh_pid == pid) {//如果struct sigevent结构为空,且当前进程就是之前的注册进程,则删除注册
mqhdr->mqh_pid = 0;
}
}
else {//如果struct sigevent结构不为空
if (mqhdr->mqh_pid != 0) {//如果 当前队列已有注册进程,则发送信号0检测该进程是否存在,如果不存在返回ESRCH错误,信号0是一个特殊的信号,之用来检测进程是否存在,并不向进程实际发送信号
if (kill(mqhdr->mqh_pid, 0) != -1 || errno != ESRCH) {
errno = EBUSY;
goto err;
}
}
//将消息队列注册到当前进程
mqhdr->mqh_pid = pid;
mqhdr->mqh_event = *notification;
}
//释放互斥锁
pthread_mutex_unlock(&mqhdr->mqh_lock);
return(0);
err:
pthread_mutex_unlock(&mqhdr->mqh_lock);
return(-1);
}
//关闭消息队列的描述符,本进程不再使用,这个操作并不会从系统中删除消息队列
int mq_close(mqd_t mqd)
{
long msgsize, filesize;
struct mq_hdr *mqhdr;
struct mq_attr *attr;
struct mq_info *mqinfo;
mqinfo = mqd;
if (mqinfo->mqi_magic != MQI_MAGIC) {
errno = EBADF;
return(-1);
}
mqhdr = mqinfo->mqi_hdr;
attr = &mqhdr->mqh_attr;
//撤销当前进程的注册
if (mq_notify(mqd, NULL) != 0)
return(-1);
//根据消息队列的属性,再次计算内存对齐的大小和整个消息队列的大小
msgsize = MSGSIZE(attr->mq_msgsize);
filesize = sizeof(struct mq_hdr) + (attr->mq_maxmsg *
(sizeof(struct msg_hdr) + msgsize));
//撤销当前进程对消息队列的内存映射
if (munmap(mqinfo->mqi_hdr, filesize) == -1)
return(-1);
//重置魔数,以防再次使用
mqinfo->mqi_magic = 0;
free(mqinfo);
return(0);
}
//删除消息队列,注意每个消息队列都维护了一个引用计数,就像文件一样,当引用计数不为0时并不发生真正的析构,即内核中的消息队列并不真正删除
int mq_unlink(const char *pathname)
{
if (unlink(pathname) == -1)
return(-1);
return(0);
}
//设置消息队列的属性
int mq_setattr(mqd_t mqd, const struct mq_attr *mqstat,
struct mq_attr *omqstat)
{
int n;
struct mq_hdr *mqhdr;
struct mq_attr *attr;
struct mq_info *mqinfo;
mqinfo = mqd;
if (mqinfo->mqi_magic != MQI_MAGIC) {
errno = EBADF;
return(-1);
}
mqhdr = mqinfo->mqi_hdr;
attr = &mqhdr->mqh_attr;
if ( (n = pthread_mutex_lock(&mqhdr->mqh_lock)) != 0) {
errno = n;
return(-1);
}
//保存当前的消息队列作为返回值
if (omqstat != NULL) {
omqstat->mq_flags = mqinfo->mqi_flags; /* previous attributes */
omqstat->mq_maxmsg = attr->mq_maxmsg;
omqstat->mq_msgsize = attr->mq_msgsize;
omqstat->mq_curmsgs = attr->mq_curmsgs; /* and current status */
}
//只设置一个属性O_NONBLOCK
if (mqstat->mq_flags & O_NONBLOCK)
mqinfo->mqi_flags |= O_NONBLOCK;
else
mqinfo->mqi_flags &= ~O_NONBLOCK;
pthread_mutex_unlock(&mqhdr->mqh_lock);
return(0);
}
//获得消息队列的属性
int mq_getattr(mqd_t mqd, struct mq_attr *mqstat)
{
int n;
struct mq_hdr *mqhdr;
struct mq_attr *attr;
struct mq_info *mqinfo;
mqinfo = mqd;
if (mqinfo->mqi_magic != MQI_MAGIC) {
errno = EBADF;
return(-1);
}
mqhdr = mqinfo->mqi_hdr;
attr = &mqhdr->mqh_attr;
if ( (n = pthread_mutex_lock(&mqhdr->mqh_lock)) != 0) {
errno = n;
return(-1);
}
mqstat->mq_flags = mqinfo->mqi_flags;
mqstat->mq_maxmsg = attr->mq_maxmsg;
mqstat->mq_msgsize = attr->mq_msgsize;
mqstat->mq_curmsgs = attr->mq_curmsgs;
pthread_mutex_unlock(&mqhdr->mqh_lock);
return(0);
}
//向消息队列发送消息,len是消息长度,prio是消息优先级
int mq_send(mqd_t mqd, const char *ptr, size_t len, unsigned int prio)
{
int n;
long index, freeindex;
int8_t *mptr;
struct sigevent *sigev;
struct mq_hdr *mqhdr;
struct mq_attr *attr;
struct msg_hdr *msghdr, *nmsghdr, *pmsghdr;
struct mq_info *mqinfo;
mqinfo = mqd;
if (mqinfo->mqi_magic != MQI_MAGIC) {
errno = EBADF;
return(-1);
}
//消息队列head的指针
mqhdr = mqinfo->mqi_hdr;
//字节指针
mptr = (int8_t *) mqhdr;
//消息队列的属性
attr = &mqhdr->mqh_attr;
if ( (n = pthread_mutex_lock(&mqhdr->mqh_lock)) != 0) {
errno = n;
return(-1);
}
//消息长度不能超过属性中mq_msgsize字段指定的大小
if (len > attr->mq_msgsize) {
errno = EMSGSIZE;
goto err;
}
if (attr->mq_curmsgs == 0) {//当消息队列由空边非空时才会向组测进程发送信号
if (mqhdr->mqh_pid != 0 && mqhdr->mqh_nwait == 0) {
sigev = &mqhdr->mqh_event;
if (sigev->sigev_notify == SIGEV_SIGNAL) {//发送指定信号
sigqueue(mqhdr->mqh_pid, sigev->sigev_signo,
sigev->sigev_value);
}
mqhdr->mqh_pid = 0; //取消进程对消息队列的注册
}
} else if (attr->mq_curmsgs >= attr->mq_maxmsg) {
//消息队列已满,并且设置非阻塞,则直接返回
if (mqinfo->mqi_flags & O_NONBLOCK) {
errno = EAGAIN;
goto err;
}
//阻塞,直到有进程从消息队列读走一条消息,使条件变量返回
while (attr->mq_curmsgs >= attr->mq_maxmsg)
pthread_cond_wait(&mqhdr->mqh_wait, &mqhdr->mqh_lock);
}
//取得可用消息的索引下标
if ( (freeindex = mqhdr->mqh_free) == 0){
printf("mq_send: curmsgs = %ld; free = 0", attr->mq_curmsgs);
exit(0);
}
nmsghdr = (struct msg_hdr *) &mptr[freeindex];
nmsghdr->msg_prio = prio;
nmsghdr->msg_len = len;
//nmsghdr + 1其实就是sizeof(struct msg_hdr)+1,指向的就是实际存储消息的首地址
memcpy(nmsghdr + 1, ptr, len); //内存拷贝
//修改链表的指针
mqhdr->mqh_free = nmsghdr->msg_next;
//根据优先级将当前消息查到对应优先级的最后面,这样可以直接返回消息链表的第一个就是优先级最高且最早接受的消息
index = mqhdr->mqh_head;
pmsghdr = (struct msg_hdr *) &(mqhdr->mqh_head);
while (index != 0) {
msghdr = (struct msg_hdr *) &mptr[index];
if (prio > msghdr->msg_prio) {
nmsghdr->msg_next = index;
pmsghdr->msg_next = freeindex;
break;
}
index = msghdr->msg_next;
pmsghdr = msghdr;
}
if (index == 0) {
pmsghdr->msg_next = freeindex;
nmsghdr->msg_next = 0;
}
//如果放消息前当前消息数为0,唤醒任何一个阻塞的接受进程
if (attr->mq_curmsgs == 0)
pthread_cond_signal(&mqhdr->mqh_wait);
attr->mq_curmsgs++;
pthread_mutex_unlock(&mqhdr->mqh_lock);
return(0);
err:
pthread_mutex_unlock(&mqhdr->mqh_lock);
return(-1);
}
//从消息队列上接受一个消息
ssize_t mq_receive(mqd_t mqd, char *ptr, size_t maxlen, unsigned int *priop)
{
int n;
long index;
int8_t *mptr;
ssize_t len;
struct mq_hdr *mqhdr;
struct mq_attr *attr;
struct msg_hdr *msghdr;
struct mq_info *mqinfo;
mqinfo = mqd;
if (mqinfo->mqi_magic != MQI_MAGIC) {
errno = EBADF;
return(-1);
}
mqhdr = mqinfo->mqi_hdr;
mptr = (int8_t *) mqhdr;
attr = &mqhdr->mqh_attr;
if ( (n = pthread_mutex_lock(&mqhdr->mqh_lock)) != 0) {
errno = n;
return(-1);
}
if (maxlen < attr->mq_msgsize) {
errno = EMSGSIZE;
goto err;
}
if (attr->mq_curmsgs == 0) {//队列空且非阻塞,直接返回
if (mqinfo->mqi_flags & O_NONBLOCK) {
errno = EAGAIN;
goto err;
}
//阻塞等待,并将mqh_nwait加一
mqhdr->mqh_nwait++;
while (attr->mq_curmsgs == 0)
pthread_cond_wait(&mqhdr->mqh_wait, &mqhdr->mqh_lock);
mqhdr->mqh_nwait--;
}
//取得第一个消息的索引下标
if ( (index = mqhdr->mqh_head) == 0){
printf("mq_receive: curmsgs = %ld; head = 0", attr->mq_curmsgs);
exit(0);
}
msghdr = (struct msg_hdr *) &mptr[index];
mqhdr->mqh_head = msghdr->msg_next; /* new head of list */
len = msghdr->msg_len;
memcpy(ptr, msghdr + 1, len); /* copy the message itself */
//如果调用要求返回优先级,则赋值优先级
if (priop != NULL)
*priop = msghdr->msg_prio;
//将发当前消息挂在可用消息链表的最前面
msghdr->msg_next = mqhdr->mqh_free;
mqhdr->mqh_free = index;
//唤醒任何一个等待发送的进程
if (attr->mq_curmsgs == attr->mq_maxmsg)
pthread_cond_signal(&mqhdr->mqh_wait);
attr->mq_curmsgs--;
pthread_mutex_unlock(&mqhdr->mqh_lock);
return(len);
err:
pthread_mutex_unlock(&mqhdr->mqh_lock);
return(-1);
}