第二章 关键数据结构

来源:互联网 发布:郑州 java 编辑:程序博客网 时间:2024/06/06 02:18

一、网络代码常见数据结构

 struct sk_buff
 struct net_device

 struct sock(本文不涉及)


二、 套接字缓冲区:sk_buff结构

        涉及文件:src/net/core/dev.c、src/include/linux/skbuff.h

        任何一个网络封包都会存储在这里。各个网络分层都会使用这个结构来储存其报头、有关用户数据的信息等。这个是linux网络代码中最重要的数据结构,代表已经接收或者正在传输的数据结构。结构体数据字段大致分为:

Layout(布局字段,主要是维护链表和维护skb_buff数据)
General(通用字段)
Feature-specific(功能专用字段)

Management functions(管理函数)

sk_buff结构体具体内容(linux3.0.8)如下:

struct sk_buff {
    /* These two members must be first. */
    struct sk_buff      *next;
    struct sk_buff      *prev;

    ktime_t         tstamp;

    struct sock     *sk;
    struct net_device   *dev;

    /*
     * This is the control buffer. It is free to use for every
     * layer. Please put your private variables there. If you
     * want to keep them across layers you have to do a skb_clone()
     * first. This is owned by whoever has the skb queued ATM.
     */
    char            cb[48] __aligned(8);

    unsigned long       _skb_refdst;
#ifdef CONFIG_XFRM
    struct  sec_path    *sp;
#endif

    unsigned int        len,
                data_len;
    __u16           mac_len,
                hdr_len;
    union {
        __wsum      csum;
        struct {
            __u16   csum_start;
            __u16   csum_offset;
        };
    };
    __u32           priority;
    kmemcheck_bitfield_begin(flags1);
    __u8            local_df:1,
                cloned:1,
                ip_summed:2,
                nohdr:1,
                nfctinfo:3;
    __u8            pkt_type:3,
                fclone:2,
                ipvs_property:1,
                peeked:1,
                nf_trace:1;
    kmemcheck_bitfield_end(flags1);

    __be16          protocol;

    void            (*destructor)(struct sk_buff *skb);
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
    struct nf_conntrack *nfct;
#endif
#ifdef NET_SKBUFF_NF_DEFRAG_NEEDED
    struct sk_buff      *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
    struct nf_bridge_info   *nf_bridge;
#endif

    int         skb_iif;
#ifdef CONFIG_NET_SCHED
    __u16           tc_index;   /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
    __u16           tc_verd;    /* traffic control verdict */
#endif
#endif

    __u32           rxhash;

    __u16           queue_mapping;

    kmemcheck_bitfield_begin(flags2);
#ifdef CONFIG_IPV6_NDISC_NODETYPE
    __u8            ndisc_nodetype:2;
#endif
    __u8            ooo_okay:1;
    kmemcheck_bitfield_end(flags2);

    /* 0/13 bit hole */

#ifdef CONFIG_NET_DMA
    dma_cookie_t        dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
    __u32           secmark;
#endif
    union {
        __u32       mark;
        __u32       dropcount;
    };

    __u16           vlan_tci;

    sk_buff_data_t      transport_header;
    sk_buff_data_t      network_header;
    sk_buff_data_t      mac_header;
    /* These elements must be at the end, see alloc_skb() for details.  */
    sk_buff_data_t      tail;
    sk_buff_data_t      end;
    unsigned char       *head,
                *data;
    unsigned int        truesize;
    atomic_t        users;
};

2.1 layout字段

           该字段主要是为了方便搜寻以及组织sk_buff数据结构本身。通过一个双向链表来维护sk_buff数据结构,为了迅速找出整个链表头,额外增加了一个sk_buff_head结构(src/include/linux/skbuff.h)。sk_buff与sk_buff_head的关系图如下(摘自深入理解linux网络技术):

1、sk_buff_head结构初始化

(1)网络设备驱动(以stmmac为例)

         在stmmac_dvr_probe()函数里初始化一个回收sk_buff队列:skb_queue_head_init(&priv->rx_recycle),priv为网卡私有数据结构:struct stmmac_priv。

(2)网络设备处理(公共)

        在net_dev_init函数里进行了两个初始化处理:skb_queue_head_init(&sd->input_pkt_queue)和skb_queue_head_init(&sd->process_queue),sd为每个CPU的网络数据结构softnet_data,因此每个CPU都有sk_buff处理的相应队列。

(3)其他一些字段

struct sock *sk:指向拥有此缓冲区的套接字的sock数据结构。当数据在本地产生或者正由本地进程接受时,就需要这个指针。

unsigned int len:指缓冲区中数据区块的大小,包括主要缓冲区(由head指)的数据以及一些片段的数据。

unsigned int data_len:只计算片段中的数据大小

__u16 mac_len:MAC报头的大小

__u16 hdr_len:head头的大小


2.2 general字段

(1)ktime_t tstamp

        时间戳记,通常只对一个已经接收的封包才有意义,表示封包何时被接收,或者有时用于表示封包预定传输的时间。在接受函数netif_rx or netif_receive_skb对该字段进行设置。

(2)struct net_device *dev

       此字段描述一个网络设备,其类型net_device。当接收到一个封包时,该字段填充接收该封包的设备;当发送一个封包时,该字段填发送该封包的设备。

(3)unsigned long _skb_refdst

       由路由子系统使用,目的地入口

(4)char cb[48] __aligned(8)

       这是一个控制缓冲区或者私有数据存储空间,保存每层的控制信息,各个层通过宏来进行访问。

(5)校验和及关联标志

 union {
        __wsum      csum;
        struct {
            __u16   csum_start;
            __u16   csum_offset;
        };
    };

和ip_summed:2,

(6)位段

       kmemcheck_bitfield_begin(flags1);
    __u8            local_df:1, //本地可切片标志
                cloned:1,  //表示该缓冲区为另外一个sk_buff的克隆
                ip_summed:2, //驱动程序是否进行校验表示
                nohdr:1, //
                nfctinfo:3; //netfilter使用
    __u8            pkt_type:3, //根据帧的L2目的地址进行类型划分,表示主要数据包的类型:多播,单薄,回环等
                fclone:2,
                ipvs_property:1,
                peeked:1,
                nf_trace:1;
    kmemcheck_bitfield_end(flags1);

(7)__u32 priority

     表示正被传输或转发的封包QOS(服务质量)优先级

(8) __be16 protocol

      驱动程序通过该字段识别L3层的协议,通知上层使用哪个处理例程,典型的有IP、IPV6、ARP等。

2.3 功能专用字段

        这些字段一般只有当内核配置支持特定功能时才会被包含近sk_buff数据结构,如:netfilter防火墙 或QOS。
(1)netfilter防火墙使用
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
    struct nf_conntrack *nfct;
#endif
#ifdef NET_SKBUFF_NF_DEFRAG_NEEDED
    struct sk_buff      *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
    struct nf_bridge_info   *nf_bridge;
#endif

(2)流量控制
#ifdef CONFIG_NET_SCHED
    __u16           tc_index;   /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
    __u16           tc_verd;    /* traffic control verdict */
#endif
#endif

2.4 相关操作函数

      内核通常提供许多很短、很简单的函数,用以操作sk_buff元素或元素列表。
(1)skb内存申请dev_allock_skb 和 alloc_skb
       alloc_skb分配缓冲区主要函数,会进行两次内存申请,一次是从cache使用slab申请报头(即为sizeof(struct sk_buff)),第二次是使用kmalloc取得一个数据缓冲区。
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
....
size = SKB_DATA_ALIGN(size);
data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info)
struct skb_shared_info:主要处理有些IP片段。
申请完之后进行初始化:
    skb->truesize = size + sizeof(struct sk_buff);
    atomic_set(&skb->users, 1);
    skb->head = data;
    skb->data = data;
    skb_reset_tail_pointer(skb);
    skb->end = skb->tail + size;

       
      dev_allock_skb由设备驱动程序使用的缓冲区分配函数,该函数只是一个包裹alloc_skb的函数,在申请大小多添加了NET_IP_ALIGN,并使用skb_reserve进行保留skb_reserve(skb, NET_IP_ALIGN) NET_IP_ALIGN=2,把IP对其在16字节地址边界上。由于Ethernet帧有14个字节头,保留两个字节后,IP报头就可以从缓冲区开始按照16字节边界对齐,并紧接在Ethernet报头之后。

(2)释放内存:kfree_skb 和 dev_kfree_skb
(3  skb_reserve
     该函数会在缓冲区的头部预留一些空间,通常允许插入一个报头,或者强迫数据对齐某个边界。
static inline void skb_reserve(struct sk_buff *skb, int len)
{       
    skb->data += len;
    skb->tail += len;
}
(4)skb_put
     该函数在缓冲区末尾添加数据
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
    unsigned char *tmp = skb_tail_pointer(skb);
    SKB_LINEAR_ASSERT(skb);
    skb->tail += len;
    skb->len  += len;
    if (unlikely(skb->tail > skb->end))
        skb_over_panic(skb, len, __builtin_return_address(0));
    return tmp;
}
(5)skb_push
     该函数在缓冲区开头添加数据
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
    skb->data -= len;
    skb->len  += len;
    if (unlikely(skb->data<skb->head))
        skb_under_panic(skb, len, __builtin_return_address(0));
    return skb->data;
}
(6)skb_pull
     该函数从缓冲区开端删除数据,包裹函数__skb_pull
static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
    skb->len -= len;
    BUG_ON(skb->len < skb->data_len);
    return skb->data += len;
}


(7)struct skb_shared_info
      data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info)在数据缓冲区的末尾申请该数据结构,紧接在标记数据尾端的end指针之后,用于保存此数据区块的附加信息IP分片:
struct skb_shared_info {
    unsigned short  nr_frags;
    unsigned short  gso_size;
    /* Warning: this field is not always filled in (UFO)! */
    unsigned short  gso_segs;
    unsigned short  gso_type;
    __be32          ip6_frag_id;
    __u8        tx_flags;
    struct sk_buff  *frag_list;
    struct skb_shared_hwtstamps hwtstamps;
    /*
     * 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];
};
sk_buff结构中没有指向该数据结构的字段,为了访问该数据结构,函数必须使用返回end指针的skb_shinfo宏进行访问:
#define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))


(8)缓冲区的克隆和拷贝
       只是读取一个缓冲区并不修改数据区内容——克隆;另外,一种是需要修改数据区内容甚至连片段数据区内容也要修改——拷贝。克隆使用函数skb_clone,拷贝使用函数pskb_copy和skb_copy,两者的区别是后者也拷贝片段数据。


(9)列表管理函数
       这些函数会操作sk_buff元素列表,具体如下:
skb_queue_head_init:初始化一个队列头
skb_queue_head, skb_queue_tail:把一个缓冲区分别添加到队列的头或尾
skb_dequeue, skb_dequeue_tail:把一个缓冲区从队列的头或尾删除
skb_queue_purge:把队列清空
skb_queue_walk:依次循环运行队列里中的每个元素 

三、 网络设kfree_skb备:net_device结构

每种网络设备都用这个数据结构表示,包括软硬件信息。
0 0
原创粉丝点击