现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

linux的netlink接口详解(下)

2019-05-04 06:49 工业·编程 ⁄ 共 16360字 ⁄ 字号 暂无评论

——linux版本: 3.14.38

netlink支持用户进程和内核相互交互(两边都可以主动发起),同时还支持用户进程之间相互交互(虽然这种应用通常都采用unix-sock)

但是有一点需要注意,内核不支持接收netlink组播消息

本文将从用户进程发送一个netlink消息开始,对整个netlink消息通信原理进行展开分析

用户进程一般都通过调用sendmsg来向内核或其他进程发送netlink消息(有关sendmsg系统调用的公用部分代码解析将在另一片文章中展开)

    /* 用户进程对netlink套接字调用sendmsg()系统调用后,内核执行netlink操作的总入口函数

     * @sock    - 指向用户进程的netlink套接字,也就是发送方的

     * @msg     - 承载了发送方传递的netlink消息

     * @len     - netlink消息长度

     * @kiocb   - 这个参数在最新内核中已经去掉,这里索性不再进行分析(其实是因为还没开撸这块代码~)

     *

     * 备注: netlink套接字在创建的过程中(具体是在__netlink_create函数开头),已经和netlink_ops(socket层netlink协议族的通用操作集合)关联,

     *        其中注册的sendmsg回调就是指向本函数

     */

    static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock,struct msghdr *msg, size_t len)

    {

        // 显然,这里定义的addr指向netlink消息的目的地

        DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name);   

        // netlink消息不支持传输带外数据

        if (msg->msg_flags&MSG_OOB)

            return -EOPNOTSUPP;

        // 辅助消息处理

        if (NULL == siocb->scm)

            siocb->scm = &scm;

        err = scm_send(sock, msg, siocb->scm, true);

        if (msg->msg_namelen) {

            // 如果用户进程指定了目的地址,则对其进行合法性检测,然后从中获取单播地址和组播地址

            err = -EINVAL;

            if (addr->nl_family != AF_NETLINK)

                goto out;

            dst_portid = addr->nl_pid;

            dst_group = ffs(addr->nl_groups);

            err =  -EPERM;

            // 如果有设置目的组播地址,或者目的单播地址不是发往kernel,就需要检查具体的netlink协议是否支持

            if ((dst_group || dst_portid) && !netlink_allowed(sock, NL_CFG_F_NONROOT_SEND))

                goto out;

            netlink_skb_flags |= NETLINK_SKB_DST;

       

        }   

        else{

            // 如果用户进程没有指定目的地址,则使用该netlink套接字默认的(缺省都是0,可以通过connect系统调用指定)

            dst_portid = nlk->dst_portid;

            dst_group = nlk->dst_group;

        }

        // 如果该netlink套接字还没有被绑定过,首先执行动态绑定

        if (!nlk->portid){

            err = netlink_autobind(sock);

        }

        // 以下这部分只有当内核配置了CONFIG_NETLINK_MMAP选项才生效(暂不分析)

        if (netlink_tx_is_mmaped(sk) && msg->msg_iov->iov_base == NULL){

            err = netlink_mmap_sendmsg(sk, msg, dst_portid, dst_group,siocb);

        }

        // 检查需要发送的数据长度是否合法

        err = -EMSGSIZE;

        if (len > sk->sk_sndbuf - 32)

            goto out;

        // 分配skb结构空间

        err = -ENOBUFS;

        skb = netlink_alloc_large_skb(len, dst_group);

        // 初始化这个skb中的cb字段

        NETLINK_CB(skb).portid  = nlk->portid;

        NETLINK_CB(skb).dst_group = dst_group;

        NETLINK_CB(skb).creds   = siocb->scm->creds;

        NETLINK_CB(skb).flags   = netlink_skb_flags;

        // 将数据从msg_iov拷贝到skb中

        err = -EFAULT;

        memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)

        // 执行LSM模块,略过

        err = security_netlink_send(sk, skb);

        // 至此,netlink消息已经被放入新创建的sbk中,接下来,内核根据单播还是组播消息,执行了不同的发送流程

        // 发送netlink组播消息

        if (dst_group){

            atomic_inc(&skb->users);

            netlink_broadcast(sk, skb, dst_portid, dst_group, GFP_KERNEL);

        }

        // 发送netlink单播消息

        err = netlink_unicast(sk, skb, dst_portid, msg->msg_flags&MSG_DONTWAIT);

    }

    小结:从netlink_sendmsg函数的末尾可以看出,来自用户进程的netlink消息会以netlink_unicast单播方式或netlink_broadcast组播方式进行传递。

          所以接下来进一步对这两种传播方式进行展开分析。

    /* 以下是内核执行netlink单播消息的发送流程

     * @ssk         - 源sock结构

     * @skb         - 属于发送方的承载了netlink消息的skb

     * @portid      - 目的单播地址

     * @nonblock    - 1:非阻塞调用,2:阻塞调用

     *

     * 备注: 以下3种情况都会调用到本函数:

     *          [1]. kernel     --单播--> 用户进程

     *          [2]. 用户进程   --单播--> kernel

     *          [3]. 用户进程a  --单播--> 用户进程b

     */

    int netlink_unicast(struct sock *ssk, struct sk_buff *skb,u32 portid, int nonblock)

    {

        // 重新调整skb数据区大小

        skb = netlink_trim(skb, gfp_any());

       

        // 计算发送超时时间(如果是非阻塞调用,则返回0)

        timeo = sock_sndtimeo(ssk, nonblock);

    retry:

        // 根据源sock结构和目的单播地址,得到目的sock结构

        sk = netlink_getsockbyportid(ssk, portid);

        // 如果该目的netlink套接字属于内核,则进入第 [2] 种情况下的分支

        if (netlink_is_kernel(sk))

            return netlink_unicast_kernel(sk, skb, ssk);

        // 程序运行到这里,意味着以下属于第 [1]/[3] 种情况下的分支

        // 对于发往用户进程的单播消息都要调用BPF过滤

        if (sk_filter(sk, skb)){

            err = skb->len;

            kfree_skb(skb);

            sock_put(sk);

            return err;

        }

        // 将该skb绑定到目的用户进程netlink套接字上,如果成功,skb的所有者也从这里开始变更为目的用户进程netlink套接字

        err = netlink_attachskb(sk, skb, &timeo, ssk);

        // 如果返回值是1,意味着要重新尝试绑定操作

        if (err == 1)

            goto retry;

        if (err)

            return err;

        // 将该skb发送到目的用户进程netlink套接字

        return netlink_sendskb(sk, skb);

    }

    /* 来自用户进程的netlink消息单播发往内核netlink套接字

     * @sk  - 目的sock结构

     * @skb - 属于发送方的承载了netlink消息的skb

     * @ssk - 源sock结构

     *

     * 备注:skb的所有者在本函数中发生了变化

     */

    static int netlink_unicast_kernel(struct sock *sk, struct sk_buff *skb,struct sock *ssk)

    {

        // 获取目的netlink套接字,也就是内核netlink套接字

        struct netlink_sock *nlk = nlk_sk(sk);

        // 检查内核netlink套接字是否注册了netlink_rcv回调(就是各个协议在创建内核netlink套接字时通常会传入的input函数)

        if (nlk->netlink_rcv != NULL) {

            ret = skb->len;

            // 设置该skb的所有者是内核的netlink套接字

            netlink_skb_set_owner_r(skb, sk);

            // 保存该netlink消息的源sock结构

            NETLINK_CB(skb).sk = ssk;

            // netlink tap机制暂略

            netlink_deliver_tap_kernel(sk, ssk, skb);

            // 调用内核netlink套接字的协议类型相关的netlink_rcv钩子来处理收到的消息

            // 到这里,意味着发往内核的netlink消息已经被成功接收

            nlk->netlink_rcv(skb);

        } else {

            // 如果指定的内核netlink套接字没有注册netlink_rcv回调,就直接丢弃所有收到的netlink消息

            kfree_skb(skb);

        }

        sock_put(sk);

    }

   

    /* 将一个指定skb绑定到一个指定的属于用户进程的netlink套接字上

     * @sk      - 指向目的sock结构

     * @skb     - 属于发送方的承载了netlink消息的skb

     * @timeo   - 指向一个超时时间

     * @ssk     - 指向源sock结构

     */

    int netlink_attachskb(struct sock *sk, struct sk_buff *skb,long *timeo, struct sock *ssk)

    {

        // 获取目的netlink套接字,也就是目的用户进程netlink套接字

        nlk = nlk_sk(sk);

        /* 在没有内核使能CONFIG_NETLINK_MMAP配置选项时,

         * 如果目的netlink套接字上已经接收尚未处理的数据大小超过了接收缓冲区大小,或者目的netlink套接字被设置了拥挤标志,

         * 意味着该sbk不能立即被目的netlink套接字接收,需要加入等待队列

         */

        if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(NETLINK_CONGESTED, &nlk->state)) && !netlink_skb_is_mmaped(skb)){

            // 申请一个等待队列

            DECLARE_WAITQUEUE(wait, current);

            // 如果传入的超时时间为0,意味着非阻塞调用,则丢弃这条netlink消息,并返回EAGAIN

            if (!*timeo) {

                /* 如果该netlink消息对应的源sock结构不存在,或者该netlink消息来自kernel

                 * 则对目的netlink套接字设置缓冲区溢出状态

                 */

                if (!ssk || netlink_is_kernel(ssk))

                    netlink_overrun(sk);

                sock_put(sk);

                kfree_skb(skb);

                return -EAGAIN;

            }

            // 程序运行到这里意味着是阻塞调用

            // 改变当前进程状态为可中断

            __set_current_state(TASK_INTERRUPTIBLE);

            // 将目的netlink套接字加入等待队列(同时意味着进入睡眠,猜测)

            add_wait_queue(&nlk->wait, &wait);

            // 程序到这里意味着被唤醒了(猜测)

            // 如果接收条件还是不满足,则要计算剩余的超时时间

            if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(NETLINK_CONGESTED, &nlk->state)) && !sock_flag(sk, SOCK_DEAD))

                *timeo = schedule_timeout(*timeo);

            // 改变当前进程状态为运行

            __set_current_state(TASK_RUNNING);

            // 将目的netlink套接字从等待队列中删除

            remove_wait_queue(&nlk->wait, &wait);

            sock_put(sk);

            // 目测是信号相关处理,略

            if (signal_pending(current)) {

                kfree_skb(skb);

                return sock_intr_errno(*timeo);

            }

            // 返回1,意味着还将要再次调用本函数

            return 1;

        }

        // 设置该skb的所有者是目的用户进程netlink套接字,这里才是真正的绑定操作,上面都是前奏

        netlink_skb_set_owner_r(skb, sk);

    }

    /* 将指定skb发送到目的用户进程netlink套接字(显然,本函数只是个封装)

     * @sk  - 目的用户进程netlink套接字

     * @skb - 指向一个承载了netlink消息的skb

     * @返回值  - 成功返回实际发送的netlink消息长度

     *

     * 备注:该skb调用本函数前已经绑定到该用户进程netlink套接字

     */

    int netlink_sendskb(struct sock *sk, struct sk_buff *skb)

    {

        int len = __netlink_sendskb(sk, skb);

        sock_put(sk);

        return len

    }

    /* 将指定skb发送到指定用户进程netlink套接字,换句话说,实际也就是将该skb放入了该套接字的接收队列中

     * @sk  - 指向一个用户进程netlink套接字

     * @skb - 指向一个承载了netlink消息的skb

     *

     * 备注:该skb调用本函数前已经绑定到该用户进程netlink套接字

     *       之所以说组播消息不支持发往内核的根本原因就在本函数中:

     *              本函数最后通过调用sk_data_ready钩子函数来通知所在套接字接收组播消息,

     *              而内核netlink套接字实际屏蔽了这个钩子函数,也就意味着永远无法接收到组播消息

     */

    static int __netlink_sendskb(struct sock *sk, struct sk_buff *skb)

    {

        // 这里可以知道,成功时的返回值就是skb中承载的netlink消息长度

        int len = skb->len;

        // 目前不知道tap系列函数的用处

        netlink_deliver_tap(skb);

        // 将skb放入该netlink套接字接收队列末尾

        skb_queue_tail(&sk->sk_receive_queue, skb);

        // 执行sk_data_ready回调通知该套接字有数据可读

        sk->sk_data_ready(sk, len);

        return len;

    }

    小结:至此,一个来自用户进程的netlink单播消息的传递流程基本分析完毕

          可以看出,流程的终点,一个是内核netlink套接字具体协议类型相关的netlink_rcv回调,另一个是目的用户进程netlink套接字的接收队列

          还可以看出,跟内核发往用户进程的netlink单播消息流程存在部分重合

    /* 发送netlink组播消息(显然这只是个封装)

     * @ssk         - 源sock结构

     * @skb         - 属于发送方的承载了netlink消息的skb

     * @portid      - 目的单播地址

     * @group       - 目的组播地址

     *

     * 备注: 以下2种情况都会调用到本函数:

     *          [1]. 用户进程   --组播--> 用户进程

     *          [2]. kernel     --组播--> 用户进程

     */

    int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,u32 group, gfp_t allocation)

    {

        return netlink_broadcast_filtered(ssk, skb, portid, group, allocation,NULL, NULL);

    }

    /* 发送netlink组播消息

     * @ssk         - 源sock结构

     * @skb         - 属于发送方的承载了netlink消息的skb

     * @portid      - 目的单播地址

     * @group       - 目的组播地址

     * @filter      - 指向一个过滤器

     * @filter_data - 指向一个传递给过滤器的参数

     */

    int netlink_broadcast_filtered(struct sock *ssk, struct sk_buff *skb, u32 portid,u32 group, gfp_t allocation,int (*filter)(struct sock *dsk, struct sk_buff *skb, void *data),void *filter_data)

    {

        // 获取源sock所在net命名空间

        struct net *net = sock_net(ssk);

        // 重新调整skb数据区大小

        skb = netlink_trim(skb, allocation);

        info.exclude_sk = ssk;

        info.net = net;

        info.portid = portid;

        info.group = group;

        info.failure = 0;

        info.delivery_failure = 0;

        info.congested = 0;

        info.delivered = 0;

        info.allocation = allocation;

        info.skb = skb;

        info.skb2 = NULL;

        info.tx_filter = filter;

        info.tx_data = filter_data;

        // 遍历该netlink套接字所在协议类型中所有阅订了组播功能的套接字,然后尝试向其发送该组播消息

        sk_for_each_bound(sk, &nl_table[ssk->sk_protocol].mc_list)

            do_one_broadcast(sk, &info);

        // 至此,netlink组播消息已经发送完毕

        // 释放skb结构

        consume_skb(skb);

        // 发送失败处理

        if (info.delivery_failure){

            kfree_skb(info.skb2);

            return -ENOBUFS;

        }

        // 释放skb2结构

        consume_skb(info.skb2);

        // 发送成功处理

        if (info.delivered){

            if (info.congested && (allocation & __GFP_WAIT))

                yield();

            return 0;

        }

    }

    /* 尝试向指定用户进程netlink套接字发送组播消息

     * @sk  - 指向一个sock结构,对应一个用户进程netlink套接字

     * @p   - 指向一个netlink组播消息的管理块

     *

     * 备注:传入的netlink套接字跟组播消息属于同一种netlink协议类型,并且这个套接字开启了组播阅订,除了这些,其他信息(比如阅订了具体哪些组播)都是不确定的

     */

    static int do_one_broadcast(struct sock *sk,struct netlink_broadcast_data *p)

    {

        // 如果源sock和目的sock是同一个则直接返回

        if (p->exclude_sk == sk)

            goto out;

        // 如果目的单播地址就是该netlink套接字,或者目的组播地址超出了该netlink套接字的上限,或者该netlink套接字没有阅订这条组播消息,都直接返回

        if (nlk->portid == p->portid || p->group - 1 >= nlk->ngroups || !test_bit(p->group - 1, nlk->groups))

            goto out;

        // 如果两者不属于同一个net命名空间,则直接返回

        if (!net_eq(sock_net(sk), p->net))

            goto out;

        // 如果netlink组播消息的管理块携带了failure标志, 则对该netlink套接字设置缓冲区溢出状态

        if (p->failure){

            netlink_overrun(sk);

            goto out;

        }

        // 设置skb2,其内容来自skb

        if (p->skb2 == NULL){

            if (skb_shared(p->skb))

                p->skb2 = skb_clone(p->skb, p->allocation);

            else{

                p->skb2 = skb_get(p->skb);

                skb_orphan(p->skb2);

            }

        }

        if (p->skb2 == NULL){

            // 到这里如果skb2还是NULL,意味着上一步中clone失败

            netlink_overrun(sk);

            p->failure = 1;

            if (nlk->flags & NETLINK_BROADCAST_SEND_ERROR)

                p->delivery_failure = 1;

        }

        else if (p->tx_filter && p->tx_filter(sk, p->skb2, p->tx_data)){

            // 如果传入了发送过滤器,但是过滤不通过,释放skb2

            kfree_skb(p->skb2);

            p->skb2 = NULL;

        }

        else if (sk_filter(sk, p->skb2)){

            // 如果BPF过滤不通过,释放skb2

            kfree_skb(p->skb2);

            p->skb2 = NULL;

        }

        else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0){

            // 如果将承载了组播消息的skb发送到该用户进程netlink套接字失败

            netlink_overrun(sk);

            if (nlk->flags & NETLINK_BROADCAST_SEND_ERROR)

                p->delivery_failure = 1;

        }

        else{

            // 发送成功最终会进入这里

            p->congested |= val;

            p->delivered = 1;

            p->skb2 = NULL;     // 这里没有释放skb2,而是在上层函数进行释放,原因是因为上层遍历时可能要反复进入本函数,所以skb2要被反复用到

        }

    }

    /* 将携带了netlink组播消息的skb发送到指定目的用户进程netlink套接字

     * @返回值      -1  :套接字接收条件不满足

     *              0   :netlink组播消息发送成功,套接字已经接收但尚未处理数据长度小于等于其接收缓冲的1/2

     *              1   :netlink组播消息发送成功,套接字已经接收但尚未处理数据长度大于其接收缓冲的1/2(这种情况似乎意味着套接字处于拥挤状态)

     *

     * 备注:到本函数这里,已经确定了传入的netlink套接字跟组播消息匹配正确;

     *       netlink组播消息不支持阻塞

     */

    static int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)

    {

        // 判断该netlink套接字是否满足接收条件

        if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf && !test_bit(NETLINK_CONGESTED, &nlk->state)){

            // 如果满足接收条件,则设置skb的所有者是该netlink套接字

            netlink_skb_set_owner_r(skb, sk);

            // 将skb发送到该netlink套接字,实际也就是将该skb放入了该套接字的接收队列中

            __netlink_sendskb(sk, skb);

            // 这里最后判断该netlink套接字的已经接收尚未处理数据长度是否大于其接收缓冲的1/2

            return atomic_read(&sk->sk_rmem_alloc) > (sk->sk_rcvbuf >> 1);

        }

        // 程序运行到这里意味着接收条件不满足

        return -1;

    }

    小结:至此,一个来自用户进程的netlink组播消息的传递流程基本分析完毕

          可以看出,流程的终点只有一个,就是目的用户进程netlink套接字的接收队列

          还可以看出,跟内核发往用户进程的netlink组播消息流程存在重合

以上就是用户进程向内核或其他进程发送netlink消息的完整流程,显然,其中重合了大量由内核发送netlink消息到用户进程的流程。

所以接下来内核发送netlink消息到用户进程的分析中,重合部分的代码将不再展开。

    /* 内核提供了API函数nlmsg_unicast供各个netlink协议调用,来向用户进程发送netlink单播消息(显然,这只是个封装)

     * @sk  - 指向源sock结构,也就是内核netlink套接字

     * @skb - 指向一个承载了单播netlink消息的skb

     * @portid  - 目的单播地址

     *

     * 备注:可以看出,内核只会以非阻塞的形式往用户进程发送netlink单播消息

     */

    static inline int nlmsg_unicast(struct sock *sk, struct sk_buff *skb, u32 portid)

    {

        netlink_unicast(sk, skb, portid, MSG_DONTWAIT);

    }

    /* 内核提供了API函数nlmsg_multicast供各个netlink协议调用,来向用户进程发送netlink组播消息(显然,这只是个封装)

     * 指向源sock结构

     * 指向一个承载了组播netlink消息的skb

     * 目的单播地址

     * 目的组播地址

     */

    static inline int nlmsg_multicast(struct sock *sk, struct sk_buff *skb,u32 portid, unsigned int group, gfp_t flags)

    {

        NETLINK_CB(skb).dst_group = group;

        netlink_broadcast(sk, skb, portid, group, flags);

    }

    小结:至此,内核发送netlink消息到用户进程的接口分析完毕

          内核不管是发送单播还是组播消息,流程的终点都只有一个,就是目的用户进程netlink套接字的接收队列

本文最后要分析的就是netlink消息接收流程了,由于发往内核的netlink消息在调用协议类型相关的netlink_rcv钩子时就已经意味着接收完成了,也就没有展开分析的必要。

所以接下来实际上就是对用户进程调用recvmsg接收来自内核或者其他进程的netlink消息流程展开分析(有关recvmsg系统调用的公用部分代码解析将在另一片文章中展开)

    /* 用户进程对netlink套接字调用recvmsg()系统调用后,内核执行netlink操作的总入口函数

     *  @sock    - 指向用户进程的netlink套接字,也就是接收方的

     *  @msg     - 用于存放接收到的netlink消息

     *  @len     - 用户空间支持的netlink消息接收长度上限

     *  @flags   - 跟本次接收操作有关的标志位集合(主要来源于用户空间)

     */

    static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,struct msghdr *msg, size_t len,int flags)

    {

        int noblock = flags&MSG_DONTWAIT;

        // netlink消息不支持接收带外数据

        if (flags&MSG_OOB)

            return -EOPNOTSUPP;

        // 从套接字的接收队列中接收数据

        skb = skb_recv_datagram(sk, flags, noblock, &err);

        if (skb == NULL)

            goto out;

       

        // 程序运行到这里意味着已经获取到一个skb

        data_skb = skb;

        // 如果收到的skb中承载的netlink消息长度大于用户空间接收缓存的最大长度,则设置MSG_TRUNC标志,并将实际接收长度改为接收缓存的长度

        copied = data_skb->len;

        if (len < copied){

            msg->msg_flags |= MSG_TRUNC;

            copied = len;

        }

        // 计算transport layer相对缓冲区头部的偏移量(目前不知道干嘛用)

        skb_reset_transport_header(data_skb);

        // 将skb中的数据拷贝到iovec结构的数据缓冲区

        err = skb_copy_datagram_iovec(data_skb, 0, msg->msg_iov, copied);

   

        // 填充返回给用户进程的地址信息

        if (msg->msg_name){

            DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name);

            addr->nl_family = AF_NETLINK;

            addr->nl_pad    = 0;

            addr->nl_pid    = NETLINK_CB(skb).portid;

            addr->nl_groups = netlink_group_mask(NETLINK_CB(skb).dst_group);

            msg->msg_namelen = sizeof(*addr);

        }

        // 处理netlink辅助消息

        if (nlk->flags & NETLINK_RECV_PKTINFO)

            netlink_cmsg_recv_pktinfo(msg, skb);

        if (NULL == siocb->scm){

            memset(&scm, 0, sizeof(scm));

            siocb->scm = &scm;

        }

        siocb->scm->creds = *NETLINK_CREDS(skb);

        // 如果用户进程recvmsg传入了MSG_TRUNC标志,这里重新将返回的长度值改为skb实际收到的数据长度

        if (flags & MSG_TRUNC)

            copied = data_skb->len;

        // 释放承载了该netlink消息的skb

        skb_free_datagram(sk, skb);

        // 如果有需要还要执行dump操作(执行dump操作的通常是内核,用户进程执行dump操作需要进行确认)

        if (nlk->cb_running && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)

            ret = netlink_dump(sk);

        scm_recv(sock, msg, siocb->scm, flags);

out:

        // 正常情况下,程序运行到这里意味着一个承载了netlink消息的skb已经处理完毕

        // 最后在条件合适的情况下将会唤醒该netlink套接字的等待队列中的用户进程

        netlink_rcv_wake(sk);

        return err ? : copied;

    }

至此,netlink通信原理全部分析完毕,当然本文只是针对通用的通信流程进行了展开。

具体协议类型的netlink还拥有自己私有的消息处理方式,针对几个重要的具体协议类型(NETLINK_ROUTE、NETLINK_GENERIC),将会另写文章分别进行分析

给我留言

留言无头像?