Linux 源代码分析 消息管理

497 views

Published on

Published in: Technology, News & Politics
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
497
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
4
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Linux 源代码分析 消息管理

  1. 1. Linux 源代码分析报告Linux 源代码分析内容: msg.c作者:欧阳杨单位:浙江大学混合 974 1999 年 12 月 10 日于求是园 Page1
  2. 2. Linux 源代码分析报告Linux 源代码分析 ----ipc/msg.c 前言: 在操作系统中, 有些进程存在着相互制约的关系,这些制约关系来源于并行进程的相互合作和资源共享。为了使合作进程和资源共享进程能协调一致的向前推进,必须使他们保持联系,一边相互了解。进程相互间需要交换一定数量的信息, 以便协调一致共同完成指定的任务. 这种机制就叫做进程间通信,或 IP C . 在 linux 中支持 UNIX S Y S T EM V的三种通信机制 : 消息队列 , 信号量和共享内存 . 现就消息队列这种机制进行分析. 包含的头文件: #include <linux/malloc.h> #include <linux/msg.h> #include <linux/interrupt.h> #include <linux/smp_lock.h> #include <linux/init.h> #include <asm/uaccess.h> m s g . c 中包含的函数模块: Page2
  3. 3. Linux 源代码分析报告 有关进程间通信资源的属性:  键 (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; // 序列号 , 计算标识符]; 系统在创建消息队列的同时设定了访问权限 , 并返 回一个标识. 进程通信时必须先传递该标识, 待函数 i p c p er m s ( ) 确 认 权 限 后 才 可 以 访 问 通 信 资 源 . 访 问 权 限 由 ip c_ p erm 结 构 描 述 . 通 过 key 可 以 得 到 引 用 标 识 , Page3
  4. 4. Linux 源代码分析报告从而访问 通信资源. Key 为 pu bli c , 则任何进程都可以通过 key 得到引用标识.2. struct msg { struct msg *msg_next; //消息队列中的下一个 long msg_type; //消息的类型 char *msg_spot; //存放消息内容的地址 time_t msg_time; //消息发送的时间 short msg_ts; //消息的长度 };m s g 结构用来存放消息的有关信息.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
  5. 5. Linux 源代码分析报告 ushort msg_lrpid; //最后一个接收消息的进程的标识号 }; 每一个 m s qid_d s 结构代表一个消息队列, 是进 程读写的信息的存储空间。static struct msqid_ds *msgque[MSGMNI]; 定义了一个消息队列数组 m s g q u e , 数组的元素类 型是指向 m s qid_d s 结构的指针。消息在队列中是按 到来的顺序维护。进程读消息时,这些消息按 FIFO 从队列中移去。 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 消息队列 以下是消息传递的示意图: Page5
  6. 6. Linux 源代码分析报告 Struct msgqid_ds receiver sender msg msg msg receiver sender 4. struct msgbuf { long mtype; //消息的类型 char mtext[1]; //消息的内容 }; 存放消息的信息。 5. struct wait_queue { struct wait_queue *next; //指针指向等待队列的下一个 struct task_struct *task; /*task_struct 存放的是进程控制块的信息 */ }; wait_queu e 代表各种各样的进程等待队列。 初始化变量: static int msgbytes = 0; //代表消息的字节数 static int msghdrs = 0; static unsigned short msg_seq = 0; //代表消息的序列号 static int used_queues = 0; //代表使用的消息数 Page6
  7. 7. Linux 源代码分析报告 static int max_msqid = 0; //代表最大的消息序列号 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. 发送消息模块: Page7
  8. 8. Linux 源代码分析报告  static int real_msgsnd (int msqid, struct msgbuf *msgp, size_t 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
  9. 9. 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; /*调用函数 int ipcperms (struct ipc_perm *ipcp, short flag) 进 程 通 信 时 先 传 递 flag if ( ipcperm(ipcp, S_IWUGO)) 标 识 , 然 后 由 此 函 数 确 认 访 问 通 信 资源的权限。 /*stat.h 中 S_IWUGO 则权限没有被确认。 此函数若返回-1 定 义 为 S_IWUSR|S_IWURP|S_IWOTH, 即 { int requested_mode, granted_mode; requested_mode = (flag >> 6) | (flag >> 3) | flag; 00020 表示00200|00020|00002。 其中 00200 表示资源创建者的写权限, granted_mode = ipcp->mode; if (current->euid == ipcp->cuid || current->euid == ipcp->uid)资源创建组的写权限, 00002 表示所有用户写权限 */ /*各 id 号一致*/ granted_mode >>= 6; else if (in_group_p(ipcp->cgid) || in_group_p(ipcp->gid)) return -EACCES; /*调 用 函 数 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; Page9 return 0;} }*/
  10. 10. 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); 调用函数 void interruptible_sleep_on(struct wait_queue **p) /*调度其他等待队列上的进程,直到此进程被唤醒 */ { SLEEP_ON_VAR /*见 sched.c 中 #define SLEEP_ON_VAR unsigned long flags; struct wait_queue wait; */ Page10
  11. 11. Linux 源代码分析报告 current->state = TASK_INTERRUPTIBLE; /*将当前任务的状态置为 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
  12. 12. 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); spin_unlock_irqrestore(&runqueue_lock, flags); } Page12
  13. 13. 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(); Page13
  14. 14. Linux 源代码分析报告 /*调用函数 lock_kernel{ do{}while(0);}, 此函数用于多 cpu 处理,当一个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: 消息的类型; msgflg: 消息的标志符。 函数返回类型:若接收正确,返回接收到的消息字节数。否则,返回 错误信息,详情请看 返回错误一览表。函数功能:从消息队列中接收标示号为 msqid,内容为 msgp 所指向的信息。 Page14
  15. 15. Linux 源代码分析报告{ 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 则权限没有被确认 */ return -EACCES; } /*以下寻找类型正确的消息 */ if (msgtyp == 0) // msgtyp=0 取消息队列的第一个 nmsg = msq->msg_first; Page15
  16. 16. Linux 源代码分析报告 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)) { /* 若 找 到 的 消 息 大 小 超 过 允 许 的 最 大 长 度 且 msgflg 和 MSG_NOERROR 没有置位, 则溢出 */ return -E2BIG; } msgsz = (msgsz > nmsg->msg_ts)? nmsg->msg_ts : msgsz; Page16
  17. 17. Linux 源代码分析报告 /*若 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) || copy_to_user (msgp->mtext, nmsg->msg_spot, msgsz)) /* 调 用 copy_to_user, 将 msgz 大 小 的 内 容 为 msgp->mtext 放 入 msgh->msg_spot 指向的空间,正常则返回 0*/ Page17
  18. 18. Linux 源代码分析报告 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) 函数参数: msqid: 整型, 消息的序列号; msgp: 指向 msgbuf 结构的指针; Page18
  19. 19. Linux 源代码分析报告 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 置位,且消息队列中没有需要的消息。 返回4 . 创建或获得消息的控制模块:  asmlinkage int sys_msgget (key_t key, int msgflg) 函数参数: key:代表访问通信资源的权限 msgflg:标志位。 函数返回类型:整型 函数功能:传递键值,标志位以及其他的参数,获得消息。{ int id, ret = -EPERM; Page19
  20. 20. Linux 源代码分析报告 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置位,返回错误 */ ret = -EEXIST; } else { /*若没有制定标志位,则内核试着查找有相同键值的已经存在的消息 */ msq = msgque[id]; if (msq == IPC_UNUSED || msq == IPC_NOID)//消息不存在 ret = -EIDRM; else if (ipcperms(&msq->msg_perm, msgflg)) //权限被否认 ret = -EACCES; else ret = (unsigned int) msq->msg_perm.seq * MSGMNI + id; /*取得该消息的 ID 号 */ Page20
  21. 21. Linux 源代码分析报告 } 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; 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: Page21
  22. 22. Linux 源代码分析报告/*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; } 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; Page22
  23. 23. Linux 源代码分析报告 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 中去 */ 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: Page23
  24. 24. Linux 源代码分析报告 /*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; /*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))) Page24
  25. 25. Linux 源代码分析报告 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 dont */ 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)) /*无删除的权限 */ goto out; freeque (id); //删除此队列 err = 0; goto out; default: err = -EINVAL; goto out; }out: Page25
  26. 26. Linux 源代码分析报告 unlock_kernel(); return err;} 返回错误类型一览表:错误类型 代表的意思EDIRM 将消息删去EINVAL 错误的 cmd 或 msgqidEPERM Cmd 参数值为 IPC_SET 或 IPC_RMID,但是,调用的进程的 有 效的 UID 没有足够的权限执行此项命令. 返回6. 其他相关的函数:  static int findkey (key_t key) 函数参数: key:代表访问通信资源的权限 函数返回类型:找到了者返回 id 号,否则返回 -1; 函数功能:找到所需的 key 值{ int id; 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) 函数参数: Page26
  27. 27. Linux 源代码分析报告 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) { //若分配空间失败 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; Page27
  28. 28. Linux 源代码分析报告 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; 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);//唤醒等待获取消息的队列上的进程 Page28
  29. 29. Linux 源代码分析报告 schedule(); //对等待队列上的进程进行进程调度 } for (msgp = msq->msg_first; msgp; msgp = msgh ) { /*释放掉 msq 所指的空间 */ msgh = msgp->msg_next; msghdrs--; kfree(msgp); } kfree(msq);} 讨论:  在消息传递机制中,当读取一个消息后,消息将从 队列移去,其他进程不能读到。若因为接收的缓冲区 太小造成消息被截断,截断的部分将永远丢失。  进程必须通过带有 IPC_RMID 的 sys _msgctl 调用,来 显示的删除消息队列。如果不是这样则消息队列可以 长久的存在。则样就回导致系统很难判断,消息是为 了将来进程访问而留下来还是被无意的抛弃了,或 是由于想要释放它的进程不正常终止了,这样导致 系统无限的保存这消息。如果这种情况经常发生,这 种消息资源就会用光。  总的说来,消息队列传递数据时是以一种不连续消 息的方式,这样就可以更加灵活的处理数据。 Page29

×