ILD

non linear skb
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2023-5-17 站点:Inside Linux Development

最近在研究ipq5018的有线驱动,发现它支持S/G (scatter gather)。


驱动首先得告诉内核,我支持S/G,然后内核才会产生对应得skb。在set_features接口中:

static void syn_dp_if_set_features(struct nss_dp_data_plane_ctx *dpc)

{

        struct net_device *netdev = dpc->dev;


        netdev->features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM | NETIF_F_FRAGLIST | NETIF_F_SG;

        netdev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM | NETIF_F_FRAGLIST | NETIF_F_SG;

        netdev->vlan_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM | NETIF_F_FRAGLIST | NETIF_F_SG;

        netdev->wanted_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM | NETIF_F_FRAGLIST | NETIF_F_SG;

}


有两个重要的feature,就是:NETIF_F_FRAGLIST 和 NETIF_F_SG。

前者对应 frag_list,表示有多个skb,链在一起。

后者对应nr_frags,表示一个skb,有多个数据片。

这也是非线性skb的两种形式。


判断一个skb是否是非线性skb

static inline bool skb_is_nonlinear(const struct sk_buff *skb)

{

        return skb->data_len;

}

data_len是fragments中数据的大小。


非线性信息都存储在skb_shared_info结构体中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* This data is invariant across clones and lives at
 * the end of the header data, ie. at skb->end.
 */
struct skb_shared_info {
        __u8            flags;
        __u8            meta_len;
        __u8            nr_frags;
        __u8            tx_flags;
        unsigned short  gso_size;
        /* Warning: this field is not always filled in (UFO)! */
        unsigned short  gso_segs;
        struct sk_buff  *frag_list;
        struct skb_shared_hwtstamps hwtstamps;
        unsigned int    gso_type;
        u32             tskey;
 
        /*
         * Warning : all fields before dataref are cleared in __alloc_skb()
         */
        atomic_t        dataref;
 
        /* Intermediate layers must ensure that destructor_arg
         * remains valid until skb destructor */
        void *          destructor_arg;
 
        /* must be last field, see pskb_expand_head() */
        skb_frag_t      frags[MAX_SKB_FRAGS];
};


使用

#define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))

可以获得一个skb的 skb_shared_info,这个结构体存储在head数据区的结尾。


frag_list

单向链表,指向下一个skb碎片,使用宏skb_walk_frags可以遍历各个skb:

#define skb_walk_frags(skb, iter)       \

        for (iter = skb_shinfo(skb)->frag_list; iter; iter = iter->next)


frags

这个是一个数组,nr_frags表示当前有多少个碎片,最多有MAX_SKB_FRAGS个碎片。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* To allow 64K frame to be packed as single skb without frag_list we
 * require 64K/PAGE_SIZE pages plus 1 additional page to allow for
 * buffers which do not start on a page boundary.
 *
 * Since GRO uses frags we allocate at least 16 regardless of page
 * size.
 */
#if (65536/PAGE_SIZE + 1) < 16
#define MAX_SKB_FRAGS 16UL
#else
#define MAX_SKB_FRAGS (65536/PAGE_SIZE + 1)
#endif
extern int sysctl_max_skb_frags;

根据内核注释,frags最多可以容纳64K的数据。如果数据超过64K,那就需要是用frag_list,来链接额外的skb。


碎片数据结构体是:

typedef struct bio_vec skb_frag_t;

它是一个bio_vec结构体,定义在linux/bvec.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * struct bio_vec - a contiguous range of physical memory addresses
 * @bv_page:   First page associated with the address range.
 * @bv_len:    Number of bytes in the address range.
 * @bv_offset: Start of the address range relative to the start of @bv_page.
 *
 * The following holds for a bvec if n * PAGE_SIZE < bv_offset + bv_len:
 *
 *   nth_page(@bv_page, n) == @bv_page + n
 *
 * This holds because page_is_mergeable() checks the above property.
 */
struct bio_vec {
        struct page     *bv_page;
        unsigned int    bv_len;
        unsigned int    bv_offset;
};

一个碎片就是一个page。因为一个page,不是所有的内容都可以用来存储数据,所以 MAX_SKB_FRAGS 增多了一个,满足64K


获取碎片的长度

1
2
3
4
5
6
7
8
/**
 * skb_frag_size() - Returns the size of a skb fragment
 * @frag: skb fragment
 */
static inline unsigned int skb_frag_size(const skb_frag_t *frag)
{
        return frag->bv_len;
}


所以,对于tx一个skb的数据流程:

1 先tx head数据区,数据 skb->data,长度 skb_headlen(skb)  skb->len - skb->data_len

2 在tx frags 碎片区,个数nr_frags,每个碎片的数据skb_frag_address(frag),长度 skb_frag_size(frag)

3 遍历frag list:     skb_walk_frags(skb, iter_skb) ,一个就是一个skb,执行 1和2 发送。


疑问:

skb->data_len,是不是包括frag list中所有skb的数据长度?


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.