用内存映射实现posix消息队列

来源:互联网 发布:院长入额首选知产团队 编辑:程序博客网 时间:2024/04/28 19:52
POSIX消息队列与System V消息队列的主要区别:
1.对POSIX队列的读总数返回最高优先级到最早消息,对SV队列到读则可以返回任意指定优先级的消息
2.当往一个空队列放置一个消息时,POSIX允许产生一个信号或启动一个线程,System V不提供此机制


消息的属性:
1.一个无符号整数的优先级(POSIX)或一个长整数的类型(SV)
2.消息的数据部分长度(可以为0)
3.数据本身(如果长度大于0)


POSIX消息队列总结:
mq_open创建一个新队列或者打开一个已经存在的队列
mq_close关闭队列
mq_unlink删除队列名,删除队列
mq_send往队列放置消息
mq_receive从一个队列中读出消息
mq_setattr和mq_getattr查询和设置队列的属性
mq_notify允许注册一个信号或者线程,在有一个消息被放置到空队列时,发送信号或者激活线程
每个消息被赋予一个小整数优先级,mq_receive总是返回最高优先级的最早消息


限制:
/proc/sys/fs/mqueue/msg_max 10
/proc/sys/fs/mqueue/msgsize_max 8192
/proc/sys/fs/mqueue/queues_max 256


创建一个新的消息队列或者打开一个已经存在的消息队列
<mqueue.h> 注意:编译加-lrt
<fcntl.h>
<sys/stat.h>
mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode,  struct mq_attr *attr);
成功返回描述字,失败返回-1并设置errno
name: 必须为/开头!!!
oflag: O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NONBLOCK


关闭消息队列,但不能删除它
mqd_t mq_close(mqd_t mqdes);
成功返回0,失败返回-1


删除消息队列,不一定马上删除消息队列,但队列名会立即删除,(真正的析构发生在引用计数为0的时候)
mqd_t mq_unlink(const char *name);
成功返回0,失败返回-1
当某个进程还没有关闭此消息队列时,调用mq_unlink时,不会马上删除队列,当最后一个进程关闭队列时,该队列被删除
int flags;
mqd_t mqd;
flags = O_RDWR | O_CREAT | O_EXCL;
mqd = mq_open("/tmp.111", flags, 0644, NULL);
if (mqd == (mqd_t)-1) {
perror("mq_open");
return 1;
}


消息队列的属性
mq_getattr mq_setattr
mqd_t mq_getattr(mqd_t mqdes, struct mq_attr *attr);
mqd_t mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr);
成功返回0,失败返回-1

收发消息

mq_send mq_receive


mq_receive返回队列中最高优先级的最早消息,而且该优先级能随该消息的内容及其长度一起返回


ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
成功返回消息的长度,消息的实际长度,不包括消息头;失败返回-1
msg_len指示msg_ptr的长度,必须大于等于mq_msgsize
如果msg_prio不为NULL,函数返回消息的优先级
如果队列为空,调用将阻塞,如果队列设置0_NONBLOCK,调用立即返回EAGAIN



队列限制
long int open_max = sysconf(_SC_MQ_OPEN_MAX);  // -1
long int prio_max = sysconf(_SC_MQ_PRIO_MAX);  // 32768


消息通告
当往空队列放置了一个消息时,通知进程
通告方式有2种:
1. 产生一个信号
2. 创建一个线程执行一个指定的函数
mqd_t mq_notify(mqd_t mqdes, const struct sigevent *notification);
成功返回0;失败返回-1
给队列建立或者删除异步事件通知
1.如果notification非空,那么当前进程希望在有一个消息到达而且队列先前为空时得到通知,该进程被注册为接收该队列的通知
2.如果notification为空,而且当前进程目前被注册为接收该队列的通知,那么现有注册将被撤销
3.任意时刻只有一个进程可以被注册为接收队列的通知
4.当有一个消息到达一个空队列,而且已经有一个进程被注册为接收该队列的通知时,只有在没有任何线程阻塞在该队列的mq_receive调用的前提下,通知才会发送。即在mq_receive调用中的阻塞比任何通知的注册都优先
5.当该通知已经发送给它的注册进程时,其注册即被撤销。该进程必须再次调用mq_notify以重新注册

6.当调用mq_notify但是队列不为空时,通知不会发送;当队列变为空,并且有一个消息入队时,才发送通知


下面是使用内存映射实现的消息队列,消息队列的数据结构如下:


以下是UNPv2上的实现

/*_______________|mq_attr||_______________||mqh_head||mqh_free||mgh_nwait||mgh_pid||_______________||mqh_enevt||_______________||mqh_lock||______________||mqh_hdr||数据||————————||mqh_hdr||数据||________________||..........||_______________|*/#include<signal.h>#include<stdarg.h>#include<sys/mman.h>#include<stdlib.h>#include<stdio.h>#include<pthread.h>//取得文件stat结构的最大尝试次数,#defineMAX_TRIES10//不同系统对mode_t类型有不同的定义,这里统一#ifdef __bsdi__#define va_mode_t int#else #define va_mode_t mode_t#endiftypedef 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;//注册当前进程的pidpid_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_attrdefattr = { 0, 128, 1024, 0 };//打开或创建一个消息队列mqd_t mq_open(const char *pathname, int oflag, ...){inti, fd, nonblock, created, save_errno;longmsgsize, filesize, index;va_listap;mode_tmode;int8_t*mptr;struct statstatbuff;struct mq_hdr*mqhdr;struct msg_hdr*msghdr;struct mq_attr*attr;struct mq_info*mqinfo;pthread_mutexattr_tmattr;pthread_condattr_tcattr;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;elsereturn((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){intn;pid_tpid;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){longmsgsize, 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){intn;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_NONBLOCKif (mqstat->mq_flags & O_NONBLOCK)mqinfo->mqi_flags |= O_NONBLOCK;elsemqinfo->mqi_flags &= ~O_NONBLOCK;pthread_mutex_unlock(&mqhdr->mqh_lock);return(0);}//获得消息队列的属性int  mq_getattr(mqd_t mqd, struct mq_attr *mqstat){intn;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){intn;longindex, 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){intn;longindex;int8_t*mptr;ssize_tlen;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);}


原创粉丝点击