linux 源代码分析 消息管理
DESCRIPTION
TRANSCRIPT
Linux 源代码分析报告
Linux 源代码分析
内容:msg.c
作者:欧阳杨 单位:浙江大学混合 974
1999 年 12 月 10 日于求是园
Page1
Linux 源代码分析报告
Linux 源代码分析----ipc/msg.c
前言:在操作系统中,有些进程存在着相互制约的关系,这些制约
关系来源于并行进程的相互合作和资源共享。为了使合作进程和资源共享进程能协调一致的向前推进,必须使他们保持联系,一边相互了解。进程相互间需要交换一定数量的信息,以便协调一致共同完成指定的任务. 这种机制就叫做进程间通信,或IPC.在 linux 中支持 UNIX SYSTEM V 的三种通信机制: 消息队列, 信号量和共享内存. 现就消息队列这种机制进行分析.
包含的头文件:#include <linux/malloc.h>#include <linux/msg.h>#include <linux/interrupt.h>#include <linux/smp_lock.h>
Page2
Linux 源代码分析报告
#include <linux/init.h>#include <asm/uaccess.h>
msg.c 中包含的函数模块: 有关进程间通信资源的属性:
键(key): 一个由用户提供的整数,用来标志某个消息。 创建者(creator): 创建这个消息的进程的用户 ID(UID )和
组 ID(GID).
所有者(owner): 消息所有者的 UID 和 GID. 资源创建时,资源的创建者就是资源的所有者。资源的创建者进程、当前的所有者进程和超级用户具有改变资源所有者的权力。
参数类型的说明:1. struct ipc_perm
{
key_t key; // 整型, 0 表示 private, 非 0 表示 public
ushort uid; // 资源拥有者的有效标识ushort gid; // 资源拥有者所在组的有效标识ushort cuid; // 资源创建者的有效标识ushort cgid; // 资源创建者所在组的有效标识
ushort mode; // 访问模式ushort seq; // 序列号, 计算标识符
];
系统在创建消息队列的同时设定了访问权限, 并返回一个标识. 进程通信时必须先传递该标识, 待函数 ipcperms()
确认权限后才可以访问通信资源. 访问权限由 ipc_perm 结
Page3
Linux 源代码分析报告
构描述. 通过 key 可以得到引用标识, 从而访问通信资源.
Key 为 public , 则任何进程都可以通过 key 得到引用标识.2. struct msg {
struct msg *msg_next; //消息队列中的下一个 long msg_type; //消息的类型 char *msg_spot; //存放消息内容的地址 time_t msg_time; //消息发送的时间 short msg_ts; //消息的长度 };
msg 结构用来存放消息的有关信息.3. struct msqid_ds
{ struct ipc_perm msg_perm;
struct msg *msg_first; //指向消息队列的第一条消息
struct msg *msg_last; //指向消息队列的最后一条消息 time_t msg_stime; // 最后发送时间
time_t msg_rtime; //最后接收时间 time_t msg_ctime; //最后修改时间 struct wait_queue *wwait; //写消息进程的等待队列 struct wait_queue *rwait; //读消息进程的等待队列 ushort msg_cbytes; //队列中消息的字节数 ushort msg_qnum; //队列中的消息数 ushort msg_qbytes; //队列中消息的最大字节数 ushort msg_lspid; // 最后一个发送消息的进程的标识号
Page4
Linux 源代码分析报告
ushort msg_lrpid; //最后一个接收消息的进程的标识号};
每一个 msqid_ds 结构代表一个消息队列, 是进程读写的信息的存储空间。
static struct msqid_ds *msgque[MSGMNI];
定义了一个消息队列数组 msgque, 数组的元素类型是指向 msqid_ds 结构的指针。消息在队列中是按到来的顺序维护。进程读消息时,这些消息按 FIFO 从队列中移去。
图:Linux 消息队列
以下是消息传递的示意图:
Page5
msgque msgid_ds msg msg msg_perms msg_next msg_next msg_first msg_type msg_last msg_spot
msg_stime msg_ts
message
Linux 源代码分析报告
4. struct msgbuf{
long mtype; //消息的类型 char mtext[1]; //消息的内容};
存放消息的信息。5. struct wait_queue
{
struct wait_queue *next; //指针指向等待队列的下一个 struct task_struct *task;
/*task_struct 存放的是进程控制块的信息*/
};
wait_queue 代表各种各样的进程等待队列。 初始化变量:
static int msgbytes = 0; //代表消息的字节数static int msghdrs = 0;
static unsigned short msg_seq = 0; //代表消息的序列号static int used_queues = 0; //代表使用的消息数static int max_msqid = 0; //代表最大的消息序列号
Page6
msg msg msg
Struct msgqid_ds sender
sender
receiver
receiver
Linux 源代码分析报告
static struct wait_queue *msg_lock = NULL;
函数列表:1. void msg_init(void)2. static int real_msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)
3. static int real_msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg)
4. asmlinkage int sys_msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)
5. asmlinkage int sys_msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz,
6. static int findkey(key_t key)7. static int newque (key_t key, int msgflg)8. asmlinkage int sys_msgget(key_t key, int msgflg)9. static void free_que (int id)10. asmlinkage int sys_msgctl (int msqid, int cmd, struct msqid_ds *buf)
函数模块的说明与分析:1. 初始化模块:
void __init msg_init (void)
函数参数:无函数返回类型:空函数功能:对消息队列及各变量初始化{ int id;
for (id = 0; id < MSGMNI; id++) msgque [id] = (struct msqid_ds *) IPC_UNUSED;
/* 给指向消息队列的指针分配空间,标志为 IPC_UNUSED,定义见 linux/ipc.h : #define IPC_UNUSED ((void ) -1)*/
msgbytes = msghdrs = msg_seq = max_msqid = used_queues = 0;msg_lock = NULL;return;
}
2. 发送消息模块: static int real_msgsnd (int msqid, struct msgbuf *msgp, size_t
Page7
Linux 源代码分析报告
msgsz, int msgflg)
函数参数: msqid: 整型, 消息的序列号; msgp: 指向 msgbuf 结构的指针; msgsz: 消息的大小; msgflg: 消息的标志符。
函数返回类型:若消息正确的放入消息队列,则返回 0,否则返回各种 错误信息。详情请看返回错误类型一览表。
函数功能:将 msgp 所指向的消息缓冲区的内容放入消息队列中,且将标志号设为msqid。其他调用可通过msqid访问消息队列。{
int id; struct msgque*msq; struct ipc_perm*ipcp; struct msg*msgh; long mtype;
if (msgsz > MSGMAX || (long) msgsz < 0 || msqid < 0)
/* 见 msg.h 中#define MSGMAX 4056 消息的最大字节数, 消息的大小不符和要求或无序列号, 则出错*/
return -EINVAL; if (get_user(mtype, &msgp->mtype))
/*用户获取消息的类型,正常返回 0*/
return -EFAULT;
if (mtype < 1) //若消息类型<1,则用户无法发送此消息
return -EINVAL; id = (unsigned int) msqid % MSGMNI;
Page8
/*调用函数 int ipcperms (struct ipc_perm *ipcp, short flag) 进程通信时先传递 flag 标识,然后由此函数确认访问通信资源的权限。 此函数若返回-1 则权限没有被确认。 { int requested_mode, granted_mode;
requested_mode = (flag >> 6) | (flag >> 3) | flag; granted_mode = ipcp->mode;
if (current->euid == ipcp->cuid || current->euid == ipcp->uid) /*各 id 号一致*/
granted_mode >>= 6; else if (in_group_p(ipcp->cgid) || in_group_p(ipcp->gid))
/*调用函数 in_group_p,见/kernel/sys.c 判断资源拥有者的组号或资源创建者的组号是否存在*/
granted_mode >>= 3; if ((requested_mode & ~granted_mode & 0007) && !capable(CAP_IPC_OWNER))
/*判断是否有在 requested_mode但不在 granted_mode 中的位 */ return -1;
return 0;} }*/
Linux 源代码分析报告
/*见 msg.h 中#define MSGMNI 128 消息队列的最大序列号 id 即消息队列中对应的下标*/
msq = msgque [id]; if (msq == IPC_UNUSED || msq == IPC_NOID)
/*特殊的 shmsegs[id], msgque[id] or semary[id]值 。见 ipc.h
#define IPC_UNUSED ((void *) -1) //初始化时设置#define IPC_NOID ((void *) -2) */
return -EINVAL; ipcp = &msq->msg_perm;
slept: if (msq->msg_perm.seq != (unsigned int) msqid / MSGMNI)
/*将消息的序列号与 ipc_perm 中的进行比较,若不一致则表示此消 息以被从消息队列中删去,返回出错信息*/
return -EIDRM;
if ( ipcperm(ipcp, S_IWUGO))
/*stat.h 中 S_IWUGO 定 义 为 S_IWUSR|S_IWURP|S_IWOTH, 即00200|00020|00002。其中 00200 表示资源创建者的写权限,00020 表示资源创建组的写权限,00002 表示所有用户写权限*/
return -EACCES;
Page9
调用函数 void interruptible_sleep_on(struct wait_queue **p)
Linux 源代码分析报告
if (msgsz + msq->msg_cbytes > msq->msg_qbytes) { if (msgsz + msq->msg_cbytes > msq->msg_qbytes) {
/* 如果队列中无足够空间 */
if (msgflg & IPC_NOWAIT)
/*msgflg 中 IPC_NOWAIT 置位,则进程不进入的等待队列,返回错误 EAGAIN*/
return -EAGAIN;if (signal_pending(current))
/*如果此进程某个必须处理的信号已经到达,则返回 EINTR*/
return -EINTR;interruptible_sleep_on (&msq->wwait);
/*调度其他等待队列上的进程,直到此进程被唤醒*/
{SLEEP_ON_VAR
/*见 sched.c 中#define SLEEP_ON_VAR
unsigned long flags;struct wait_queue wait; */
current->state = TASK_INTERRUPTIBLE;
Page10
Linux 源代码分析报告
/*将当前任务的状态置为 TASK_INTERRUPTIBLE,表示处于 等待队列中的进程,待资源有效时唤醒,或由其它进程通过信号和定时中断唤醒后进入就绪队列*/
SLEEP_ON_HEAD
Schedule(); //对等待队列上的进程进行调度 SLEEP_ON_TAIL
}
goto slept; /*重新发送*/
}}
/*以下分配消息的指针和存放内容的空间*/
msgh = (struct msg *) kmalloc (sizeof(*msgh) + msgsz, GFP_KERNEL);
if (!msgh) /*系统无足够空间存放消息缓存区*/
return -ENOMEM;msgh->msg_spot = (char *) (msgh + 1);
if (copy_from_user(msgh->msg_spot, msgp->mtext, msgsz))
/*调用 copy_from_user, 将 msgz 大小的内容为 msgp->mtext
放入 msgh->msg_spot 指向的空间,正常则返回 0*/
{kfree(msgh);
/*调用函数 kfree,见 mm/slab.c,释放掉msgh 的 空间*/
return -EFAULT;}
if (msgque[id] == IPC_UNUSED || msgque[id] == IPC_NOID|| msq->msg_perm.seq != (unsigned int) msqid / MSGMNI) {
Page11
spin_unlock_irqrestore(&runqueue_lock, flags);
}
Linux 源代码分析报告
/*msgque 的值为 IPC_UNUSED 或 IPC_NOID,或序列号不符,表示此消息已从消息队列中删去*/
kfree(msgh);return -EIDRM;
}
/*设置msgh 所指的消息的各项属性*/
msgh->msg_next = NULL;msgh->msg_ts = msgsz;msgh->msg_type = mtype;msgh->msg_stime = CURRENT_TIME;
if (!msq->msg_first) //消息队列中无成员msq->msg_first = msq->msg_last = msgh;
else {
/*将 msgh插入消息队列 msq*/
msq->msg_last->msg_next = msgh;msq->msg_last = msgh;
}
/*msg 中加入 msgh 后,修改各项相应的属性*/
msq->msg_cbytes += msgsz;msgbytes += msgsz;msghdrs++;msq->msg_qnum++;msq->msg_lspid = current->pid;msq->msg_stime = CURRENT_TIME;wake_up (&msq->rwait);
/*调用函数 wake_up ,唤醒等待获得消息的进程*/
调用函数 void wake_up_process(struct task_struct * p)
{ unsigned long flags;
spin_lock_irqsave(&runqueue_lock, flags);
Page12
Linux 源代码分析报告
/*调用函数 spin_lock_irqsave,将以下部分上锁,防止其他进程访问*/
p->state = TASK_RUNNING; //将进程设为运行状态 if (!p->next_run) {
add_to_runqueue(p); //将此任务加入运行队列 reschedule_idle(p);
}
/*解除锁机制*/
return 0;}
asmlinkage int sys_msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)
函数参数: msqid: 整型, 消息的序列号; msgp: 指向 msgbuf 结构的指针; msgsz: 消息的大小; msgtyp: 消息的类型; msgflg: 消息的标志符。函数返回类型:有关汇编连接的类型函数功能:系统调用,发送消息
{int ret;
lock_kernel();
/*调用函数 lock_kernel{ do{}while(0);}, 此函数用于多 cpu处理,当一个Page13
Linux 源代码分析报告
cpu处理进程时,不允许其他 cpu 访问*/
ret = real_msgsnd(msqid, msgp, msgsz, msgflg);unlock_kernel();
/*调用函数 unlock_kernel{do{}while(0);},功能与 lock_kernel 相反*/
return ret;}
返回错误类型一览表:返回的类型 代表的意思EACCES 发送消息的进程没有往消息队列写的权限。EFAULT
Msgp 所指的地址无法访问。EIDRM 消息已被从消息队列中删去。EINTR 消息队列已满,正在发送消息的进程获得某个信号。EINVAL 错误的 msqid 或非正的 mtype,或错误的 msgsz。ENOMEN 系统没有足够的空间给消息缓冲区。EAGAIN 若消息队列已满,且 IPC _NOWAIT 置位。
返回3. 接收消息模块;
static int real_msgrcv (int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg)
函数参数: msqid: 整型, 消息的序列号; msgp: 指向 msgbuf 结构的指针; msgsz: 消息的大小; msgtyp: 消息的类型;
Page14
Linux 源代码分析报告
msgflg: 消息的标志符。函数返回类型:若接收正确,返回接收到的消息字节数。否则,返回错误信息,详情请看返回错误一览表。
函数功能:从消息队列中接收标示号为msqid,内容为msgp所指向的信息。{
struct msqid_ds*msq;struct ipc_perm *ipcp;struct msg *tmsg, *leastp = NULL;struct msg *nmsg = NULL;int id;
if (msqid < 0 || (long) msgsz < 0) //消息的序列号或大小不符和要求return -EINVAL;
id = (unsigned int) msqid % MSGMNI;
msq = msgque [id]; //将消息队列中下标为 id 的成员赋给 msq
if (msq == IPC_NOID || msq == IPC_UNUSED) return -EINVAL;
ipcp = &msq->msg_perm;
while (!nmsg) {if (msq->msg_perm.seq != (unsigned int) msqid / MSGMNI) {
/*序列号不符,表示此消息已被删去*/
return -EIDRM;}if (ipcperms (ipcp, S_IRUGO)) {
/* stat.h 中 S_IRUGO 定 义 为 S_IRUSR|S_IRGRP|S_IROTH ,即 00400|
00040|00004。00400 表示资源创建者的读权限,00040 表示资源创建组的读权限00004 表示所有用户的读权限。函数确认访问通信资源的权限 。此函数若返回-1
则权限没有被确认*/
Page15
Linux 源代码分析报告
return -EACCES;
}
/*以下寻找类型正确的消息*/
if (msgtyp == 0) // msgtyp=0取消息队列的第一个nmsg = msq->msg_first;
else if (msgtyp > 0) {if (msgflg & MSG_EXCEPT) {
/*若 msgtyp>0 且 MSG_EXCEPT置位,则取消息队列的第一个类型不符和的 */
for (tmsg = msq->msg_first; tmsg; tmsg = tmsg->msg_next)
if (tmsg->msg_type != msgtyp)break;
nmsg = tmsg;} else {
/*否则取第一个类型符合的*/
for (tmsg = msq->msg_first; tmsg; tmsg = tmsg->msg_next)
if (tmsg->msg_type == msgtyp)break;
nmsg = tmsg;}
} else {
/* msgtyp<0取 type值小于 msgtyp 的绝对值中最小的一个*/
for (leastp = tmsg = msq->msg_first; tmsg; tmsg = tmsg->msg_next)
if (tmsg->msg_type < leastp->msg_type) leastp = tmsg;
if (leastp && leastp->msg_type <= - msgtyp)nmsg = leastp;
}
if (nmsg) {
/* 确实找到了符合条件的消息 */
if ((msgsz < nmsg->msg_ts) && !(msgflg & MSG_NOERROR)) {
Page16
Linux 源代码分析报告
/* 若 找 到 的 消 息 大 小 超 过 允 许 的 最 大 长 度 且 msgflg 和 MSG_NOERROR没有置位, 则溢出*/
return -E2BIG;}msgsz = (msgsz > nmsg->msg_ts)? nmsg->msg_ts : msgsz;
/*若 msgflg 和 MSG_NOERROR置位,则取消息的前 msgsz 个字节*/
/*以下处理消息取出后队列的调整*/
if (nmsg == msq->msg_first) //取出的消息为第一个msq->msg_first = nmsg->msg_next;
else {for (tmsg = msq->msg_first; tmsg; tmsg = tmsg->msg_next)
if (tmsg->msg_next == nmsg) break;
tmsg->msg_next = nmsg->msg_next;if (nmsg == msq->msg_last)
msq->msg_last = tmsg;}
if (!(--msq->msg_qnum)) //若取出消息后,消息队列中无剩余消息msq->msg_last = msq->msg_first = NULL;
/*设置改动后,各变量的值*/
msq->msg_rtime = CURRENT_TIME;msq->msg_lrpid = current->pid;msgbytes -= nmsg->msg_ts; msghdrs--; msq->msg_cbytes -= nmsg->msg_ts;wake_up (&msq->wwait);
/*调用函数 wake_up, 唤醒等待发送消息的进程*/
if (put_user (nmsg->msg_type, &msgp->mtype) ||
Page17
Linux 源代码分析报告
copy_to_user (msgp->mtext, nmsg->msg_spot, msgsz))
/* 调 用 copy_to_user, 将 msgz 大 小 的 内 容 为 msgp->mtext 放 入msgh->msg_spot 指向的空间,正常则返回 0*/
msgsz = -EFAULT; kfree(nmsg);return msgsz;
} else { /*若没有找到符合的消息*/
if (msgflg & IPC_NOWAIT) {
/*若 IPC_NOWAIT置位,则进程不进入等待队列,返回错误信息*
/return -ENOMSG;
}if (signal_pending(current)) {
/*调用函数 sigal _pending,判断若当前进程所需的信号已到且进程未被堵塞*/
return -EINTR; }interruptible_sleep_on (&msq->rwait);
/*调用函数 interruptible_sleep_on,对等待发送消息的队列中的进程进行调度*/
}}return -1;
}
asmlinkage int sys_msgrcv (int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg)
函数参数:
Page18
Linux 源代码分析报告
msqid: 整型, 消息的序列号; msgp: 指向 msgbuf 结构的指针; msgsz: 消息的大小; msgtyp: 消息的类型; msgflg: 消息的标志符。函数返回类型:有关汇编链接的类型函数功能:系统调用,获得消息
{int ret;
lock_kernel(); //含义与 sys_msgsnd 相同ret = real_msgrcv (msqid, msgp, msgsz, msgtyp, msgflg);unlock_kernel();return ret;
}
返回错误类型一览表;返回类型 代表的意思E2BIG 消息大小超过 msgsz,且 msgflg 中 MSG_NOERROR 没有置位EACCES 接收消息的进程没有从消息队列读的权限。EFAULT
Msgp 所指的地址无法访问。EIDRM 当进程处于 sleep 状态时,消息已被从消息队列中删去。EINTR 当进程处于 sleep 状态时,消息队列已满,正在接收消息的进程获
得某个信号。EINVAL 错误的 msqid 或非正的 mtype,或错误的 msgsz。ENOMSG
Msgflg 中 IPC_NOWAIT 置位,且消息队列中没有需要的消息。
Page19
Linux 源代码分析报告
返回
4. 创建或获得消息的控制模块: asmlinkage int sys_msgget (key_t key, int msgflg)
函数参数: key:代表访问通信资源的权限 msgflg:标志位。函数返回类型:整型函数功能:传递键值,标志位以及其他的参数,获得消息。
{int id, ret = -EPERM;struct msqid_ds *msq;
lock_kernel();if (key == IPC_PRIVATE)
/*若键值为 IPC_RPIVATE,则创建一个新的消息队列,此消息队列不能通过其他 get 调用访问。调用者有独占权,通过 fork 系统调用,子进程继承这个消息对俄,属主可以和它的子进程共享*/
ret = newque(key, msgflg);else if ((id = findkey (key)) == -1) {
/*没有找到相应的 key */
if (!(msgflg & IPC_CREAT))
/*IPC_CREAT置位表示创建尚不存在的新的消息*/
ret = -ENOENT;else
ret = newque(key, msgflg);} else if (msgflg & IPC_CREAT && msgflg & IPC_EXCL) {
/*若 msgflg 中 IPC_CREAT置位且相应的消息已存在,而且 IPC_EXCL置Page20
Linux 源代码分析报告
位,返回错误*/
ret = -EEXIST;} else {
/*若没有制定标志位,则内核试着查找有相同键值的已经存在的消息*/
msq = msgque[id];
if (msq == IPC_UNUSED || msq == IPC_NOID)//消息不存在ret = -EIDRM;
else if (ipcperms(&msq->msg_perm, msgflg)) //权限被否认ret = -EACCES;
elseret = (unsigned int) msq->msg_perm.seq * MSGMNI + id;
/*取得该消息的 ID 号*/
}unlock_kernel();return ret;
}
5. 消息传递的控制模块: asmlinkage int sys_msgctl (int msqid, int cmd, struct msqid_ds
*buf)
函数参数:msqid:消息序列号cmd:处理消息的命令buf:指向 msqid_ds 的指针
函数返回类型:有关汇编连接的类型,有关错误返回的信息请看返回错误类型一览表。函数功能: 系统调用。处理各种有关消息处理的命令,详情请看命令览表{
int id, err = -EINVAL;struct msqid_ds*msq;struct msqid_dstbuf;
Page21
Linux 源代码分析报告
struct ipc_perm*ipcp;
lock_kernel();if (msqid < 0 || cmd < 0)
goto out;err = -EFAULT;switch (cmd) {
/*开始处理命令*/
case IPC_INFO:
/*IPC_INFO输出 msginfo 结构中有关消息队列的相关值的最大值*/
case MSG_INFO:
/*MSG_INFO输出的与 ICP_INFOU 有所不同,它给出 msgpool 中使用过的等待队列的数目,msgmap 中消息的数目和系统存储在 msgtql 中的总的消息数*/
if (!buf)goto out;
{
/ 得到消息的数量信息*/
struct msginfo msginfo;msginfo.msgmni = MSGMNI;msginfo.msgmax = MSGMAX;msginfo.msgmnb = MSGMNB;msginfo.msgmap = MSGMAP;msginfo.msgpool = MSGPOOL;msginfo.msgtql = MSGTQL;msginfo.msgssz = MSGSSZ;msginfo.msgseg = MSGSEG;if (cmd == MSG_INFO) {
msginfo.msgpool = used_queues;msginfo.msgmap = msghdrs;msginfo.msgtql = msgbytes;
}
Page22
Linux 源代码分析报告
err = -EFAULT; if (copy_to_user (buf, &msginfo, sizeof(struct msginfo)))
/*调用函数 copy_to_user 将 msginfo 所指的内容放入 buf 中*/
goto out; err = max_msqid;goto out;
}case MSG_STAT:
/* MSG_STAT 变量允许参数传递系统内部消息队列表的索引*/
if (!buf)goto out;
err = -EINVAL;if (msqid > max_msqid)
goto out;msq = msgque[msqid];if (msq == IPC_UNUSED || msq == IPC_NOID)
goto out;err = -EACCES;
if (ipcperms (&msq->msg_perm, S_IRUGO)) //权限被否认goto out;
id = (unsigned int) msq->msg_perm.seq * MSGMNI + msqid;
/*设置 tbuf 的各项值*/
tbuf.msg_perm = msq->msg_perm;tbuf.msg_stime = msq->msg_stime;tbuf.msg_rtime = msq->msg_rtime;tbuf.msg_ctime = msq->msg_ctime;tbuf.msg_cbytes = msq->msg_cbytes;tbuf.msg_qnum = msq->msg_qnum;tbuf.msg_qbytes = msq->msg_qbytes;tbuf.msg_lspid = msq->msg_lspid;tbuf.msg_lrpid = msq->msg_lrpid;err = -EFAULT;if (copy_to_user (buf, &tbuf, sizeof(*buf)))
/*将 tbuf 的内容拷贝 buf 中去*/
Page23
Linux 源代码分析报告
goto out; err = id;goto out;
case IPC_SET:
/*IPC_SET允许消息队列的拥有者,模式和最大允许的字节数被改变*/
if (!buf)goto out;
err = -EFAULT; if (!copy_from_user (&tbuf, buf, sizeof (*buf)))
err = 0; break;
case IPC_STAT:
/*IPC_STAT 将有关的消息队列的 msqid_id 结构拷贝到用户的存储区域*/
if (!buf)goto out;
break;}
id = (unsigned int) msqid % MSGMNI; // 获得消息的 ID 号msq = msgque [id];err = -EINVAL;
if (msq == IPC_UNUSED || msq == IPC_NOID) //消息不存在goto out;
err = -EIDRM;
if (msq->msg_perm.seq != (unsigned int) msqid / MSGMNI) //权限被否认goto out;
ipcp = &msq->msg_perm;
switch (cmd) {case IPC_STAT:
err = -EACCES;
if (ipcperms (ipcp, S_IRUGO)) //确认访问权限goto out;
Page24
Linux 源代码分析报告
/*IPC_STAT 将有关的消息队列的 msqid_id 结构拷贝到用户的存储区域*/
tbuf.msg_perm = msq->msg_perm;tbuf.msg_stime = msq->msg_stime;tbuf.msg_rtime = msq->msg_rtime;tbuf.msg_ctime = msq->msg_ctime;tbuf.msg_cbytes = msq->msg_cbytes;tbuf.msg_qnum = msq->msg_qnum;tbuf.msg_qbytes = msq->msg_qbytes;tbuf.msg_lspid = msq->msg_lspid;tbuf.msg_lrpid = msq->msg_lrpid;err = -EFAULT;if (!copy_to_user (buf, &tbuf, sizeof (*buf)))
err = 0;goto out;
case IPC_SET:
/*IPC_SET允许消息队列的拥有者,模式和最大允许的字节数被改变*/
err = -EPERM;if (current->euid != ipcp->cuid && current->euid != ipcp->uid && !capable(CAP_SYS_ADMIN)) /* We _could_ check for CAP_CHOWN above, but we don't */
goto out;if(tbuf.msg_qbytes > MSGMNB && !capable(CAP_SYS_RESOURCE))
goto out;msq->msg_qbytes = tbuf.msg_qbytes;ipcp->uid = tbuf.msg_perm.uid;ipcp->gid = tbuf.msg_perm.gid;ipcp->mode = (ipcp->mode & ~S_IRWXUGO) |
(S_IRWXUGO & tbuf.msg_perm.mode);msq->msg_ctime = CURRENT_TIME;err = 0;goto out;
case IPC_RMID:
/*IPC_RMID允许超级用户和消息队列的创建者或拥有者删除队列*/
err = -EPERM;if (current->euid != ipcp->cuid && current->euid != ipcp->uid && !capable(CAP_SYS_ADMIN))
Page25
Linux 源代码分析报告
/*无删除的权限*/
goto out;
freeque (id); //删除此队列err = 0;goto out;
default:err = -EINVAL;goto out;
}out:
unlock_kernel();return err;
}
返回错误类型一览表:错误类型 代表的意思EDIRM 将消息删去EINVAL 错误的 cmd 或 msgqid
EPERMCmd 参数值为 IPC_SET 或 IPC_RMID,但是,调用的进程的 有效的 UID 没有足够的权限执行此项命令.
返回
6. 其他相关的函数: static int findkey (key_t key)
函数参数:key:代表访问通信资源的权限函数返回类型:找到了者返回 id 号,否则返回-1;函数功能:找到所需的 key值
{int id;
Page26
Linux 源代码分析报告
struct msqid_ds*msq;
for (id = 0; id <= max_msqid; id++) {while ((msq = msgque[id]) == IPC_NOID)
interruptible_sleep_on(&msg_lock);if (msq == IPC_UNUSED)
continue;
if (key == msq->msg_perm.key) //找到相应的 key值return id;
}return -1;
}
static int newque (key_t key, int msgflg)
函数参数: key:代表访问通信资源的权限 msgflg:消息的标志符函数返回类型:整型函数功能:寻找可获得的消息队列资源。
{int id;struct msqid_ds *msq;struct ipc_perm *ipcp;
for (id = 0; id < MSGMNI; id++) if (msgque[id] == IPC_UNUSED) {
msgque[id] = (struct msqid_ds ) IPC_NOID;
/*给消息队列置位为 IPC_NOID*/
goto found;}
return -ENOSPC; //没有空间可以分配found:
msq = (struct msqid_ds *) kmalloc (sizeof (*msq), GFP_KERNEL);
if (!msq) { //若分配空间失败Page27
Linux 源代码分析报告
msgque[id] = (struct msqid_ds ) IPC_UNUSED;
/*置位为 IPC_UNUSED*/
wake_up (&msg_lock);
/*调用 wake_up 函数,唤醒被阻塞的队列上的进程*/
return -ENOMEM;}
/*设置 ipc_perm 结构的内容*/
ipcp = &msq->msg_perm;ipcp->mode = (msgflg & S_IRWXUGO);ipcp->key = key;ipcp->cuid = ipcp->uid = current->euid;ipcp->gid = ipcp->cgid = current->egid;
/* 设置msq 的各项内容*/
msq->msg_perm.seq = msg_seq;msq->msg_first = msq->msg_last = NULL;msq->rwait = msq->wwait = NULL;msq->msg_cbytes = msq->msg_qnum = 0;msq->msg_lspid = msq->msg_lrpid = 0;msq->msg_stime = msq->msg_rtime = 0;msq->msg_qbytes = MSGMNB;msq->msg_ctime = CURRENT_TIME;if (id > max_msqid)
max_msqid = id;
msgque[id] = msq; //将新成员加入消息队列used_queues++;
wake_up (&msg_lock); //唤醒被阻塞的进程return (unsigned int) msq->msg_perm.seq * MSGMNI + id;
} static void freeque (int id)
函数参数:id 消息序列号函数返回类型:空函数功能:从 msgque 数组中删去下标为 id 的消息
{struct msqid_ds *msq = msgque[id];struct msg*msgp, *msgh;
Page28
Linux 源代码分析报告
msq->msg_perm.seq++;msg_seq = (msg_seq+1) % ((unsigned)(1<<31)/MSGMNI);
/* 空闲的消息序列数增 1, 且避免溢出 */
msgbytes -= msq->msg_cbytes;if (id == max_msqid)
while (max_msqid && (msgque[--max_msqid] == IPC_UNUSED));msgque[id] = (struct msqid_ds *) IPC_UNUSED;
used_queues--; //使用的消息队列数减 1
while (waitqueue_active(&msq->rwait) || waitqueue_active(&msq->wwait)) {
wake_up (&msq->rwait); //唤醒等待发送消息的队列上的进程
wake_up (&msq->wwait);//唤醒等待获取消息的队列上的进程schedule(); //对等待队列上的进程进行进程调度
}for (msgp = msq->msg_first; msgp; msgp = msgh ) {
/*释放掉msq 所指的空间*/
msgh = msgp->msg_next;msghdrs--;kfree(msgp);
}kfree(msq);
}
讨论: 在消息传递机制中,当读取一个消息后,消息将从队列
移去,其他进程不能读到。若因为接收的缓冲区太小造成消息被截断,截断的部分将永远丢失。
进程必须通过带有 IPC_RMID 的 sys _msgctl 调用,来显示的删除消息队列。如果不是这样则消息队列可以长久的存在。则样就回导致系统很难判断,消息是为了将来进程
Page29
Linux 源代码分析报告
访问而留下来还是被无意的抛弃了,或是由于想要释放它的进程不正常终止了,这样导致系统无限的保存这消息。如果这种情况经常发生,这种消息资源就会用光。
总的说来,消息队列传递数据时是以一种不连续消息的方式,这样就可以更加灵活的处理数据。
Page30