linux的netlink接口詳解(下)

——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),將會另寫文章分別進行分析

發佈了61 篇原創文章 · 獲贊 15 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章