Linux 进程间通信-消息队列

一、简单例子

发送消息:

//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;
	}
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值