感谢支持
我们一直在努力

Linux内核情景分析之消息队列

早期的Unix通信只有管道与信号,管道的缺点:

所载送的信息是无格式的字节流,不知道分界线在哪,也没通信规范,另外缺乏控制手段,比如保温优先级,管道机制的大小只有1页,管道很容易写满而读取没有及时,发送者只能休眠,强化了管道机制同步要求.另外管道机制开销不少,尤其是当发送信息量很少时,平均每个字节所耗费的代价非常高
 
Linux内核为系统IPC提供了一个统一的系统调用ipc()

  1. int ipc(unsignedint call,int firtst,int second,int third,void*ptr,int firth);

其中第一个参数为具体的操作码

  1. #define SEMOP 1
  2. #define SEMGET 2
  3. #define SEMCTL 3
  4. #define MSGSND 11
  5. #define MSGRCV 12
  6. #define MSGGET 13
  7. #define MSGCTL 14
  8. #define SHMAT 21
  9. #define SHMDT 22
  10. #define SHMGET 23
  11. #define SHMCTL 24

操作码SEM开头的都是信号量设定,MSG开头则是消息队列,”SHM”开头则是共享内存区设定,其他参数因具体操作而不同,不过为了方便使用,c语言库函数分别提供了semget,msgget,msgsnd等库函数,这些库函数把其调用统一成了系统调用ipc.

内核入口为sys_ipc()
 
 
消息队列并没有纳入文件系统范畴,所以并不占用打开文件号,内核有个全局的数据结构msg_ids.专门用来管理报文队列
static struct ipc_ids msg_ids;

  1. struct ipc_ids {
  2. int size;
  3. int in_use;
  4. int max_id;
  5. unsignedshort seq;
  6. unsignedshort seq_max;
  7. struct semaphore sem;
  8. spinlock_t ary;
  9. struct ipc_id* entries;//指向一个结构数组
  10. };

//ipc_id如下定义

  1. struct ipc_id {
  2. struct kern_ipc_perm* p;
  3. };

kern_ipc_perm结构如下定义

  1. /* used by in-kernel data structures */
  2. struct kern_ipc_perm
  3. {
  4. key_t key;//建值
  5. uid_t uid;
  6. gid_t gid;
  7. uid_t cuid;
  8. gid_t cgid;
  9. mode_t mode;
  10. unsignedlong seq;
  11. };

 

 
每个报文队列都有个队列头,msg_queue数据结构

  1. /* one msq_queue structure for each present queue on the system */
  2. struct msg_queue {
  3. struct kern_ipc_perm q_perm;
  4. time_t q_stime;/* last msgsnd time */
  5. time_t q_rtime;/* last msgrcv time */
  6. time_t q_ctime;/* last change time */
  7. unsignedlong q_cbytes;/* current number of bytes on queue */
  8. unsignedlong q_qnum;/* number of messages in queue */
  9. unsignedlong q_qbytes;/* max number of bytes on queue */
  10. pid_t q_lspid;/* pid of last msgsnd */
  11. pid_t q_lrpid;/* last receive pid */
  12. struct list_head q_messages;
  13. struct list_head q_receivers;
  14. struct list_head q_senders;
  15. };

每个msg_queue数据结构唯一对应着一个报文队列,这些数据结构以及结构之间的关系可以如下总结:

全局ipc_ids数据结构的msg_ids是系统中所有报文队列的总根,总跟的指针entries指向一个ipc_id结构数组,数组中的每个元素都是ipc_id数据结构,结构中有个指针p指向一个kern_ipc_perm数据结构,由于kern_ipc_perm数据结构是报文队列头msg_queue的第一个成分,数组元素的指针p实际指向一个报文对哦,数组大小已经决定了可以建立的报文队列数目

消息队列的创建或获取(msgget)

  1. //可以用于2个目的,通过给定的key创建队列,通过给定的key查找已存在队列
  2. asmlinkage long sys_msgget (key_t key,int msgflg)
  3. {
  4. int id, ret =-EPERM;
  5. struct msg_queue *msq;//队列
  6. down(&msg_ids.sem);
  7. if(key == IPC_PRIVATE)//自己私用,无条件创建一个报文队列
  8. ret = newque(key, msgflg);//根据key与msgflg
  9. elseif((id = ipc_findkey(&msg_ids, key))==-1){/* key没有找到key not used */
  10. if(!(msgflg & IPC_CREAT))//没找到,但没设定IPC_CREAT那就返回错误
  11. ret =-ENOENT;
  12. else//设定了就创建报文队列
  13. ret = newque(key, msgflg);
  14. }elseif(msgflg & IPC_CREAT && msgflg & IPC_EXCL){//同时设定了IPC_CREAT与IPC_EXCL返回错误
  15. ret =-EEXIST;
  16. }else{
  17. msq = msg_lock(id);
  18. if(msq==NULL)
  19. BUG();
  20. if(ipcperms(&msq->q_perm, msgflg))//检查访问权限是否符合规则
  21. ret =-EACCES;
  22. else
  23. ret = msg_buildid(id, msq->q_perm.seq);//将数组下标转换一体化的标识号
  24. msg_unlock(id);
  25. }
  26. up(&msg_ids.sem);
  27. return ret;//返回标识号
  28. }

查看具体的队列创建函数newque()

  1. staticint newque (key_t key,int msgflg)
  2. {
  3. int id;
  4. struct msg_queue *msq;//队列头
  5. msq =(struct msg_queue *) kmalloc (sizeof(*msq), GFP_KERNEL);//分配结构
  6. if(!msq)
  7. return-ENOMEM;
  8. id = ipc_addid(&msg_ids,&msq->q_perm, msg_ctlmni);//分配一个标识号
  9. if(id ==-1){
  10. kfree(msq);
  11. return-ENOSPC;
  12. }//以下是报文队列头各种初始化
  13. msq->q_perm.mode =(msgflg & S_IRWXUGO);
  14. msq->q_perm.key = key;//key值
  15. msq->q_stime = msq->q_rtime =0;
  16. msq->q_ctime = CURRENT_TIME;
  17. msq->q_cbytes = msq->q_qnum =0;
  18. msq->q_qbytes = msg_ctlmnb;
  19. msq->q_lspid = msq->q_lrpid =0;
  20. INIT_LIST_HEAD(&msq->q_messages);
  21. INIT_LIST_HEAD(&msq->q_receivers);
  22. INIT_LIST_HEAD(&msq->q_senders);
  23. msg_unlock(id);
  24. //将标识号转换为一个一体化的标识号,因为实际分配的id实际是数组下标会重复使用
  25. return msg_buildid(id,msq->q_perm.seq);
  26. }

每个已建立的报文队列由一个标识号来代表,报文队列的标识号是全局的,所以必须把这全局范围内的唯一性,标识号由ipc_addid()分配,

  1. /**
  2. * ipc_addid - add an IPC identifier
  3. * @ids: IPC identifier set
  4. * @new: new IPC permission set
  5. * @size: new size limit for the id array
  6. *
  7. * Add an entry 'new' to the IPC arrays. The permissions object is
  8. * initialised and the first free entry is set up and the id assigned
  9. * is returned. The list is returned in a locked state on success.
  10. * On failure the list is not locked and -1 is returned.
  11. */
  12. //全局队列管理结构 新创建的队列头的这个结构
  13. int ipc_addid(struct ipc_ids* ids,struct kern_ipc_perm*new,int size)
  14. {
  15. int id;
  16. size = grow_ary(ids,size);//增加管理数组的大小
  17. for(id =0; id < size; id++){
  18. if(ids->entries[id].p == NULL)//总根看管理数组的哪个为空
  19. goto found;
  20. }
  21. return-1;
  22. found:
  23. ids->in_use++;//已使用++
  24. if(id > ids->max_id)//最大的key值
  25. ids->max_id = id;
  26. new->cuid =new->uid = current->euid;//uid赋值
  27. new->gid =new->cgid = current->egid;//gid赋值
  28. new->seq = ids->seq++;//序列号
  29. if(ids->seq > ids->seq_max)//超过了最大的限定
  30. ids->seq =0;//从0开始继续
  31. spin_lock(&ids->ary);
  32. ids->entries[id].p =new;//挂钩,新创建的报文头与总跟挂钩成功
  33. return id;
  34. }

库函数msgsnd-报文发送

参与通信双方通过mssget创建了一个报文队列后或取得该队列的标识号以后就可以向该队列发送或接收报文了
发送格式为msgbuf

  1. /* message buffer for msgsnd and msgrcv calls */
  2. struct msgbuf {
  3. long mtype;/* type of message */
  4. char mtext[1];/* message text */
  5. };

内核保存消息队列的格式

  1. /* one msg_msg structure for each message */
  2. struct msg_msg {
  3. struct list_head m_list;
  4. long m_type;//类型
  5. int m_ts;/*长度 message text size */
  6. struct msg_msgseg* next;
  7. /* the actual message follows immediately */
  8. };
  1. struct msg_msgseg {
  2. struct msg_msgseg* next;
  3. /* the next part of the message follows immediately */
  4. };

分配一页,第一个除去msg_msg其他的用于保存消息,如果不够继续分配一页除去msg_msg_seq以此类推


 

  1. //标识号 发送格式 //大小 //标志位
  2. asmlinkage long sys_msgsnd (int msqid,struct msgbuf *msgp,size_t msgsz,int msgflg)
  3. {
  4. struct msg_queue *msq;//队列头
  5. struct msg_msg *msg;//内核保存信息的格式
  6. long mtype;
  7. int err;
  8. //消息不可以超过8k
  9. if(msgsz > msg_ctlmax ||(long) msgsz <0|| msqid <0)
  10. return-EINVAL;
  11. if(get_user(mtype,&msgp->mtype))//从用户空间拷贝到内核
  12. return-EFAULT;
  13. if(mtype <1)
  14. return-EINVAL;
  15. msg = load_msg(msgp->mtext, msgsz);//分配缓冲区保存消息(从用户拷贝到内核)
  16. if(IS_ERR(msg))
  17. return PTR_ERR(msg);
  18. msg->m_type = mtype;//消息类型
  19. msg->m_ts = msgsz;//消息大小
  20. msq = msg_lock(msqid);//根据给定的标号msg_msg找到相应的报文队列,将其数据结构上锁
  21. err=-EINVAL;
  22. if(msq==NULL)
  23. goto out_free;
  24. retry:
  25. err=-EIDRM;
  26. if(msg_checkid(msq,msqid))//验证下id号
  27. goto out_unlock_free;
  28. err=-EACCES;
  29. if(ipcperms(&msq->q_perm, S_IWUGO))//检查是否有权限向这个队列发送报文
  30. goto out_unlock_free;
  31. //当前报文大小+当前队列统计的字节数超过了报文队列的总容量.或者报文的个数超过了限制,那就不可以发送了
  32. if(msgsz + msq->q_cbytes > msq->q_qbytes ||
  33. 1+ msq->q_qnum > msq->q_qbytes){
  34. struct msg_sender s;
  35. if(msgflg&IPC_NOWAIT){//是否等待,不等待直接退出
  36. err=-EAGAIN;
  37. goto out_unlock_free;
  38. }
  39. ss_add(msq,&s);//挂载到报文队列q_sender链,这样可以通过此链找到休眠正在等待发送的进程
  40. msg_unlock(msqid);
  41. schedule();//调度
  42. current->state= TASK_RUNNING;
  43. msq = msg_lock(msqid);
  44. err =-EIDRM;
  45. if(msq==NULL)
  46. goto out_free;
  47. ss_del(&s);//删除
  48. if(signal_pending(current)){
  49. err=-EINTR;
  50. goto out_unlock_free;
  51. }
  52. goto retry;//重新运行一遍
  53. }
  54. if(!pipelined_send(msq,msg)){//如果有相关进程正在读这个报文就不用放入队列了
  55. /* noone is waiting for this message, enqueue it */
  56. list_add_tail(&msg->m_list,&msq->q_messages);//链入队列
  57. msq->q_cbytes += msgsz;//总数++
  58. msq->q_qnum++;//数目++
  59. atomic_add(msgsz,&msg_bytes);
  60. atomic_inc(&msg_hdrs);
  61. }
  62. err =0;
  63. msg = NULL;
  64. msq->q_lspid = current->pid;
  65. msq->q_stime = CURRENT_TIME;
  66. out_unlock_free:
  67. msg_unlock(msqid);
  68. out_free:
  69. if(msg!=NULL)
  70. free_msg(msg);
  71. return err;
  72. }

load_msg用于消息分割,在内核保存,见上图

  1. //报文的源
  2. staticstruct msg_msg* load_msg(void* src,int len)
  3. {
  4. struct msg_msg* msg;
  5. struct msg_msgseg** pseg;
  6. int err;
  7. int alen;
  8. alen = len;
  9. if(alen > DATALEN_MSG)//一页减去msg结构大小
  10. alen = DATALEN_MSG;
  11. msg =(struct msg_msg *) kmalloc (sizeof(*msg)+ alen, GFP_KERNEL);//一个报文的头
  12. if(msg==NULL)
  13. return ERR_PTR(-ENOMEM);
  14. msg->next = NULL;
  15. if(copy_from_user(msg+1, src, alen)){//从msg的尾部开始拷贝
  16. err =-EFAULT;
  17. goto out_err;
  18. }
  19. len -= alen;//剩余长度
  20. src =((char*)src)+alen;//剩余源头
  21. pseg =&msg->next;//指向下一个页
  22. while(len >0){//还有剩余长度
  23. struct msg_msgseg* seg;
  24. alen = len;
  25. if(alen > DATALEN_SEG)//是否超过page-msgseg
  26. alen = DATALEN_SEG;
  27. seg =(struct msg_msgseg *) kmalloc (sizeof(*seg)+ alen, GFP_KERNEL);//获取一页
  28. if(seg==NULL){
  29. err=-ENOMEM;
  30. goto out_err;
  31. }
  32. *pseg = seg;//链接
  33. seg->next = NULL;
  34. if(copy_from_user (seg+1, src, alen)){//继续拷贝
  35. err =-EFAULT;
  36. goto out_err;
  37. }
  38. pseg =&seg->next;
  39. len -= alen;
  40. src =((char*)src)+alen;
  41. }
  42. return msg;
  43. out_err:
  44. free_msg(msg);
  45. return ERR_PTR(err);
  46. }
  1. /* one msg_sender for each sleeping sender */
  2. struct msg_sender {
  3. struct list_head list;
  4. struct task_struct* tsk;
  5. };

 

 
ss_add(msq, &s);//挂载到报文队列q_sender链,这样可以通过此链找到休眠正在等待发送的进程

  1. staticinlinevoid ss_add(struct msg_queue* msq,struct msg_sender* mss)
  2. {
  3. mss->tsk=current;//获取当前队列的进程运行的进程
  4. current->state=TASK_INTERRUPTIBLE;//设置为可中断睡眠状态
  5. list_add_tail(&mss->list,&msq->q_senders);//添加到队尾
  6. }

如果有相关进程正在读这个报文就不用放入队列了

  1. intinline pipelined_send(struct msg_queue* msq,struct msg_msg* msg)
  2. {
  3. struct list_head* tmp;
  4. tmp = msq->q_receivers.next;//聚集正在睡眠等待接收的读进程
  5. while(tmp !=&msq->q_receivers){//表示有
  6. struct msg_receiver* msr;
  7. msr = list_entry(tmp,struct msg_receiver,r_list);
  8. tmp = tmp->next;
  9. if(testmsg(msg,msr->r_msgtype,msr->r_mode)){//类型是否匹配
  10. list_del(&msr->r_list);
  11. if(msr->r_maxsize < msg->m_ts){//读的缓冲区是否够用
  12. msr->r_msg = ERR_PTR(-E2BIG);
  13. wake_up_process(msr->r_tsk);//不够用则将进程唤醒,让其出错返回
  14. }else{
  15. msr->r_msg = msg;//有的话,直接读取
  16. msq->q_lspid = msr->r_tsk->pid;
  17. msq->q_rtime = CURRENT_TIME;
  18. wake_up_process(msr->r_tsk);
  19. return1;
  20. }
  21. }
  22. }
  23. return0;
  24. }


 

 
消息队列接收msgrcv

  1. //标识号 用户空间缓冲区 大小
  2. asmlinkage long sys_msgrcv (int msqid,struct msgbuf *msgp,size_t msgsz,
  3. long msgtyp,int msgflg)//msgtyp消息类型,
  4. {
  5. struct msg_queue *msq;//队列头
  6. struct msg_receiver msr_d;//接收的需要睡眠的进程
  7. struct list_head* tmp;
  8. struct msg_msg* msg,*found_msg;
  9. int err;
  10. int mode;
  11. if(msqid <0||(long) msgsz <0)
  12. return-EINVAL;
  13. mode = convert_mode(&msgtyp,msgflg);
  14. msq = msg_lock(msqid);//根据报文队列标识号,找到具体队列
  15. if(msq==NULL)
  16. return-EINVAL;
  17. retry:
  18. err=-EACCES;
  19. if(ipcperms (&msq->q_perm, S_IRUGO))//检测是否具有权限
  20. goto out_unlock;
  21. tmp = msq->q_messages.next;
  22. found_msg=NULL;
  23. while(tmp !=&msq->q_messages){//遍历完
  24. msg = list_entry(tmp,struct msg_msg,m_list);//根据队列当前项找到其指针
  25. if(testmsg(msg,msgtyp,mode)){
  26. found_msg = msg;//查找到了消息
  27. if(mode == SEARCH_LESSEQUAL && msg->m_type !=1){
  28. found_msg=msg;
  29. msgtyp=msg->m_type-1;//将type减到比这个报文的类型值更小,看能否找到更小的
  30. }else{
  31. found_msg=msg;
  32. break;
  33. }
  34. }
  35. tmp = tmp->next;
  36. }
  37. if(found_msg){
  38. msg=found_msg;
  39. if((msgsz < msg->m_ts)&&!(msgflg & MSG_NOERROR)){//如果接收大小小于报文大小,出错
  40. err=-E2BIG;
  41. goto out_unlock;
  42. }
  43. list_del(&msg->m_list);//否则将该报文从队列脱链
  44. msq->q_qnum--;
  45. msq->q_rtime = CURRENT_TIME;
  46. msq->q_lrpid = current->pid;
  47. msq->q_cbytes -= msg->m_ts;
  48. atomic_sub(msg->m_ts,&msg_bytes);
  49. atomic_dec(&msg_hdrs);
  50. ss_wakeup(&msq->q_senders,0);//将发送的睡眠等待进程全部唤醒,因为拿出了一个报文
  51. msg_unlock(msqid);
  52. out_success:
  53. msgsz =(msgsz > msg->m_ts)? msg->m_ts : msgsz;
  54. if(put_user (msg->m_type,&msgp->mtype)||//实际接收的报文类型,通过put_user送回用户空间
  55. store_msg(msgp->mtext, msg, msgsz)){//将实际接收到的复制到用户空间
  56. msgsz =-EFAULT;
  57. }
  58. free_msg(msg);//释放内核缓存
  59. return msgsz;
  60. }else//报文队列还没有报文可供接收
  61. {
  62. struct msg_queue *t;
  63. /* no message waiting. Prepare for pipelined
  64. * receive.
  65. */
  66. if(msgflg & IPC_NOWAIT){//不等待直接返回错误
  67. err=-ENOMSG;
  68. goto out_unlock;
  69. }
  70. list_add_tail(&msr_d.r_list,&msq->q_receivers);//链入等待接收队列进程
  71. msr_d.r_tsk = current;//保存各种信息
  72. msr_d.r_msgtype = msgtyp;
  73. msr_d.r_mode = mode;
  74. if(msgflg & MSG_NOERROR)
  75. msr_d.r_maxsize = INT_MAX;
  76. else
  77. msr_d.r_maxsize = msgsz;
  78. msr_d.r_msg = ERR_PTR(-EAGAIN);
  79. current->state = TASK_INTERRUPTIBLE;
  80. msg_unlock(msqid);//解锁并调度
  81. //当前进程一旦睡下,以下需要等待进程通过pipelined_send()向其发送报文,并且选择这个进程作为接收进程才会被唤醒
  82. schedule();
  83. current->state = TASK_RUNNING;
  84. msg =(struct msg_msg*) msr_d.r_msg;
  85. if(!IS_ERR(msg))//表示已经成功接收
  86. goto out_success;
  87. //以下是因为缓冲区太小,唤醒了睡眠进程依旧无法接收,而是被信号唤醒的错误处理
  88. t = msg_lock(msqid);//对报文加锁,隐藏着等待,可能被其他进程抢先锁住该队列
  89. if(t==NULL)
  90. msqid=-1;
  91. msg =(struct msg_msg*)msr_d.r_msg;
  92. if(!IS_ERR(msg)){//在锁住队列之前,还有可能接收到其他进程pipelined_send发来的报文
  93. /* our message arived while we waited for
  94. * the spinlock. Process it.
  95. *///所以还需要检查下是否成功接收到报文
  96. if(msqid!=-1)
  97. msg_unlock(msqid);
  98. goto out_success;
  99. }
  100. err = PTR_ERR(msg);
  101. if(err ==-EAGAIN){//要将本进程的msg_receiver结构拖链,并且看是否有信号处理
  102. if(msqid==-1)
  103. BUG();
  104. list_del(&msr_d.r_list);
  105. if(signal_pending(current))//如果没有信号处理,则跳转到retry重新开始
  106. err=-EINTR;
  107. else
  108. goto retry;
  109. }
  110. }
  111. out_unlock:
  112. if(msqid!=-1)
  113. msg_unlock(msqid);
  114. return err;
  115. }

msgctl()报文机制的控制与设置

管道的缺点在于缺乏对管道的控制手段,也缺乏获取其状态的信息(例如,有多少个字节已经在管道中等待读取)的手段,msgctl就是为报文队列提供了这样的手段

  1. long sys_msgctl(int msqid,int cmd,struct msqid_ds*buf);

cmd为具体的操作码

  1. /*
  2. * Control commands used with semctl, msgctl and shmctl
  3. * see also specific commands in sem.h, msg.h and shm.h
  4. */
  5. #define IPC_RMID 0/* remove resource关闭报文队列 */
  6. #define IPC_SET 1/* set ipc_perm options 改变ipc设施的相关状态与属性*/
  7. #define IPC_STAT 2/* get ipc_perm options 获取状态*/
  8. #define IPC_INFO 3/* see ipcs 统计信息*/

以上命令码并不是专门为报文队列设置,也适用于sysv ipc的其他两种机制,对于具体的机制还可能补充其他专用命令.

msg专用命令

  1. /* ipcs ctl commands */
  2. #define MSG_STAT 11
  3. #define MSG_INFO 12

调用参数buf是一个msgid_ds结构指针,此结构适用于ipc_stat与ipc_set.

  1. /* Obsolete, used only for backwards compatibility and libc5 compiles */
  2. struct msqid_ds {
  3. struct ipc_perm msg_perm;
  4. struct msg *msg_first;/* first message on queue,unused */
  5. struct msg *msg_last;/* last message in queue,unused */
  6. __kernel_time_t msg_stime;/* last msgsnd time */
  7. __kernel_time_t msg_rtime;/* last msgrcv time */
  8. __kernel_time_t msg_ctime;/* last change time */
  9. unsignedlong msg_lcbytes;/* Reuse junk fields for 32 bit */
  10. unsignedlong msg_lqbytes;/* ditto */
  11. unsignedshort msg_cbytes;/* current number of bytes on queue */
  12. unsignedshort msg_qnum;/* number of messages in queue */
  13. unsignedshort msg_qbytes;/* max number of bytes on queue */
  14. __kernel_ipc_pid_t msg_lspid;/* pid of last msgsnd */
  15. __kernel_ipc_pid_t msg_lrpid;/* last receive pid */
  16. };

当命令为ipc_info时,指向一个msginfo结构

  1. /* buffer for msgctl calls IPC_INFO, MSG_INFO */
  2. struct msginfo {
  3. int msgpool;
  4. int msgmap;
  5. int msgmax;
  6. int msgmnb;
  7. int msgmni;
  8. int msgssz;
  9. int msgtql;
  10. unsignedshort msgseg;
  11. };

 

  1. asmlinkage long sys_msgctl (int msqid,int cmd,struct msqid_ds *buf)
  2. {
  3. int err, version;
  4. struct msg_queue *msq;
  5. struct msq_setbuf setbuf;
  6. struct kern_ipc_perm *ipcp;
  7. if(msqid <0|| cmd <0)
  8. return-EINVAL;
  9. version = ipc_parse_version(&cmd);//判断是64位版本还是32位版本
  10. switch(cmd){//根据不同类型选择不同操作
  11. case IPC_INFO://合在一起
  12. case MSG_INFO:
  13. {
  14. struct msginfo msginfo;
  15. int max_id;
  16. if(!buf)
  17. return-EFAULT;
  18. /* We must not return kernel stack data.
  19. * due to padding, it's not enough
  20. * to set all member fields.
  21. */
  22. memset(&msginfo,0,sizeof(msginfo));
  23. msginfo.msgmni = msg_ctlmni;
  24. msginfo.msgmax = msg_ctlmax;
  25. msginfo.msgmnb = msg_ctlmnb;
  26. msginfo.msgssz = MSGSSZ;
  27. msginfo.msgseg = MSGSEG;
  28. down(&msg_ids.sem);
  29. if(cmd == MSG_INFO){
  30. msginfo.msgpool = msg_ids.in_use;
  31. msginfo.msgmap = atomic_read(&msg_hdrs);
  32. msginfo.msgtql = atomic_read(&msg_bytes);
  33. }else{
  34. msginfo.msgmap = MSGMAP;
  35. msginfo.msgpool = MSGPOOL;
  36. msginfo.msgtql = MSGTQL;
  37. }
  38. max_id = msg_ids.max_id;
  39. up(&msg_ids.sem);
  40. if(copy_to_user (buf,&msginfo,sizeof(struct msginfo)))//从内核拷贝到用户空间
  41. return-EFAULT;
  42. return(max_id <0)?0: max_id;
  43. }
  44. case MSG_STAT://stat状态相关操作
  45. case IPC_STAT:
  46. {
  47. struct msqid64_ds tbuf;
  48. int success_return;
  49. if(!buf)
  50. return-EFAULT;
  51. if(cmd == MSG_STAT && msqid > msg_ids.size)
  52. return-EINVAL;
  53. memset(&tbuf,0,sizeof(tbuf));
  54. msq = msg_lock(msqid);
  55. if(msq == NULL)
  56. return-EINVAL;
  57. if(cmd == MSG_STAT){
  58. success_return = msg_buildid(msqid, msq->q_perm.seq);
  59. }else{
  60. err =-EIDRM;
  61. if(msg_checkid(msq,msqid))//id检查
  62. goto out_unlock;
  63. success_return =0;
  64. }
  65. err =-EACCES;
  66. if(ipcperms (&msq->q_perm, S_IRUGO))//权限判断
  67. goto out_unlock;
  68. kernel_to_ipc64_perm(&msq->q_perm,&tbuf.msg_perm);
  69. tbuf.msg_stime = msq->q_stime;
  70. tbuf.msg_rtime = msq->q_rtime;
  71. tbuf.msg_ctime = msq->q_ctime;
  72. tbuf.msg_cbytes = msq->q_cbytes;
  73. tbuf.msg_qnum = msq->q_qnum;
  74. tbuf.msg_qbytes = msq->q_qbytes;
  75. tbuf.msg_lspid = msq->q_lspid;
  76. tbuf.msg_lrpid = msq->q_lrpid;
  77. msg_unlock(msqid);
  78. if(copy_msqid_to_user(buf,&tbuf, version))//从内核拷贝到用户
  79. return-EFAULT;
  80. return success_return;
  81. }
  82. case IPC_SET://set命令操作
  83. if(!buf)
  84. return-EFAULT;
  85. if(copy_msqid_from_user (&setbuf, buf, version))//从用户拷贝到内核
  86. return-EFAULT;
  87. break;
  88. case IPC_RMID:
  89. break;
  90. default:
  91. return-EINVAL;
  92. }
  93. down(&msg_ids.sem);
  94. msq = msg_lock(msqid);
  95. err=-EINVAL;
  96. if(msq == NULL)
  97. goto out_up;
  98. err =-EIDRM;
  99. if(msg_checkid(msq,msqid))
  100. goto out_unlock_up;
  101. ipcp =&msq->q_perm;
  102. err =-EPERM;
  103. if(current->euid != ipcp->cuid &&
  104. current->euid != ipcp->uid &&!capable(CAP_SYS_ADMIN))//是否有权限操作
  105. /* We _could_ check for CAP_CHOWN above, but we don't */
  106. goto out_unlock_up;
  107. switch(cmd){
  108. case IPC_SET:
  109. {
  110. if(setbuf.qbytes > msg_ctlmnb &&!capable(CAP_SYS_RESOURCE))
  111. goto out_unlock_up;
  112. msq->q_qbytes = setbuf.qbytes;
  113. ipcp->uid = setbuf.uid;
  114. ipcp->gid = setbuf.gid;
  115. ipcp->mode =(ipcp->mode &~S_IRWXUGO)|
  116. (S_IRWXUGO & setbuf.mode);
  117. msq->q_ctime = CURRENT_TIME;
  118. /* sleeping receivers might be excluded by
  119. * stricter permissions.
  120. */
  121. expunge_all(msq,-EAGAIN);//使所有正在等待此队列接收报文的进程都出错返回
  122. /* sleeping senders might be able to send
  123. * due to a larger queue size.
  124. */
  125. ss_wakeup(&msq->q_senders,0);//将所有正在等待此队列发送报文的进程都唤醒,进行新一轮尝试
  126. msg_unlock(msqid);
  127. break;
  128. }
  129. case IPC_RMID:
  130. freeque (msqid);//将所有正在等待的发送还是接收的进程全部唤醒,让其出错返回
  131. break;
  132. }
  133. err =0;
  134. out_up:
  135. up(&msg_ids.sem);
  136. return err;
  137. out_unlock_up:
  138. msg_unlock(msqid);
  139. goto out_up;
  140. out_unlock:
  141. msg_unlock(msqid);
  142. return err;
  143. }

ipc_rmid对应操作:freeque (msqid); //将所有正在等待的发送还是接收的进程全部唤醒,让其出错返回

  1. staticvoid freeque (int id)
  2. {
  3. struct msg_queue *msq;
  4. struct list_head *tmp;
  5. msq = msg_rmid(id);
  6. expunge_all(msq,-EIDRM);//将所有读写的从链表拖链,让其出错返回
  7. ss_wakeup(&msq->q_senders,1);//唤醒
  8. msg_unlock(id);
  9. tmp = msq->q_messages.next;
  10. while(tmp !=&msq->q_messages){//将所有报文释放
  11. struct msg_msg* msg = list_entry(tmp,struct msg_msg,m_list);
  12. tmp = tmp->next;
  13. atomic_dec(&msg_hdrs);
  14. free_msg(msg);
  15. }
  16. atomic_sub(msq->q_cbytes,&msg_bytes);
  17. kfree(msq);//是否队列头
  18. }

 

赞(0) 打赏
转载请注明出处:服务器评测 » Linux内核情景分析之消息队列
分享到: 更多 (0)

听说打赏我的人,都进福布斯排行榜啦!

支付宝扫一扫打赏

微信扫一扫打赏