Your SlideShare is downloading. ×
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Linux 源代码分析 消息管理
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Linux 源代码分析 消息管理

331

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
331
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
3
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Linux 源代码分析报告Linux 源代码分析内容: msg.c作者:欧阳杨单位:浙江大学混合 974 1999 年 12 月 10 日于求是园 Page1
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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

×