--------------------mq_open---------------mq_close------------------mq_unlink------------
#include <mqueue.h>
mqd_t mqopen( const char * name,int oflag,...
/* mode_t mode,struct mq_attr *attr */)
//成功返回消息队列描述符,出错返回-1
oflag参数时 O_RDONLY,O_WRONLY,O_RDWR之一。可以或上 O_CREAT,O_EXCL,O_NONBLOCK.
当open的消息队列尚未创建时,mode和attr参数是必要的。mode就是chmod下的权限(mode是十进制),attr可以填NULL,使用默认属性
mp_open函数的返回值称为:消息队列描述符,它不必是向文件描述符或套接字那样的短整数,所以后面的select的fdset不能直接用此关键字。
mqd_t 在mq的其他函数中都要用到
int mq_close(mqd_t mqdes); //成功返回0,出错返回-1
其作用跟关闭一个已打开的文件的close函数类似。执行mq_close后,调用进程不再使用该描述符,但其消息队列(name)并不从系统中删除。
一个进程终止时,所有打开的消息队列都关闭,相当于执行了mq_close.
要从系统中删除mq_open的第一个参数name,必须调用mq_unlink.(回想FIFO,是用unlink删除的名字)
int mq_unlink(const char * name); //成功返回0,出错返回-1
一个简单的创建mq程序:
#include <unistd.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <mqueue.h>
#include <errno.h>
using namespace std;
#define FILE_MODE 420
int main(int argc,char ** argv)
{
char c;
mqd_t mq;
int flags=O_CREAT | O_RDWR;
opterr=0;
while( ( c = getopt(argc,argv,":ed:")) != -1)
{
switch(c)
{
case 'e':
flags |= O_EXCL;
break;
case 'd':
mq_unlink(optarg);
break;
case ':':
perror("less unlink_name");
break;
}
}
if(optind != argc - 1)
perror("mq名字未输入");
if( ( mq = mq_open(argv[optind],flags,FILE_MODE,NULL) ) == -1 )
perror("mq_open err");
mq_close(mq);
exit(0);
}
执行期间,遇到了一些问题,记录一下
g++ mq.c -lrt //必须要加-lrt不然编译不通过(undefine mq_open,mq_close)
然后:
mkdir /tmp/mqueue
mount -t mqueue none /tmp/mqueue //执行完这两步,创建的mq的名字即文件才能看见
下面是我的运行结果:
---------------------------------mq_getattr-------------------mq_setattr-----------------------
int mq_getattr( mqd_t mqdes, struct mq_attr * attr);
int mq_setattr( mqd_t mqdes, const struct mq_attr * attr,struct mq_attr * oattr);
//成功返回0,失败返回-1
strcut mq_attr{
long mq_flags; //0或者 O_NONBLOCK
long mq_maxmsg; //最大消息数
long mq_msgsize; //每个消息的最大长度
long mq_curmasgs; //队列中当前消息数
};
第二和第三个参数只能创建时设置,第四个参数只能get不能set
所以mq_setattr只能改变第一个参数mq_flags的值
用mq_getattr和mq_setattr对上面的/mq.124 进行操作
#include <mqueue.h>
#include <unistd.h>
#include <string.h>
#include <cstdio>
using namespace std;
int main()
{
mqd_t mq = mq_open("/mq.125",O_RDWR);
struct mq_attr attr;
bzero(&attr,sizeof(attr));
mq_getattr(mq,&attr);
printf("mq_flags=%ld\nmq_maxmsg=%ld\nmq_msgsize=%ld\n" \
"mq_curmsgs=%ld\n",attr.mq_flags,attr.mq_maxmsg,
attr.mq_msgsize,attr.mq_curmsgs);
attr.mq_flags=O_NONBLOCK;
mq_setattr(mq,&attr,NULL);
printf("mq_flags=%ld\nmq_maxmsg=%ld\nmq_msgsize=%ld\n" \
"mq_curmsgs=%ld\n",attr.mq_flags,attr.mq_maxmsg,
attr.mq_msgsize,attr.mq_curmsgs);
mq_close(mq);
return 0;
}
程序得到的结果是0,所以创建mq的时候还是要自己指定attr的2,3个参数的大小。
NULL得来的是0,经测试无法获得消息。
-----------------------------------mq_send--------------mq_receive----------
int mq_send(mqd_t mqdes, const char * ptr,size_t len,unsigned int prio);
//成功返回0,出错返回-1
ssize_t mq_receive(mqd_t mdqes, char * ptr,size_t len, unsigned int *priop);
//成功则返回消息中字节数,失败返回-1
mq_receive总是返回所指定队列中最高优先级的最早消息,而且该优先级能随消息的内容和长度一同返回
//system V有一个类似于优先级的类型字段,使用msgrcv时,我们可以就返回哪一个消息指定三种不同的
情形:所指定消息队列中最早的消息
具有某个特定类型的最早消息
消息类型小于或等于某个值得最早消息
注意:mq_receive的len参数 和sBuf的实际大小不能小于指定队列中的消息的最大大小。(mq_attr的mq_msgsize),不然立即返回
EMSGSIZE错误
------------------------消息队列的限制--------------------------------------------
attr结构体的第2,3个参数限制了队列的属性,还有另外两个限制参数
MQ_OPEN_MAX //一个进程能够同时拥有的打开着的最大消息队列数
MQ_PRIO_MAX //任意消息的最大优先值加1
----------------------------mq_notify------------------------------------------------
mq_receive函数默认是阻塞的,阻塞期间我们不能干别的工作。
如果用非阻塞的mq_receive 进行轮询查询的话,又浪费了cpu
posix消息队列允许异步事件通知,已告知何时有消息被放置到了空消息队列中(有点像是条件变量唤醒其他wait的线程),这种通知有两种方式:
产生一个信号 //sigevent.sigev_notify=SIGEV_SIGNAL, sigevent.sigev_signal=SIG_INT...
创建一个线程来执行一个指定的函数 //sigevent.sigev_notify=SIGEV_THREAD,sigevent.sigev_notify_function=func
union sigval {
int sigval_int;
void * sigval_ptr;
}
struct sigevent{
int sigev_notify; //SIGEV_{NONE,SIGNAL,THREAD}
int sigev_signal; //signal numble
union sigval sigev_value; //passed to signal handle or thread
void (*sigev_notify_function)(union sigval);
pthread_attr_t *sigev_notify_attributes;
}
函数原型:
int mq_notify(mqd_t mqdes, const struct sigevent * notification); //notification:通知
//成功返回0,出错返回-1
该函数遵循的若干规则:
1> 如果notification参数非空,那么当前进程希望在有一个消息到达所指定的先前为空的队列时得到通知
,该进程被注册为接受该队列的通知
2> 如果notification参数为空,而且当前进程目前被注册为接受指定队列的通知,那么已经存在的注册被撤销。
//这计划两个意思,如果当前进程第一次执行 mq_notify,该参数又为空,那就是无效的,不接受任何消息
// 如果当前进程之前执行过mq_notify,那么这次后执行的参数为空的会替换掉之前的,替换后的是不接受消息的
3> 任意时刻只有一个进程可以被注册为接受某个给定队列的通知
4> 当有一个消息到达某个先前为空的队列,而且已有一个进程被注册为接受该队列的通知时,只有在没有任何
线程阻塞在该队列的mq_receive调用中的前提下,通知才能发出。即mq_receive调用中的阻塞比任何通知
的注册都优先
5> 当该通知被发送给它的注册进程时,其注册即被撤销(类似于signal)
针对第五点,产生了一个问题,即再次注册时应该在receive之前还是之后?
书上说是一定要在receive之后。
自己简单得做了个测试,不管放在receive前面还是后面,不加延迟的情况,多次发送都能收到
receive之前加了sleep之后,发送多条消息,mq_notify在receive之前的能够处理2条,在receive后面的只能接受一条
这里关于mq_notify的使用书上有几个程序一次递进,直到完善,这里就不贴程序了,记录下大概思想
-----------------------
信号处理函数是不可重入函数,其内部必须使用异步信号安全函数,向read,write这些。使用printf这类函数是不可靠的。
为了不再信号处理函数内执行这些函数,解决办法是可以设置一个全局变量,信号处理函数只设置这个变量的值得变化。
这样还有一个缺陷,就是我之前提到了,receive之前有多条信号,只能处理两条。再次优化,使用非阻塞的receive函数。
至此,算是比较完善。不过sigprocmask,sigsuspend组合的情况可以用sigwait替换,效率更高。使用sigwait的情况,
这个过程也称为:同步的等待一个异步事件。
使用select的posix消息队列
posix消息队列的描述符是mqd_t类型的,select的fd_set是int类型的,所以这里可以用管道或者fifo的描述符。
因为write函数是异步信号安全函数,所以可以在信号处理函数内部执行write给管道或者FIFO写入一个字符。然后
主程序的select的读关键字就可以检测到了。
使用mq_notify的线程处理方式线程.
下面贴出一点简单的代码:
sigwait/sigsuspend
#include <mqueue.h>
#include <unistd.h>
#include <errno.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <malloc.h>
#include <signal.h>
#include <errno.h>
using namespace std;
int sflag=0;
mqd_t mq;
void func2(int sign)
{
close(mq);
printf("\tclosed mq\n");
exit(0);
}
void func(int signum)
{
#if 0
sflag=1;
#endif
}
int main()
{
char * sBuf;
size_t len;
int count=0;
struct sigevent sigev;
sigset_t newmask;
sigset_t empty;
sigemptyset(&newmask);
sigemptyset(&empty);
sigaddset(&newmask,SIGUSR1);
mq=mq_open("/mq.111",O_RDONLY);
if(mq < 0 )
printf("open err!\n");
mq_attr attr;
unsigned prio;
mq_getattr(mq,&attr);
len=attr.mq_msgsize;
len +=4;
sBuf=(char*)calloc(len,sizeof(char));
attr.mq_flags=O_NONBLOCK;
printf("curmasg=%ld\n",attr.mq_curmsgs);
mq_setattr(mq,&attr,NULL);
#if 0
signal(SIGUSR1,func);
#endif
signal(SIGINT,func2);
sigev.sigev_notify=SIGEV_SIGNAL;
sigev.sigev_signo=SIGUSR1;
int bb = mq_notify(mq,&sigev);
if(bb < 0)
{
printf("bb=%d,errno=%d\n",bb,errno);
}
while(1)
{
sigprocmask(SIG_BLOCK,&newmask,NULL);
#if 0
while(sflag == 0 )
sigsuspend(&empty);
sflag=0;
#endif
int signo;
sigwait(&newmask,&signo);
if(signo == SIGUSR1)
{
int bb = mq_notify(mq,&sigev);
if(bb < 0)
printf("errno=%d\n",errno);
while ( ( bb = mq_receive(mq,sBuf,len,NULL) ) >= 0)
{
printf("rcv : %s,count=%d\n",sBuf,count++);
printf("---------------------------\n");
}
if(errno != EAGAIN)
perror("rcv err");
sigprocmask(SIG_UNBLOCK,&newmask,NULL);
}
}
return 0;
}
总结:sigsuspend/sigwait好像都是搭配sigprocmask一起使用的,记住这点喽
使用线程和select就不写代码了,不钻牛角尖,以后有时间再说吧。
------------------------------------实时信号--------------------------------------------------------
信号分两种:
其值在SIGREMIN-SIGRTMAX之间的是实时信号
所有其他信号,像SIGUSR1,SIGINT....
信号执行实时行为的两个条件:
1> 必须是实时信号
2> 必须指定SA_SIGINFO
实时行为的特征:
1> 信号是排队的。如果一个信号产生了三次,它就递交三次。给定信号的多次发送按先进先出(FIFO)的原则
2> 值较小的优先于值较大的
3> 实时信号比非实时信号递交更多的参数
实时信号实测:
代码:
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <cstdlib>
#include <cstdio>
#include <malloc.h>
#include <signal.h>
#include <errno.h>
using namespace std;
void func(int signum,siginfo_t * info,void * context)
{
printf("recv:\tsigno=%d\tcode =%d\tval=%d\n",
signum,info->si_code,info->si_value.sival_int);
}
static void fun(int n)
{
int stat;
int pid;
while( (pid = waitpid(-1, &stat, WNOHANG)) > 0 ) {
printf( "child %d exit\n", pid );
}
exit(0);
}
int main()
{
union sigval val;
int i,j;
int count=0;
sigset_t newmask;
pid_t pid = fork();
if(pid<10)
{
//pid=getpid();
cout << "c-pid:" << pid << endl;
sigemptyset(&newmask);
sigaddset(&newmask,SIGRTMIN);
sigaddset(&newmask,SIGRTMIN+1);
sigaddset(&newmask,SIGRTMIN+2);
struct sigaction act;
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=func;
act.sa_mask=newmask;
//无论何时处理不止一种实时信号,都应该给sa_mask指定值
//以阻塞优先级大的信号
sigprocmask(SIG_BLOCK,&newmask,NULL);
sigaction(SIGRTMIN,&act,NULL);
sigaction(SIGRTMIN+1,&act,NULL);
sigaction(SIGRTMIN+2,&act,NULL);
sleep(5); //等待父进程发信号
cout << "111111" << endl;
sigprocmask(SIG_UNBLOCK,&newmask,NULL);
cout << "222222" << endl;
//解阻塞后,开始处理这几种信号
sleep(2);
exit(0);
}
signal(SIGCHLD,fun);
cout << "f_pid=" << pid << endl;
sleep(2);
for(i =SIGRTMIN+2;i >= SIGRTMIN;i--)
{
for(j=3;j>=1;j--)
{
val.sival_int =j;
sigqueue(pid,i,val); //sigqueue比kill函数多个val参数
printf("send:\tsigno=%d\tval=%d\t\n",
i,j);
}
}
while(1)
;
}
到这里,才真正想通。
非实时信号是很不稳定的,前面用非阻塞的receive虽然能接受所有消息(非实时信号下),但并没有接收到所有信号。
ok,不纠结了,这一节过去了。