UNP卷二 chapter5 Posix消息队列

1、Posix消息队列

消息队列可认为是一个消息链表,含三个消息的某个Posix消息队列的可能布局如下,


队列与管道和FIFO的区别在于,管道和FIFO是先有读出者,后有写入者(意思是有进程在管道一端进行读取操作,此时在管道另一端进行写入操作才是有意义的。可以将管道看作是一个通道,没有缓存效果),而队列可以实现某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达(也就是说,一个进程可以往某个队列写入一些消息,然后终止,再让另外一个进程在以后某个时刻读出这些消息)

Posix消息队列与System V队列的区别:

i、对Posix消息队列的读总是返回最高优先级的最早消息,对System V消息队列的读则可以人返回任意指定优先级的消息;

ii、当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号(通过mq_notify函数实现)或启动一个线程,System V消息队列则不提供类似机制。

队列中的每个消息具有如下属性(attribution):

i、一个无符号整数优先级(priority)(Posix)或一个长整数类型(type)(System V);

ii、消息的数据部分长度(len);

iii、数据本身(如果长度大于0)。

IPC对象至少具有随内核的持续性。

2、mq_open、mq_close、mq_unlink、mq_getattr和mq_setattr、mq_send和mq_receive函数

mq_open函数

#include<mqueue.h>
mqd_t mq_open(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;

mode是指owner、group、other的权限值;

attr参数用于给新队列指定某些属性。如果为空,则使用默认属性。


mqcreate1程序代码,

#include	"unpipc.h"

struct mq_attr	attr;	/* mq_maxmsg and mq_msgsize both init to 0 */

int
main(int argc, char **argv)
{
	int		c, flags;
	mqd_t	mqd;

	flags = O_RDWR | O_CREAT;
	while ( (c = Getopt(argc, argv, "em:z:")) != -1) {//getopt返回时,getopt在optind中存放下一个待处理参数的下标
		switch (c) {
		case 'e':
			flags |= O_EXCL;//e选项表示排他性创建消息队列选项
			break;

		case 'm':
			attr.mq_maxmsg = atol(optarg);
			break;

		case 'z':
			attr.mq_msgsize = atol(optarg);
			break;
		}
	}
	if (optind != argc - 1)
		err_quit("usage: mqcreate [ -e ] [ -m maxmsg -z msgsize ] <name>");

	if ((attr.mq_maxmsg != 0 && attr.mq_msgsize == 0) ||
		(attr.mq_maxmsg == 0 && attr.mq_msgsize != 0))
		err_quit("must specify both -m maxmsg and -z msgsize");

	mqd = Mq_open(argv[optind], flags, FILE_MODE,
				  (attr.mq_maxmsg != 0) ? &attr : NULL);//FILE_MODE=S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH

	Mq_close(mqd);
	exit(0);
}

mq_close函数

#include<mqueue.h>
int mq_close(mqd_t mqdes);//返回:若成功则为0,若出错则为-1

close表示,不再使用该标志符,但其消息队列并不从系统中删除。要从系统中删除用作mq_open第一个参数的某个name,必须调用mq_unlink

mq_unlink函数

#include<mqueue.h>
int mq_unlink(const char* name);//返回:若成功则为0,若出错则为-1

每个消息队列保有一个当前打开着描述符数的引用计数器(就像文件一样),只有引用计数值变为0时才真正拆除该队列。

mqunlink程序代码,

#include	"unpipc.h"

int
main(int argc, char **argv)
{
	if (argc != 2)
		err_quit("usage: mqunlink <name>");

	Mq_unlink(argv[1]);

	exit(0);
}

mq_getattr和mq_setattr函数

#include<mqueue.h>
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
struct mq_attr{}含有以下属性,
struct mq_attr {
	long mq_flags;/* message queue flag:0,O_NOBLOCK */
	long mq_maxmsg;/* max number of messagea allowed on queue *///允话的消息最大数量
	long mq_msgsize;/* max size of a message (in bytes)*///最大消息字节数
	long mq_curmsgs;/* number of messages currently on queue *///当前消息数量
};
mqgetattr程序
#include	"unpipc.h"

int
main(int argc, char **argv)
{
	mqd_t	mqd;
	struct mq_attr	attr;

	if (argc != 2)
		err_quit("usage: mqgetattr <name>");

	mqd = Mq_open(argv[1], O_RDONLY);//以只读权限及相关默认属性创建相关消息队列

	Mq_getattr(mqd, &attr);//获取默认属性
	printf("max #msgs = %ld, max #bytes/msg = %ld, "
		   "#currently on queue = %ld\n",
		   attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs);

	Mq_close(mqd);
	exit(0);
}

mq_send和mq_receive函数

这两函数分别用于往一个队列中放置一个消息和从一队列中取走一个消息。

mq_receive总是返回所指定队列中最高优先级的最早消息,而且该优先级能随该消息的内容及长度一同返回。

#include<mqueue.h>
int mq_send(mqd_t mqdes, const char* ptr, size_t len, unsigned int prio);//返回:若成功则为0,若出错则为-1
ssize_t mq_receive(mqd_t mqdes, char* ptr, size_t len, unsigned int *priop);//返回:若成功则为消息中字节数,若出错则为-1

类比记忆,此两函数的前三个参数分别与write和read的前三个参数类似。

mq_receive的len参数的值不能小于能加到所指定队列中的消息的最大大小(该队列mq_attr结构的mq_msgsize成员)。要是len小于该值,mq_receive就立即返回EMSGSIZE错误。

mqsend程序

#include	"unpipc.h"

int
main(int argc, char **argv)
{
	mqd_t	mqd;
	void	*ptr;
	size_t	len;
	uint_t	prio;//此处uint_t即为unsigned int

	if (argc != 4)
		err_quit("usage: mqsend <name> <#bytes> <priority>");
	len = atoi(argv[2]);
	prio = atoi(argv[3]);

	mqd = Mq_open(argv[1], O_WRONLY);//打开或创建一个只写的消息队列

	ptr = Calloc(len, sizeof(char));//动态分配数组内存且初始化为0
	Mq_send(mqd, ptr, len, prio);//往一个队列中放置一个消息

	exit(0);
}
mqreceive程序
#include	"unpipc.h"

int
main(int argc, char **argv)
{
	int		c, flags;
	mqd_t	mqd;
	ssize_t	n;
	uint_t	prio;
	void	*buff;
	struct mq_attr	attr;

	flags = O_RDONLY;
	while ( (c = Getopt(argc, argv, "n")) != -1) {
		switch (c) {
		case 'n':
			flags |= O_NONBLOCK;//设置非阻塞消息队列
			break;
		}
	}
	if (optind != argc - 1)
		err_quit("usage: mqreceive [ -n ] <name>");

	mqd = Mq_open(argv[optind], flags);//创建一个消息队列
	Mq_getattr(mqd, &attr);//此处获取消息队列属性,主要是为了获取队列中消息的最大size,即mq_msgsize值

	buff = Malloc(attr.mq_msgsize);//以消息的最大尺度mq_msgsize开僻一块内存

	n = Mq_receive(mqd, buff, attr.mq_msgsize, &prio);//从队列中取出消息
	printf("read %ld bytes, priority = %u\n", (long) n, prio);

	exit(0);
}
在OS(我的是ubuntu14.04或16.04)上,需要进行以下步骤才能运行上述程序
//首先
mkdir / dev / mqueue
//然后
mount - t mqueue none / dev / mqueue
//最后,才可执行以下程序
. / mqcreate / test1
. / mqgetattr / test1
. / mqsend / test1 100 6
. / mqsend / test1 50 18
. / mqsend / test1 33 18

3、消息队列限制

任意给定队列的两个限制:mq_maxmsg  队列中的最大消息数             mq_msgsize   给定消息的最大字节数

另外两个限制:

i、MQ_OPEN_MAX  一个进程能够同时拥有的打开着消息队列的最大数目(Posix要求它至少为8);

ii、MQ_PRIO_MAX   任意消息的最大优先级值加1(Posix要求它至少为32);

4、mq_notify函数(目的让系统通知我们何时有消息放置到先前空队列中)

System V消息队列的问题之一是无法通知一个进程何时在某个队列中放置了一个消息。虽然可以阻塞在msgrcv调用中,但将阻止在等待期间做其他任何事。解决办法是给msgrcv指定非阻塞标志(IPC_NOWAIT),即轮询,对CPU利用的一种浪费。

Posix消息队列允许异步事件通知,以告知何时有一个消息放置到了某个空消息队列中。这种通知有两种方式可供选择:

i、产生一个信号;

ii、创建一个线程来执行一个指定的函数;

mq_notify函数

#include<mqueue>
int mq_notify(mqd_t mqdes, const struct sigevent* notification);//返回:若成功则为0,若出错则为-1
sigevent结构定义在<signal.h>头文件中,如下,
union sigval {
	int sival_int;/* integer value */
	void *sival_ptr;/* pointer value */
};
struct sigevent {
	int sigev_notify;/* SIGEV_{NONE,SIGNAL,THREAD}*/
	int sigev_signo;/* signal number if SIGEV_SIGNAL*/
	union sigval sigev_value;/* passed to signal handler or thread */
	void(*sigev_notify_function)(union sigval);
	pthread_attr_t* sigev_notify_attributes;
};

以下是适用于mq_notify函数的若干规则:

i、如果notification参数非空,那么当前进程希望在有一个消息到达所指定的先前为空的队列时得到通知。即所谓“该进程被注册为接收该队列的通知”。(注册进程

ii、如果notification参数为空指针,而且当前进程目前被注册为接收指定队列的通知,那么已存在的注册将被撤销。(撤销已注册进程的通知的方法

iii、任意时刻只有一个进程可以人被注册为接收某个给定队列的通知。(注册进程唯一性

iv、当有一个消息到达某个先前为空的队列,而且已有一个进程被注册为接收该队列的通知时,只有在没有任何线程阻塞在该队列的mq_receive调用中的前提下,通知才会发出。即,在mq_receive调用中的阻塞比任何通知的注册都优先。(阻塞调用优先性

v、当该通知被发送给它的注册进程时,其注册即被撤销。该进程必须再次调用mq_notify以重新注册。(通知完,注册即被撤销

以下只给出使用select的Posix消息队列,如启动线程或使用非阻塞mq_receive的信号通知或使用sigwait代替信号处理程序的信号通知的举例参见书上P69-P77。

#include	"unpipc.h"

int		pipefd[2];
static void	sig_usr1(int);
/* $$.bp$$ */
int
main(int argc, char **argv)
{
	int		nfds;
	char	c;
	fd_set	rset;
	mqd_t	mqd;
	void	*buff;
	ssize_t	n;
	struct mq_attr	attr;
	struct sigevent	sigev;

	if (argc != 2)
		err_quit("usage: mqnotifysig5 <name>");

		/* 4open queue, get attributes, allocate read buffer */
	mqd = Mq_open(argv[1], O_RDONLY | O_NONBLOCK);
	Mq_getattr(mqd, &attr);
	buff = Malloc(attr.mq_msgsize);

	Pipe(pipefd);

		/* 4establish signal handler, enable notification */
	Signal(SIGUSR1, sig_usr1);
	sigev.sigev_notify = SIGEV_SIGNAL;
	sigev.sigev_signo = SIGUSR1;
	Mq_notify(mqd, &sigev);

	FD_ZERO(&rset);//描述符集清零
	for ( ; ; ) {
		FD_SET(pipefd[0], &rset);//设置读
		nfds = Select(pipefd[0] + 1, &rset, NULL, NULL, NULL);//阻塞于此,检测管道有无信号可读(由信号处理函数写出一个字节)

		if (FD_ISSET(pipefd[0], &rset)) {
			Read(pipefd[0], &c, 1);
			Mq_notify(mqd, &sigev);			/* reregister first */
			while ( (n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0) {
				printf("read %ld bytes\n", (long) n);
			}
			if (errno != EAGAIN)
				err_sys("mq_receive error");
		}
	}
	exit(0);
}

static void
sig_usr1(int signo)//只要有通知时,便触发信号处理函数,往管道中写入一个字节
{
	Write(pipefd[1], "", 1);	/* one byte of 0 */
	return;
}

5、Posix实时信号

信号可划分为两个大组:

i、其值在SIGRTMIN和SIGRTMAX之间(包括两者在内)的实时信号。Posix要求至少提供RTSIG_MAX种实时信号,而该常值 的最小值为8.

ii、所有其他信号:SIGALRM、SIGINT、SIGGKILL等

如果需实时行为,必须使用SIGRTMIN和SIGRTMAX之间的新的实时信号,而且在安装信号处理程序时必须给sigaction指定SA_SIGINFO标志。(怎么指定哈????)

实时行为隐含着如下特征:

i、信号是排队的;

ii、当有多个SIGRTMIN到SIGRTMAX范围内的解阻塞信号排队时,值较小的信号先于值 较大的信号递交。

iii、当某个非实时信号递交时,传递给它的信号处理程序的唯一参数是该信号的值。实时信号比其它信号携带更多的信息。如下举例:

void function(int signo, siginfo_t * info, void*context);//实时信号的信号处理程序

iv、一些新函数定义成使用实时信号工作。例如,sigqueue函数用于代替kill函数向某个进程发送一个信号,该新函数允许发送者随发所送信号传递一个sigval联合。

以下是演示实时信号的简单测试程序代码,

#include	"unpipc.h"

static void	sig_rt(int, siginfo_t *, void *);

int
main(int argc, char **argv)
{
	int		i, j;
	pid_t	pid;
	sigset_t	newset;
	union sigval	val;

	printf("SIGRTMIN = %d, SIGRTMAX = %d\n", (int) SIGRTMIN, (int) SIGRTMAX);//输出实时信号最小、最大值

	if ( (pid = Fork()) == 0) {
			/* 4child: block three realtime signals */
		Sigemptyset(&newset);
		//在信号集中,设置SIGRTMAX、SIGRTMAX-1、SIGRTMAX-2三个信号值
		Sigaddset(&newset, SIGRTMAX);
		Sigaddset(&newset, SIGRTMAX - 1);
		Sigaddset(&newset, SIGRTMAX - 2);
		Sigprocmask(SIG_BLOCK, &newset, NULL);//阻塞信号

			/* 4establish signal handler with SA_SIGINFO set */
		Signal_rt(SIGRTMAX, sig_rt);//建立实时信号处理函数
		Signal_rt(SIGRTMAX - 1, sig_rt);
		Signal_rt(SIGRTMAX - 2, sig_rt);

		sleep(6);		/* let parent send all the signals */

		Sigprocmask(SIG_UNBLOCK, &newset, NULL);	/* unblock */
		sleep(3);		/* let all queued signals be delivered */
		exit(0);
	}

		/* 4parent sends nine signals to child */
	sleep(3);		/* let child block all signals */
	for (i = SIGRTMAX; i >= SIGRTMAX - 2; i--) {//三个实时信号
		for (j = 0; j <= 2; j++) {//每种实时信号发3次
			val.sival_int = j;
			Sigqueue(pid, i, val);//此为系统自带函数,给一个进程发送信号(val指定信息)
			printf("sent signal %d, val = %d\n", i, j);
		}
	}
	exit(0);
}

static void
sig_rt(int signo, siginfo_t *info, void *context)
{
	printf("received signal #%d, code = %d, ival = %d\n",
		   signo, info->si_code, info->si_value.sival_int);
}

以上知识点来均来自steven先生所著UNP卷二(version2),刚开始学习网络编程,如有不正确之处请大家多多指正。

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80954566