Linux 源代码分析 消息管理

43
Linux 源源源源源源源 Linux 源源源源源 msg.c 作作 作作 974 Page1

description

 

Transcript of Linux 源代码分析 消息管理

Page 1: Linux 源代码分析 消息管理

Linux 源代码分析报告

Linux 源代码分析

内容:msg.c

作者:欧阳杨 单位:浙江大学混合 974

1999 年 12 月 10 日于求是园

Page1

Page 2: Linux 源代码分析 消息管理

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

Page 3: Linux 源代码分析 消息管理

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

Page 4: Linux 源代码分析 消息管理

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

Page 5: Linux 源代码分析 消息管理

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

Page 6: Linux 源代码分析 消息管理

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

Page 7: Linux 源代码分析 消息管理

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

Page 8: Linux 源代码分析 消息管理

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

Page 9: Linux 源代码分析 消息管理

/*调用函数 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

Page 10: Linux 源代码分析 消息管理

调用函数 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

Page 11: Linux 源代码分析 消息管理

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

Page 12: Linux 源代码分析 消息管理

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

Page 13: Linux 源代码分析 消息管理

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

Page 14: Linux 源代码分析 消息管理

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

Page 15: Linux 源代码分析 消息管理

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

Page 16: Linux 源代码分析 消息管理

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

Page 17: Linux 源代码分析 消息管理

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

Page 18: Linux 源代码分析 消息管理

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

Page 19: Linux 源代码分析 消息管理

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

Page 20: Linux 源代码分析 消息管理

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

Page 21: Linux 源代码分析 消息管理

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

Page 22: Linux 源代码分析 消息管理

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

Page 23: Linux 源代码分析 消息管理

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

Page 24: Linux 源代码分析 消息管理

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

Page 25: Linux 源代码分析 消息管理

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

Page 26: Linux 源代码分析 消息管理

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

Page 27: Linux 源代码分析 消息管理

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

Page 28: Linux 源代码分析 消息管理

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

Page 29: Linux 源代码分析 消息管理

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

Page 30: Linux 源代码分析 消息管理

Linux 源代码分析报告

访问而留下来还是被无意的抛弃了,或是由于想要释放它的进程不正常终止了,这样导致系统无限的保存这消息。如果这种情况经常发生,这种消息资源就会用光。

总的说来,消息队列传递数据时是以一种不连续消息的方式,这样就可以更加灵活的处理数据。

Page30