消息队列分类
常用的消息队列分为以下两类:
- System V IPC之消息队列
- POSIX消息队列
1.System V IPC之消息队列
IPC数据结构是在进程请求IPC资源(信号量、消息对列或者共享内存区)时创建的。每个IPC资源都是持久的:除非被进程显示地释放,否则永远驻留在内存中(直到系统关闭)。IPC资源可以由任一进程使用,包括哪些不共享祖先进程所创建的资源的进程。
IPC标识符–每个IPC资源都是使用一个32位的IPC关键字来标识的。IPC标识符由内核分配给IPC资源,在系统内部是唯一的,而IPC关键字可以由程序员自由地选择。(摘自:深入理解linux内核,p780,关于System V IPC)
不管是进程之间还是同一个进程的不同线程之间要通过一个IPC资源进行通信时,这些进程或者同一进程的不同线程都要引用该资源的IPC标识符。
-
使用System V IPC资源之消息队列,需要调用msgget()函数创建IPC资源。
假设两个独立的进程想共享一个公共的IPC资源,可以使用两种方法: -
简单做法:这两个进程统一使用固定的、预定义的IPC关键字。【例外:假设另一个无关的程序也可能使用了相同的IPC关键字。在这种情况下,IPC函数可能被成功地调用,但返回错误资源的IPC标识符。】
-
一个进程通过指定IPC_PRIVATE作为自己的IPC关键字来调用msgget()函数。一个新的IPC资源因此而被分配,这个进程可以与应用程序中的另一个进程共享自己的IPC标识符,或者自己创建另一个进程。这种方法确保IPC资源不会偶然被其他应用程序使用。
IPC资源的管理(通过数据结构)
IPC资源的每种类型(信号量、消息队列和共享内存区)都拥有ipc_ids数据结构,该结构的如下所示:
/*path--linux-3.18.132\include\linux\ipc_namespace.h*/
struct ipc_ids {
int in_use; /*已分配IPC资源数*/
unsigned short seq; /*下一分配位置使用的序号*/
struct rw_semaphore rwsem; /*保护ipc_ids数据结构体的信号量*/
struct idr ipcs_idr;
int next_id;
};
struct idr {
struct idr_layer __rcu *hint; /* the last layer allocated from */
struct idr_layer __rcu *top;
int layers; /* only valid w/o concurrent changes */
int cur; /* current pos for cyclic allocation */
spinlock_t lock;
int id_free_cnt;
struct idr_layer *id_free;
};
/*使用ipcs -a命令可以查看IPC资源使用情况*/
root@ubuntu:/home# ipcs -a
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 294912 zhujinlin 600 524288 2 dest
0x00000000 1671169 zhujinlin 600 1048576 2 dest
------ Semaphore Arrays --------
key semid owner perms nsems
------ Message Queues --------
key msqid owner perms used-bytes messages
System V IPC()系统调用
所有的IPC函数都必须通过适当的Linux系统调用实现。当进程调用一个IPC函数时,比如说msgget(),该函数实际上调用C库函数中的一个封装函数,该函数又通过传递msgget()的所有参数加上一个适当的子命令代码来调用ipc()系统调用。sys_ipc()服务例程检查子命令代码,并调用内核函数实现所请求的服务。
消息队列阻塞进程的条件
当消息队列满时(1.达到了最大消息数;2.达到了队列最大字节数),则试图让新消息入队的进程可能被阻塞。有可能引发异常情况,这点在使用过程中需要注意。
2.POSIX消息队列
POSIX标准基于消息队列定义了一个IPC机制,就是大家知道的POSIX消息队列。与System V IPC消息队列相比,具有许多优点:
- 更简单的基于文件的应用接口
- 完全支持消息优先级(优先级最终决定在队列中的消息的位置)
- 完全支持消息到达的异步通知,这通过信号或是线程创建实现
- 用于阻塞发送与接收的超时机制
/*path --glibc-2.18\rt\mq_send.c*/
/* Add message pointed by MSG_PTR to message queue MQDES. */
int
mq_send (mqd_t mqdes, const char *msg_ptr, size_t msg_len,
unsigned int msg_prio)
{
return mq_timedsend (mqdes, msg_ptr, msg_len, msg_prio, NULL);
}
/*path --glibc-2.18\rt\mq_timedsend.c*/
/* Add message pointed by MSG_PTR to message queue MQDES, stop blocking
on full message queue if ABS_TIMEOUT expires. */
int
mq_timedsend (mqd_t mqdes, const char *msg_ptr, size_t msg_len,
unsigned int msg_prio, const struct timespec *abs_timeout)
{
__set_errno (ENOSYS);
return -1;
}
#ifdef __NR_mq_notify
/* Register notification upon message arrival to an empty message queue
MQDES. */
int
mq_notify (mqd_t mqdes, const struct sigevent *notification)
{
/* mq_notify which handles SIGEV_THREAD is included in the thread
add-on. */
if (notification != NULL
&& notification->sigev_notify == SIGEV_THREAD)
{
__set_errno (ENOSYS);
return -1;
}
return INLINE_SYSCALL (mq_notify, 2, mqdes, notification);
}
#else
# include <rt/mq_notify.c>
#endif
POSIX消息队列的实现
在Linux 2.6中,POSIX消息队列的实现是简单的。已经引入了一个叫做mqueue的特殊文件系统,每个现存队列在其中都有一个相应的索引节点。内核提供的系统调用mq_open()、mq_unlink()等,这些系统调用透明地对mqueue文件系统的文件进行操作,而大部分工作交由VFS层处理(linux提供了VFS,这让应用程序编程变得简单,因为应用程序不用去考虑不同文件系统的差异。)。
mqueue文件系统
/*可以通过如下命令查看是否挂载了明确月文件系统*/
root@ubuntu:/home# cat /proc/filesystems | grep mqueue
nodev mqueue
其中第一列nodev表示该类型文件不需要挂载在一个块设备上,否则就需要挂在一个块设备上;
第二列表示当前系统中支持的文件系统类型。
总结
通过对比学习了解到,POSIX 消息队列与System V IPC消息队列相比,具有更多优点。
参考
- 《深入理解LINUX内核》陈莉君 张琼声 张宏伟 译