一、简单例子
发送消息:
//sender
#include <stdio.h>
#include <sys/msg.h>
typedef struct
{
long type;
char data[512];
}MSG_ST;
int main()
{
int msgid;
key_t key = 0x1111;
MSG_ST stMsg;
//skip error handle
stMsg.type = 2;
stMsg.data[0] = 's';
msgid = msgget(key,IPC_CREAT | 0666);
msgsnd(msgid,&stMsg,sizeof(stMsg.data),0);
return 0;
}
接收消息
//receiver
#include <stdio.h>
#include <sys/msg.h>
typedef struct
{
long type;
char data[512];
}MSG_ST;
int main()
{
int msgid;
int type = 2;
key_t key = 0x1111;
MSG_ST stRcv;
//skip error handle
msgid = msgget(key,IPC_CREAT | 0666);
msgrcv(msgid,&stRcv,sizeof(stRcv.data),type,0);
printf("rcv msg type:%ld fist data:%d\n",stRcv.type,stRcv.data[0]);
return 0;
}
编译并执行发送端程序:
发送消息后,可通过ipcs命令查看到消息队列:
运行接收进程,从队列中接收一个消息:
再次使用ipcs,可以发现消息队列中messages为0,即队列中没有消息了:
二、消息队列的实现过程
消息队列通过系统调用实现,如下图所示:
每个消息队列以msg_queue为队列头,其中包含3个链表:
(1)发送者链表。
(2)接受者链表。
(3)消息链表。
三、msgget()函数简析
msgget()函数的实现主体为ipcget_public(),函数调用关系如下:
SYSCALL_DEFINE2(msgget) -> ipcget() -> ipcget_public()
//linux-4.2.1/ipc/util.c
static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids,
const struct ipc_ops *ops, struct ipc_params *params)
{
struct kern_ipc_perm *ipcp;
int flg = params->flg;
int err;
/*
* Take the lock as a writer since we are potentially going to add
* a new entry + read locks are not "upgradable"
*/
down_write(&ids->rwsem);
ipcp = ipc_findkey(ids, params->key); //通过键值查询是否已经被创建
if (ipcp == NULL) {
/* key not used */
if (!(flg & IPC_CREAT))
err = -ENOENT;
else
err = ops->getnew(ns, params); //如果没找到,且用户层传入参数有IPC_CREAT标志,则新建一个消息队列
} else {
/* ipc object has been locked by ipc_findkey() */
if (flg & IPC_CREAT && flg & IPC_EXCL)
err = -EEXIST; //如果系统中已经存在对应键值的消息队列,且本次调用是希望新建一个独占的消息队列,则返回一个错误。
else {
err = 0;
if (ops->more_checks)
err = ops->more_checks(ipcp, params);
if (!err)
/*
* ipc_check_perms returns the IPC id on
* success
*/
err = ipc_check_perms(ns, ipcp, ops, params); //消息队列已存在,进一步检查用户权限
}
ipc_unlock(ipcp);
}
up_write(&ids->rwsem);
return err;
}
四、msgsnd函数
msgsnd()函数主要功能由do_msgsnd()实现:
(1)先复制消息类型,再复制消息中的数据。
(2)发送消息的类型type必须大于0。
SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz,
int, msgflg)
{
long mtype;
if (get_user(mtype, &msgp->mtype))//复制消息类型mtype
return -EFAULT;
return do_msgsnd(msqid, mtype, msgp->mtext, msgsz, msgflg);
}
//linux-4.2.1/ipc/Msg.c
long do_msgsnd(int msqid, long mtype, void __user *mtext,
size_t msgsz, int msgflg)
{
...
if (mtype < 1) //消息类型不能小于1
return -EINVAL;
msg = load_msg(mtext, msgsz); //把消息内容从用户空间复制到内核空间
...
msg->m_type = mtype;
msg->m_ts = msgsz;
...
for (;;) {
...
/* queue full, wait: */
if (msgflg & IPC_NOWAIT) { //如果队列已满且用户空间调用参数中有IPC_NOWAIT,则返回错误。
err = -EAGAIN;
goto out_unlock0;
}
...
}
msq->q_lspid = task_tgid_vnr(current);
msq->q_stime = get_seconds();
if (!pipelined_send(msq, msg)) {
/* no one is waiting for this message, enqueue it */ //如果没有接收进程处于睡眠状态并等待接收,则将其挂入队列中,等待接收。
list_add_tail(&msg->m_list, &msq->q_messages);
msq->q_cbytes += msgsz;
msq->q_qnum++;
atomic_add(msgsz, &ns->msg_bytes);
atomic_inc(&ns->msg_hdrs);
}
...
}
五、msgrcv函数
msgrcv()函数的实现主体为do_msgrcv()。
其中,msgtpye取值不同,有不同的含义:
(1)等于0时,返回消息队列中的第一个消息。
(2)大于0时,返回消息队列中消息队列中消息类型相等的第一个消息。
(3)小于0时,返回消息队列中小于-msgtype的所有消息中,消息类型最小的一个消息,例如消息队列中有3个消息,消息类型分别为1,3,5, 调用msgrcv函数时参数消息类型的值为-4,则队列中消息类型为1的消息被读取。
SYSCALL_DEFINE5(msgrcv, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz,
long, msgtyp, int, msgflg)
{
return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill);
}
long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{
...
if (msgflg & MSG_COPY) {
if ((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT))
return -EINVAL;
copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax));
if (IS_ERR(copy))
return PTR_ERR(copy);
}
mode = convert_mode(&msgtyp, msgflg);
rcu_read_lock();
msq = msq_obtain_object_check(ns, msqid);
if (IS_ERR(msq)) {
rcu_read_unlock();
free_copy(copy);
return PTR_ERR(msq);
}
for (;;) {
...
msg = find_msg(msq, &msgtyp, mode);//根据消息类型,从队列中查找是否有匹配的消息
if (!IS_ERR(msg)) {
/*
* Found a suitable message.
* Unlink it from the queue.
*/
if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
msg = ERR_PTR(-E2BIG);
goto out_unlock0;
}
/*
* If we are copying, then do not unlink message and do
* not update queue parameters.
*/
if (msgflg & MSG_COPY) {
msg = copy_msg(msg, copy);
goto out_unlock0;
}
list_del(&msg->m_list);
msq->q_qnum--;
msq->q_rtime = get_seconds();
msq->q_lrpid = task_tgid_vnr(current);
msq->q_cbytes -= msg->m_ts;
atomic_sub(msg->m_ts, &ns->msg_bytes);
atomic_dec(&ns->msg_hdrs);
ss_wakeup(&msq->q_senders, 0);
goto out_unlock0;
}
/* No message waiting. Wait for a message */
if (msgflg & IPC_NOWAIT) {
msg = ERR_PTR(-ENOMSG);
goto out_unlock0;
}
}
bufsz = msg_handler(buf, msg, bufsz);
free_msg(msg);
return bufsz;
}
一旦从队列中有匹配的消息,则通过函数do_msg_fill()将消息的内容从内核空间复制到用户空间:
static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz)
{
struct msgbuf __user *msgp = dest;
size_t msgsz;
if (put_user(msg->m_type, &msgp->mtype))
return -EFAULT;
msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz;
if (store_msg(msgp->mtext, msg, msgsz))
return -EFAULT;
return msgsz;
}
六、msgctl函数
通过msgctl可更改队列的属性,如队列的最大字节数,也可以删除队列。
SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
{
int version;
struct ipc_namespace *ns;
if (msqid < 0 || cmd < 0)
return -EINVAL;
version = ipc_parse_version(&cmd);
ns = current->nsproxy->ipc_ns;
switch (cmd) {
case IPC_INFO:
case MSG_INFO:
case MSG_STAT: /* msqid is an index rather than a msg queue id */
case IPC_STAT:
return msgctl_nolock(ns, msqid, cmd, version, buf);
case IPC_SET:
case IPC_RMID:
return msgctl_down(ns, msqid, cmd, buf, version);
default:
return -EINVAL;
}
}