暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一起撸linux内核24-内核IPC机制(02)

囧囧妹 2022-03-24
208

点击上方蓝字【囧囧妹】一起学习,一起成长!

一、开篇

接上一节一起撸linux内核23-内核IPC机制(01)
继续消息队列的msgget和msgsnd分析
代码放到https://gitee.com/sunnyshare/linux-examplecode.git

二、msgget内核实现

msgget函数是获取一个消息队列标识。

msgflg含义:

      • IPC_CREAT – 如果对于该key标识的消息队列不存在则创建

      • IPC_EXCL – 一般与IPC_CREAT一起使用,表示如果消息队列已存在则创建失败

msgget内核实现源码

    SYSCALL_DEFINE2(msgget, key_t, key, int, msgflg)
    {
    struct ipc_namespace *ns;
    static const struct ipc_ops msg_ops = {
    .getnew = newque,
    .associate = msg_security,
    };
    struct ipc_params msg_params;


    获取当前进程namespace中的ipc命令空间
    // struct nsproxy {
    // atomic_t count;
    // struct uts_namespace *uts_ns;//unix timesharing system包含内存、版本、底层体系架构
    // struct ipc_namespace *ipc_ns;//ipc空间
    // struct mnt_namespace *mnt_ns;//挂载的文件系统
    // struct pid_namespace *pid_ns_for_children;//进程pid相关
    // struct net *net_ns;//网络命名空间
    // };
    ns = current->nsproxy->ipc_ns;


    msg_params.key = key;
    msg_params.flg = msgflg;
    获取一个ipc对象
    return ipcget(ns, &msg_ids(ns), &msg_ops, &msg_params);
    }

    该函数就是从当前进程中获取到ipc命名空间,通过id找到对应的ipc对象。里面有个核心函数ipcget,该函数会涉及两个key,IPC_PRIVATE私有key和公有key。

      • 如果key是私有的,#define IPC_PRIVATE ((__kernel_key_t) 0)  则会创建一个新的私有对象

      • 如果key是公有的,则在ipc_ids中查找该key,如果找到了则检测其安全和权限并返回,如果未找到同时标志位设置了IPC_CREAT则创建一个新的ipc对象并返回。


    三、msgsnd内核实现

    msgsnd函数发送消息。

      SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz,
      int, msgflg)
      {
      long mtype;
      检测消息type并拷贝给mtype
      if (get_user(mtype, &msgp->mtype))
      return -EFAULT;
      将用户态的消息内容拷贝到内核空间
      return do_msgsnd(msqid, mtype, msgp->mtext, msgsz, msgflg);
      }

      该函数主要是检测type并将其拷贝给内核空间,将用户态消息内容拷贝给内核空间。

      get_user和put_user?

      这两个函数对于复合类型比如数据结构或者数组是无法完成拷贝的,只能完成一些简单类型的拷贝比如char,long,int,同时这两个函数会对指针指向的对象做长度检测,这个已经在之前字符设备中说过该检测和拷贝感兴趣的可以翻回去再看看(一起撸linux内核06-字符设备04)。

      如果该消息没有进程接收怎么办呢?此时会将该消息挂起到链表尾部等待接收,该部分可参考我们之前写的通过字符设备完成进程间通信(一起撸linux内核20-字符设备构建消息队列(01))。

      我们再来分析do_msgsnd

        long do_msgsnd(int msqid, long mtype, void __user *mtext,
        size_t msgsz, int msgflg)
        {
        struct msg_queue *msq;
        struct msg_msg *msg;
        int err;
        struct ipc_namespace *ns;
        获取当前进程ipc的命名空间
        ns = current->nsproxy->ipc_ns;


        //该消息长度超过一条消息总长8192字节则返回错误
        //消息长度小于0或者msqid小于0均返回错误
        if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0)
        return -EINVAL;
        //消息类型不能小于1
        if (mtype < 1)
        return -EINVAL;
        //申请一个消息空间并将消息写入该内存
        msg = load_msg(mtext, msgsz);
        if (IS_ERR(msg))
        return PTR_ERR(msg);
        //设置消息类型
        msg->m_type = mtype;
        msg->m_ts = msgsz;


        rcu_read_lock();
        //做一些安全检测
        … …
        for (;;) {
        struct msg_sender s;
        … …
        //消息队列满,发送参数有IPC_NOWAIT,则返回错误
        /* queue full, wait: */
        if (msgflg & IPC_NOWAIT) {
        err = -EAGAIN;
        goto out_unlock0;
        }
        … …
        }
        msq->q_lspid = task_tgid_vnr(current);
        msq->q_stime = get_seconds();


        //查看是否有接收进程接收该消息,如果有则直接发送
        if (!pipelined_send(msq, msg)) {
        /* no one is waiting for this message, enqueue it */
        //如果没有进程接收消息,则将消息挂到队列等待进程接收
        list_add_tail(&msg->m_list, &msq->q_messages);
        msq->q_cbytes += msgsz;
        msq->q_qnum++;
        //计算消息队列的长度部分
        atomic_add(msgsz, &ns->msg_bytes);
        atomic_inc(&ns->msg_hdrs);
        }


        err = 0;
        msg = NULL;


        out_unlock0:
        ipc_unlock_object(&msq->q_perm);
        out_unlock1:
        rcu_read_unlock();
        if (msg != NULL)
        free_msg(msg);
        return err;
        }

        这里需要来说load_msg函数,该函数就是申请消息空间并将用户态消息内容拷贝到内核的消息空间上。里面涉及struct msg_msg *msg;  struct msg_msgseg *seg;两个结构,msg_msg结构占用一个page,包含了消息的type、消息长度、剩余空间存储消息体。如果剩余空间不够则开始申请msg_msgseg结构,该结构也占用一个page,只包含消息体内容,通过链表的方式来串联这些msg_msgseg结构

        然后我们再看pipelined_send

          static inline int pipelined_send(struct msg_queue *msq, struct msg_msg *msg)
          {
          struct msg_receiver *msr, *t;


          list_for_each_entry_safe(msr, t, &msq->q_receivers, r_list) {
          if (testmsg(msg, msr->r_msgtype, msr->r_mode) &&
          !security_msg_queue_msgrcv(msq, msg, msr->r_tsk,
          msr->r_msgtype, msr->r_mode)) {
          //轮询消息队列中的每个q_receivers,如果type一致则尝试发送
          //上述security_msg_queue_msgrcv发送成功则进行后续处理
          list_del(&msr->r_list);
          //消息太大,唤醒对应的接收进程并返回接收失败
          if (msr->r_maxsize < msg->m_ts) {
          /* initialize pipelined send ordering */
          msr->r_msg = NULL;
          wake_up_process(msr->r_tsk);
          /* barrier (B) see barrier comment below */
          //内存屏障-多处理器相关,避免编译器优化代码
          smp_wmb();
          msr->r_msg = ERR_PTR(-E2BIG);
          } else {
          msr->r_msg = NULL;
          msq->q_lrpid = task_pid_vnr(msr->r_tsk);
          msq->q_rtime = get_seconds();
          //发送成功唤醒接收进程
          wake_up_process(msr->r_tsk);
          /*
          * Ensure that the wakeup is visible before
          * setting r_msg, as the receiving can otherwise
          * exit - once r_msg is set, the receiver can
          * continue. See lockless receive part 1 and 2
          * in do_msgrcv(). Barrier (B).
          */
          smp_wmb();
          msr->r_msg = msg;


          return 1;
          }
          }
          }
          return 0;
          }


          总结,这里简单聊了下msgget和msgsnd两个函数的内核实现,msgget主要涉及了公有key和私有key概念,msgsnd我们聊了下消息的组成由msg_msg和msg_msgseg两个结构体组成,每个结构申请都是以一个page大小来申请的,同时回忆了一下put_user的实现,当消息在内核态组装好后会轮询消息队列上的receiver,通过消息type查看是否有该消息的接收者,如果存在则唤醒接收进程进行接收,如果不存在则将该消息挂载到等待队列等待接收。下一节继续msgrcv同时我会把消息队列的结构通过一个图来展示一下。


          觉得不错,点击“分享”,“赞”,“在看”传播给更多热爱嵌入式的小伙伴吧!

          文章转载自囧囧妹,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论