linux内核网络协议栈学习笔记(1)

这个系列内容会有点杂,会涉及tcp/ip, netfilter, lvs, 网卡驱动等各个方面。。下半年准备把内核网络这一块好好研究下。。好吧从第一篇开始


这篇介绍内核网络中最重要的数据结构,大部分可以在understanding linux network internal里找到

struct sock

struct sock首先包含一个struct sock_common结构,主要是一些属性,标志位,状态,和哈希表相关的值,内核通过哈希表把这些struct sock_common连接起来

struct sock结构下面包含了一个一个sk_backlog结构,可以看作是一个sk_buff的链表,我猜是connect request的链表,同时还有个对应的sk_sleep的wait_queue_head_t

还有缓冲区相关的参数,如sk_receive_queue, sk_write_queue, 缓冲区大小参数,如sk_sndbuf, sk_rcvbuf,发送接受的时间参数,如sk_rcvtimeo, sk_sndtimeo,以及一些setsockopt里有关的sock属性参数,如sk_priority, sk_lingertime。。 当然更多的是我不知道干吗用的,以后如果涉及到了再说吧


struct sk_buff

sk_buff应该是最重要的结构了,ULN里讲的很多了,我只把我认为最重要的列出来

struct sk_buff  *next,  *prev:所有的sk_buff通过这两个结构组成了一个双向链表

struct sock* sk: 报文对应的sock结构

ktime_t    tstamp:报文到达网卡的时间戳

struct net_device  *dev:报文到达的网络设备

char cb[48]:control buffer,e.g. tcp_skb_cb

sk_buff_data_t transport_header, network_header, mac_header:不同level的报文头offset

sk_buff_data_t tail, end

unsigned char *head, *data:报文加头剥头用到的各种offset

还有各种标志位就不一一讲了


struct sk_buff在内存中的结构有两部分,一块是struct sk_buff的内存,由slab allocator分配,另一块是data指向的真正的报文所在的内存,后面跟一个struct skb_shared_info结构。这块连续内存是在skb_alloc时由kmalloc分配的


skb_shared_info有三个成员用来支持scatter-gather IO,分别是

struct sk_buff* frag_list

unsigned short nr_frags

skb_frag_t frags[MAX_SKB_FRAGS]

frags是scatter-gather的IO。frags是一个线性数组,其中MAX_SKB_FRAGS表示一个scatter-gather skb最多可以有64K个分片(分散在64K个page中),struct skb_frag_t的结构如下

struct skb_frag_struct {
    struct page *page;
    __u32 page_offset;
    __u32 size;
};  

可以看出每个skb_frag_t都代表了单独一个page中的skb数据信息

而frag_list则指向链表的下一个skb,合并skb数据的时候,先是合并frags数组的数据,然后再在frag_list里遍历下一个skb。frag_list一般用于IP分片的场景中,相比而言frags数组只是简单的scatter-gather IO

P.S. 需要注意的是,对于IP fragment/defragment的情况,接受的时候所有分片是会形成一个sk_buff的链表的,但此时IP头的数据基本相同

sk_buff有多个表示length的成员,其区别如下:

sk_buff->len,表示当前协议下的数据长度,包括线性缓冲区的数据长度和分片的数据长度。线性缓冲区的长度从skb->data指针开始计算。注意skb->data是随着协议不同而变化的

sk_buff->data_len,只表示分片的数据长度

sk_buff->truesize,表示sk_buff->len 加上struct sk_buff结构大小


struct net_device

struct net_device代表了网络设备的抽象,在include/linux/netdevice.h中可以看到其定义

char *name, *ifalias:网络设备名,其中所有网络设备组成了一个hash表,由struct hlist_node name_hlist连起来

unsigned long base_addr 记录了设备的io port,一般用来inb outb这些操作  unsigned int irq 记录了设备对应的IRQ中断

unsigned long feature 记录了设备支持的功能:

#define NETIF_F_SG      1   /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM     2   /* Can checksum TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM     4   /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM     8   /* Can checksum all the packets. */
#define NETIF_F_IPV6_CSUM   16  /* Can checksum TCP/UDP over IPV6 */
#define NETIF_F_HIGHDMA     32  /* Can DMA to high memory. */
#define NETIF_F_FRAGLIST    64  /* Scatter/gather IO. */
#define NETIF_F_HW_VLAN_TX  128 /* Transmit VLAN hw acceleration */
#define NETIF_F_HW_VLAN_RX  256 /* Receive VLAN hw acceleration */
#define NETIF_F_HW_VLAN_FILTER  512 /* Receive filtering on VLAN */
#define NETIF_F_VLAN_CHALLENGED 1024    /* Device cannot handle VLAN packets */
#define NETIF_F_GSO     2048    /* Enable software GSO. */

 /* Segmentation offload features */
#define NETIF_F_GSO_SHIFT   16
#define NETIF_F_GSO_MASK    0x00ff0000
#define NETIF_F_TSO     (SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
#define NETIF_F_UFO     (SKB_GSO_UDP << NETIF_F_GSO_SHIFT)
#define NETIF_F_GSO_ROBUST  (SKB_GSO_DODGY << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO_ECN     (SKB_GSO_TCP_ECN << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO6        (SKB_GSO_TCPV6 << NETIF_F_GSO_SHIFT)
#define NETIF_F_FSO     (SKB_GSO_FCOE << NETIF_F_GSO_SHIFT)


int ifindex : 网络设备的单一index

unsigned short mtu:MTU大小

struct dev_addr_list *mc_list, int mc_count, 多播的mac地址列表和个数

unsigned long last_rx:上一次收到包的时间

unsigned char* dev_addr:mac地址?

unsigned char broadcast[MAX_ADDR_LEN]:mac广播地址

struct netdev_queue rx_queue:接收队列

struct netdev_queue *_tx, unsigned int num_tx_queues:既然有了rx_queue为什么还要这这个呢,原来这个是RPS/RFS的patch里新加的多队列的支持

struct netdev_queue *_rx, unsigned int num_rx_queues:发送队列链表,个数,我猜这个是tc qdisc会用到的


netdev_ops是众多函数指针组成的结构体,e.g.

ndo_init:register_netdevice时call到

ndo_uninit:unregister_netdevice时call到

ndo_open:用于dev_open

ndo_close:用于dev_close

这些函数指针都是device driver需要注册进去的,以intel 10Gbit万兆卡为例 ixgbe 驱动定义如下:


static const struct net_device_ops ixgbe_netdev_ops = {
    .ndo_open       = ixgbe_open,
    .ndo_stop       = ixgbe_close,
    .ndo_start_xmit     = ixgbe_xmit_frame,
    .ndo_select_queue   = ixgbe_select_queue,
    .ndo_set_rx_mode        = ixgbe_set_rx_mode,
    .ndo_set_multicast_list = ixgbe_set_rx_mode,
    .ndo_validate_addr  = eth_validate_addr,
    .ndo_set_mac_address    = ixgbe_set_mac,
    .ndo_change_mtu     = ixgbe_change_mtu,
    .ndo_tx_timeout     = ixgbe_tx_timeout, 
    .ndo_vlan_rx_register   = ixgbe_vlan_rx_register,
    .ndo_vlan_rx_add_vid    = ixgbe_vlan_rx_add_vid,
    .ndo_vlan_rx_kill_vid   = ixgbe_vlan_rx_kill_vid,
    .ndo_do_ioctl       = ixgbe_ioctl,
    .ndo_set_vf_mac     = ixgbe_ndo_set_vf_mac,
    .ndo_set_vf_vlan    = ixgbe_ndo_set_vf_vlan,
    .ndo_set_vf_tx_rate = ixgbe_ndo_set_vf_bw,
    .ndo_get_vf_config  = ixgbe_ndo_get_vf_config,
#ifdef CONFIG_NET_POLL_CONTROLLER
    .ndo_poll_controller    = ixgbe_netpoll,
#endif
#ifdef IXGBE_FCOE
    .ndo_fcoe_ddp_setup = ixgbe_fcoe_ddp_get,
    .ndo_fcoe_ddp_done = ixgbe_fcoe_ddp_put,
    .ndo_fcoe_enable = ixgbe_fcoe_enable,
    .ndo_fcoe_disable = ixgbe_fcoe_disable,
    .ndo_fcoe_get_wwn = ixgbe_fcoe_get_wwn,
#endif /* IXGBE_FCOE */
};


关于skb的处理有相当多的函数,一一介绍如下:

alloc_skb:

struct sk_buff *alloc_skb(unsigned int size, int gfp_mask)
{
    struct sk_buff *skb;
    u8 *data;

    /* Get the HEAD */
    /* 从cache缓冲池中获取内存,屏蔽__GFP_DMA的原因在于sk_buff结构体和DMA没毛关系,不需要占用宝贵的DMA内存区 */
    skb = kmem_cache_alloc(skbuff_head_cache,
             gfp_mask & ~__GFP_DMA);
    if (!skb)
        goto out;

    /* Get the DATA. Size must match skb_add_mtu(). */

    /* 对其size */
    size = SKB_DATA_ALIGN(size);

    /* 分配的缓冲长度包含skb_shared_info的长度。这里没有了屏蔽__GFP_DMA,因为数据区域最好能在DMA内存区,这样DMA可以直接操作映射的内存 */
    data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
    if (!data)
        goto nodata;
    
    /* 
     * offsetof是一个编译器宏或者是自定义的宏,用于计算member在struct中的偏移量。
     * 把在truesize前面的field全部清零。
     */
    memset(skb, 0, offsetof(struct sk_buff, truesize));
    
    /* truesize是广义SKB的大小,包含了4个部分的长度:skb自身,header,page frags,frag list. 和len, data_len不同在于,truesize是len + sk_buff结构体大小的总和,每当len有变化,truesize也要跟着变 */
    skb->truesize = size + sizeof(struct sk_buff);
    
    /* users初始化成1 */
    atomic_set(&skb->users, 1);

    /* 初始化所有数据指针 */
    skb->head = data;
    skb->data = data;
    skb->tail = data;
    skb->end = data + size;
    
    /* 
     * skb_shinfo是个宏,#define skb_shinfo(SKB) ((struct skb_shared_info *)((SKB)->end))
     * 所以用这个宏的时候必须等skb->end已经初始化。
     * skb_shinfo 接在skb->end指向的内存空间后面。
      */

    /* 初始化skb_shared_info结构体 */
    atomic_set(&(skb_shinfo(skb)->dataref), 1);
    skb_shinfo(skb)->nr_frags = 0;
    skb_shinfo(skb)->tso_size = 0;
    skb_shinfo(skb)->tso_segs = 0;
    skb_shinfo(skb)->frag_list = NULL;
out:
    return skb;
nodata:
    kmem_cache_free(skbuff_head_cache, skb);
    skb = NULL;
    goto out;
}


skb_drop_fraglist:

static void skb_drop_fraglist(struct sk_buff *skb)
{
    struct sk_buff *list = skb_shinfo(skb)->frag_list;

    skb_shinfo(skb)->frag_list = NULL;
    
    /* 循环前进,直到没有为止。 */
    do {
        struct sk_buff *this = list;
        list = list->next;
        kfree_skb(this);
    } while (list);
}

skb_clone_fraglist:

static void skb_clone_fraglist(struct sk_buff *skb)
{
    struct sk_buff *list;
    /* 对当前skb的frag_list区链上的每个skb增加引用计数。 */
    for (list = skb_shinfo(skb)->frag_list; list; list = list->next)
        skb_get(list);
}


__kfree_skb:

void __kfree_skb(struct sk_buff *skb)
{
    skb_release_all(skb);
    kfree_skbmem(skb);
}

__kfree_skb首先调用了skb_release_all,释放skb成员引用的其他结构体,和skb的数据区域(skb header, skb frag, skb fraglist)

/* Free everything but the sk_buff shell. */
static void skb_release_all(struct sk_buff *skb)
{
    skb_release_head_state(skb);
    skb_release_data(skb);
}

static void skb_release_head_state(struct sk_buff *skb)
{
    /* 减少路由缓存的引用计数 */
    skb_dst_drop(skb);
#ifdef CONFIG_XFRM
    secpath_put(skb->sp);
#endif
    /* 如果有析构,调用析构函数 */
    if (skb->destructor) {
        WARN_ON(in_irq());
        skb->destructor(skb);
    }
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
    nf_conntrack_put(skb->nfct);
    nf_conntrack_put_reasm(skb->nfct_reasm);
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
    nf_bridge_put(skb->nf_bridge);
#endif
/* XXX: IS this still necessary? - JHS */
#ifdef CONFIG_NET_SCHED
    skb->tc_index = 0;
#ifdef CONFIG_NET_CLS_ACT
    skb->tc_verd = 0;
#endif
#endif
}

void skb_release_data(struct sk_buff *skb)
{
    /* 查看skb是否被clone?skb_shinfo的dataref是否为0?
     * 如果是,那么就释放skb非线性区域和线性区域。 */
    if (!skb->cloned ||
     !atomic_sub_return(skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1,
             &skb_shinfo(skb)->dataref)) {
        
        /* 释放page frags区 */
        if (skb_shinfo(skb)->nr_frags) {
            int i;
            for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
                put_page(skb_shinfo(skb)->frags[i].page);
        }

        /* 释放frag_list区 */
        if (skb_shinfo(skb)->frag_list)
            skb_drop_fraglist(skb);

        /* 释放线性区域 */
        kfree(skb->head);
    }
}
kfree_skbmem :

kfree_skbmem用来通过slab allocator释放skb结构体

static void kfree_skbmem(struct sk_buff *skb)
{
    struct sk_buff *other;
    atomic_t *fclone_ref;

    switch (skb->fclone) {
    /* 如果是skbuff_head_cache分配的skb,那么直接通过slab释放掉 */
    case SKB_FCLONE_UNAVAILABLE:
        kmem_cache_free(skbuff_head_cache, skb);
        break;

    /* 如果是skbuff_fclone_cache分配的skb,那么查看fclone_ref,只有为1才能通过skbuff_fclone_cache释放整个结构体(值为1说明只有第一个skb)*/
    case SKB_FCLONE_ORIG:
        fclone_ref = (atomic_t *) (skb + 2);
        if (atomic_dec_and_test(fclone_ref))
            kmem_cache_free(skbuff_fclone_cache, skb);
        break;

    /* 
      如果是skbuff_fclone_cache分配的结构体中的第二个skb,首先把skb->fclone置为无效,接下来查看fclone_ref,只有为1才能通过skbuff_fclone_cache释放整个结构体(值为1说明只有第二个skb)
    */
    case SKB_FCLONE_CLONE:
        fclone_ref = (atomic_t *) (skb + 1);
        other = skb - 1;

        /* The clone portion is available for
         * fast-cloning again.
         */
        skb->fclone = SKB_FCLONE_UNAVAILABLE;

        if (atomic_dec_and_test(fclone_ref))
            kmem_cache_free(skbuff_fclone_cache, other);
        break;
    }
}

skb_realloc_headroom:

struct sk_buff *skb_realloc_headroom(struct sk_buff *skb, unsigned int headroom)
{
    struct sk_buff *skb2;
    /* 计算现在要求的headroom 和原来headroom之间的差值 */
    int delta = headroom - skb_headroom(skb);
    
    /* 如果现在要求的headroom没有原来的headroom大,那说明原来的header section可以用,
     * 所以只要用pskb_copy复制一份skb结构体和它的线性区域就可以了。
     */
    if (delta <= 0)
        skb2 = pskb_copy(skb, GFP_ATOMIC);
    else {
        /* 如果要求的headroom比原来的headroom大的话,clone一个skb */
        skb2 = skb_clone(skb, GFP_ATOMIC);
        /* 把新clone的skb用pskb_expand_head扩大headroom */
        if (skb2 && pskb_expand_head(skb2, SKB_DATA_ALIGN(delta), 0,
                     GFP_ATOMIC)) {
            kfree_skb(skb2);
            skb2 = NULL;
        }
    }
    return skb2;
}


skb_header_pointer

函数从skb->data开始的offset处,把len长度的数据拷贝到buffer中

static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,
                       int len, void *buffer)
{
    int hlen = skb_headlen(skb);
    
    if (hlen - offset >= len)
        return skb->data + offset;
    
    if (skb_copy_bits(skb, offset, buffer, len) < 0)
        return NULL; 

    return buffer;
}   

skb_headlen函数返回skb->len - skb->data_len,表示当前page中的skb数据大小(其中skb->len表示整个skb数据长度,由于skb会分散到多个page中,因此skb->data_len用来表示分散在其他页中的skb数据长度)

hlen - offset >= len 则表示当前page中的线性skb数据大于要拷贝的len大小,那么就很简单地返回skb->data + offset指针即可

否则就说明要拷贝的len大小的数据还有一部分放在了skb非当前page中,此时调用skb_copy_bits,该函数把skb->data + offset开始的len长度的数据拷贝到buffer头开始的空间中


skb_copy_bits

该函数用来把skb->data开头偏移offset的len长度的数据拷贝到to指向的缓冲区,由于skb header线性区的数据可能会不够,也可能offset本身已经超过了当前skb header的线性区,因此该函数需要遍历skb在其他page里的数据,即遍历frags数组指向的page甚至frag_list指向的其他skb结构

int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
{
    int start = skb_headlen(skb);
    struct sk_buff *frag_iter;
    int i, copy;

    /* 如果offset > skb->len - len,说明offset位置错误 */
    if (offset > (int)skb->len - len)
        goto fault;

    /* Copy header. */

    /* 如果copy > 0,说明拷贝的数据有一部分在skb header中,否则拷贝的数据全部在非线性空间 */
    if ((copy = start - offset) > 0) {
        /* 如果copy > len,那么要拷贝的数据全部在skb header中了,此时把copy = len,否则copy <= len,所以只剩下了两种可能 copy == len, copy < len */
        if (copy > len)
            copy = len;

        /* 调用memcpy把skb header中offset开始的copy字节拷贝到to指向的内存区域 */
        skb_copy_from_linear_data_offset(skb, offset, to, copy);

        /* 如果copy == len,那么拷贝已经完成了,返回,否则len减去copy的长度,因为这部分已经被拷贝到to里面了 */
        if ((len -= copy) == 0)
            return 0;

        /* 继续拷贝剩余的部分,此时offset从非线性区开始算起,目的地址to也顺势偏移copy个字节 */
        offset += copy;
        to     += copy;
    }

    /* 开始遍历skb frag数组 */
    for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
        int end;

        WARN_ON(start > offset + len);

        /* end为之前计算的长度加上当前frag的大小 */
        end = start + skb_shinfo(skb)->frags[i].size;
        /* 如果copy > 0,说明还有数据等待拷贝 */
        if ((copy = end - offset) > 0) {
            u8 *vaddr;

            /* 如果copy > len,那么要拷贝的数据全部在当前frag中了,此时把copy = len,否则copy <= len,所以只剩下了两种可能 copy == len, copy < len */
            if (copy > len)
                copy = len;

            /* kmap_skb_frag/kunmap_skb_frag调用kmap_atomic/kunmap_atomic为可能的高端内存地址建立虚拟地址的临时映射 */
            vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
            memcpy(to,
                   vaddr + skb_shinfo(skb)->frags[i].page_offset+
                   offset - start, copy);
            kunmap_skb_frag(vaddr);

            /* 如果copy == len,那么拷贝已经完成了,返回,否则len减去copy的长度,因为这部分已经被拷贝到to里面了 */
            if ((len -= copy) == 0)
                return 0;
            /* 继续增加offset, to的位置 */
            offset += copy;
            to     += copy;
        }
        start = end;
    }

    /* 开始遍历frag list链表 */
    skb_walk_frags(skb, frag_iter) {
        int end;

        WARN_ON(start > offset + len);

        end = start + frag_iter->len;
        if ((copy = end - offset) > 0) {
            if (copy > len)
                copy = len;
            /* 递归调用skb_copy_bits,拷贝copy大小的字节到to指向的内存区域 */
            if (skb_copy_bits(frag_iter, offset - start, to, copy))
                goto fault;
            if ((len -= copy) == 0)
                return 0;
            offset += copy;
            to     += copy;
        }
        start = end;
    }

    /* 此时len应该为0,否则返回-EFAULT */
    if (!len)
        return 0;

fault:
    return -EFAULT;
}
EXPORT_SYMBOL(skb_copy_bits);


pskb_may_pull

static inline int pskb_may_pull(struct sk_buff *skb, unsigned int len)
{   
    if (likely(len <= skb_headlen(skb)))
        return 1;
    if (unlikely(len > skb->len))
        return 0;
    return __pskb_pull_tail(skb, len - skb_headlen(skb)) != NULL;
}

pskb_may_pull函数用来保证skb的线性数据长度至少有len的大小。这里的判断逻辑为

1. 如果skb_headlen,也就是skb->len - skb->data_len,即skb当前page线性空间的大小已经超过了len,啥事情都不需要做

2. 如果len比整个skb的数据长度还要长,直接报错

3. 这个分支表示需要数据拷贝,即从其他页拷贝到线性空间页,调用__pskb_pull_tail来完成


__pskb_pull_tail:可以参考 http://blog.chinaunix.net/uid-22577711-id-3220155.html 的说明

/**
 *  __pskb_pull_tail - advance tail of skb header
 *  @skb: buffer to reallocate
 *  @delta: number of bytes to advance tail
 *
 *  The function makes a sense only on a fragmented &sk_buff,
 *  it expands header moving its tail forward and copying necessary
 *  data from fragmented part.
 *
 *  &sk_buff MUST have reference count of 1.
 *
 *  Returns %NULL (and &sk_buff does not change) if pull failed
 *  or value of new tail of skb in the case of success.
 *
 *  All the pointers pointing into skb header may change and must be
 *  reloaded after call to this function.
 */

/* Moves tail of skb head forward, copying data from fragmented part,
 * when it is necessary.
 * 1. It may fail due to malloc failure.
 * 2. It may change skb pointers.
 *
 * It is pretty complicated. Luckily, it is called only in exceptional cases.
 */
unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta)
{
    /* If skb has not enough free space at tail, get new one
     * plus 128 bytes for future expansions. If we have enough
     * room at tail, reallocate without expansion only if skb is cloned.
     */
    int i, k, eat = (skb->tail + delta) - skb->end;

    /* 如果skb是克隆过的,这时候要调用pskb_expand_head了,同时顺便扩充下skb header的空间。调用pskb_expand_head之后skb->cloned=0, skb_shareinfo(skb)->dataref = 1 */
    if (eat > 0 || skb_cloned(skb)) {
        if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0,
                     GFP_ATOMIC))
            return NULL;
    }

    /* skb header的线性空间已经准备好了,下面把skb->tail之后长度delta的数据,拷贝到skb header偏移从skb_headlen(skb)开始的地方 */
    if (skb_copy_bits(skb, skb_headlen(skb), skb_tail_pointer(skb), delta))
        BUG();

    /* 下面的工作就是去frag, fraglist里收拾残局了 */

    /* Optimization: no fragments, no reasons to preestimate
     * size of pulled pages. Superb.
     */

    /* 如果frag list为空,那么只需要pull frags即可 */
    if (!skb_has_frags(skb))
        goto pull_pages;

    /* Estimate size of pulled pages. */
    eat = delta;

    /* 
        eat = delta,为总共要pull的大小,之后遍历frags数组,如果frags数组中frag size的总和超过了delta,说明在frags中就可以把数据pull完
    */
    for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
        if (skb_shinfo(skb)->frags[i].size >= eat)
            goto pull_pages;
        eat -= skb_shinfo(skb)->frags[i].size;
    }

    /* If we need update frag list, we are in troubles.
     * Certainly, it possible to add an offset to skb data,
     * but taking into account that pulling is expected to
     * be very rare operation, it is worth to fight against
     * further bloating skb head and crucify ourselves here instead.
     * Pure masohism, indeed. 8)8)
     */
    if (eat) {
        struct sk_buff *list = skb_shinfo(skb)->frag_list;
        struct sk_buff *clone = NULL;
        struct sk_buff *insp = NULL;

        /* 如果除了frags之外,还需要pull fraglist,那么麻烦就大了,好吧开始遍历frag list */
        do {
            BUG_ON(!list);

            if (list->len <= eat) {
                /* Eaten as whole. */
                eat -= list->len;
                list = list->next;
                /* insp记录了第一个未被eat过的skb */
                insp = list;
            } else {
                /* Eaten partially. */

                /* 这个skb还是被shared的,那只能clone一个出来,因为需要修改sk_buff */
                if (skb_shared(list)) {
                    /* Sucks! We need to fork list. :-( */
                    clone = skb_clone(list, GFP_ATOMIC);
                    if (!clone)
                        return NULL;
                    /* insp记录了第一个未被eat过的skb */
                    insp = list->next;
                    /* list指向的skb由于被clone过,因此是需要被调用kfree_skb的 */
                    list = clone;
                } else {
                    /* This may be pulled without
                     * problems. */

                    /* insp这里记录了被部分eat的skb */
                    insp = list;
                }
                /* 这里pskb_pull实际递归调用了__pskb_pull_tail,因为有eat长度的部分要被pull掉,把这部分数据pull到skb header中 */
                /* 这里似乎存在一个bug,即list为clone过的skb时,此时再调用pskb_pull会对被clone的skb产生影响,使其数据不正确 */
                if (!pskb_pull(list, eat)) {
                    kfree_skb(clone);
                    return NULL;
                }
                break;
            }
        } while (eat);

        /* 下面释放已经被pull掉数据的frag list中的skb,但是clone的场景中,cloned skb只是被pull掉了部分eat长度的数据,因此需要最后再找回来插入到frag list开头 */

        /* Free pulled out fragments. */
        while ((list = skb_shinfo(skb)->frag_list) != insp) {
            skb_shinfo(skb)->frag_list = list->next;
            kfree_skb(list);
        }
        /* And insert new clone at head. */
        if (clone) {
            clone->next = list;
            skb_shinfo(skb)->frag_list = clone;
        }
    }
    /* Success! Now we may commit changes to skb data. */

pull_pages:
    /* 好了,开始处理frags数组 */
    eat = delta;
    k = 0;
    for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
        if (skb_shinfo(skb)->frags[i].size <= eat) {
            put_page(skb_shinfo(skb)->frags[i].page);
            eat -= skb_shinfo(skb)->frags[i].size;
        } else {
            /* 好了,找到了最后一个被部分eat的frags[i],下面依次把后面没有被eat过的frags[i],frags[i+1] ... frags[nr_frags]拷贝到frags[0],frags[1] ... frags[k] */
            skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i];
            if (eat) {
                /* 对于被部分eat的frags[i],在拷贝给frags[0]之后,还需要修改page_offset, size */
                skb_shinfo(skb)->frags[k].page_offset += eat;
                skb_shinfo(skb)->frags[k].size -= eat;
                eat = 0;
            }
            k++;
        }
    }
    skb_shinfo(skb)->nr_frags = k;

    skb->tail     += delta;
    skb->data_len -= delta;

    return skb_tail_pointer(skb);
}
EXPORT_SYMBOL(__pskb_pull_tail);


pskb_expand_head:pskb_expand_head用来扩展skb head的区域,如果nhead, ntail为0,那么相当于复制一个skb head出来

int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,
             gfp_t gfp_mask)
{
    int i;
    u8 *data;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
    int size = nhead + skb->end + ntail;
#else
    int size = nhead + (skb->end - skb->head) + ntail;
#endif
    long off;

    BUG_ON(nhead < 0);

    if (skb_shared(skb))
        BUG();

    size = SKB_DATA_ALIGN(size);

    data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
    if (!data)
        goto nodata;
    /* 把sk_buff的成员拷贝到新的内存去 */
#ifdef NET_SKBUFF_DATA_USES_OFFSET
    memcpy(data + nhead, skb->head, skb->tail);
#else
    memcpy(data + nhead, skb->head, skb->tail - skb->head);
#endif
    memcpy(data + size, skb_end_pointer(skb),
           sizeof(struct skb_shared_info));
    for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
        get_page(skb_shinfo(skb)->frags[i].page);   /* 遍历skb_shinfo(skb)->frags数组的所有page,增加page的refcnt */

    if (skb_has_frags(skb))  /* 遍历skb的fragments链表,增加skb的refcnt */
        skb_clone_fraglist(skb);

    skb_release_data(skb);  /* 释放skb的数据部分 */
    off = (data + nhead) - skb->head;
    skb->head     = data;
    skb->data    += off;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
    skb->end      = size;
    off           = nhead;
#else   
    skb->end      = skb->head + size;
#endif  
    /* {transport,network,mac}_header and tail are relative to skb->head */
    skb->tail         += off;
    skb->transport_header += off;
    skb->network_header   += off;
    if (skb_mac_header_was_set(skb))
        skb->mac_header += off;
    skb->csum_start       += nhead;
    skb->cloned   = 0;
    skb->hdr_len  = 0;
    skb->nohdr    = 0;
    atomic_set(&skb_shinfo(skb)->dataref, 1);
    return 0;
nodata:
    return -ENOMEM;
}

pskb_expand_head用来扩充skb的空间大小。skb->head扩充nhead大小,skb->tail扩充ntail大小。


skb_make_writable: 该函数使得skb header至少有writable_len的数据可写

int skb_make_writable(struct sk_buff *skb, unsigned int writable_len)
{
    if (writable_len > skb->len)  /* 如果要写入的数据大小超过了skb->len即整个skb的大小,溢出返错 */
        return 0;
    
    /* Not exclusive use of packet?  Must copy. */
    if (!skb_cloned(skb)) { // 非cloned
        if (writable_len <= skb_headlen(skb))  // 如果当前页skb线性空间能容纳要写入的数据长度,返回成功
            return 1;
    } else if (skb_clone_writable(skb, writable_len))  // cloned skb,则判断skb_headroom是否有空间容纳
        return 1;

    if (writable_len <= skb_headlen(skb))
        writable_len = 0;
    else
        writable_len -= skb_headlen(skb);

    return !!__pskb_pull_tail(skb, writable_len);  //为线性缓冲区增加writable_len的空间
}


skb_push

skb_push上移skb->data指针,该指针指向skb的数据区。此举增加skb数据部分的空间,但skb->data不能小于skb->head,后者代表了skb头部指针。


skb_pull: 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;
}

skb_pull下移skb->data指针,之前需要保证线性数据区的长度足够


pskb_pull: pskb_pull实际调用__pskb_pull,__pskb_pull通过调用__pskb_pull_tail保证了header中有足够的空间容纳skb->data指针下移len位置

static inline unsigned char *__pskb_pull(struct sk_buff *skb, unsigned int len)
{
    if (len > skb_headlen(skb) &&
        !__pskb_pull_tail(skb, len - skb_headlen(skb)))
        return NULL;
    skb->len -= len;
    return skb->data += len;
}


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;
}

skb_put增加skb->tail的值,skb->tail代表一个偏移量。需要保证skb->tail的偏移不超过skb->end代表的偏移。


skb_clone:

skb_clone实际调用的__skb_clone。__skb_clone只复制sk_buff结构,不复制skb数据部分,两个sk_buff结构指向同一个数据缓冲区

static struct sk_buff *__skb_clone(struct sk_buff *n, struct sk_buff *skb)
{
    n->next = n->prev = NULL;
    n->sk = NULL;
    __copy_skb_header(n, skb);

    C(len);
    C(data_len);
    C(mac_len);
    n->hdr_len = skb->nohdr ? skb_headroom(skb) : skb->hdr_len;
    n->cloned = 1;
    n->nohdr = 0;
    n->destructor = NULL;
    C(tail);
    C(end);
    C(head);
    C(data);
    C(truesize);
    atomic_set(&n->users, 1);

    atomic_inc(&(skb_shinfo(skb)->dataref));
    skb->cloned = 1;

    return n;
}

skb的引用计数为1,skb->cloned设为1。skb_shinfo(skb)->dataref增加1,因为有两个skb指向这块数据缓冲区。


pskb_copy:

struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask)
{
    /*
     *  Allocate the copy buffer
     */
    struct sk_buff *n;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
    n = alloc_skb(skb->end, gfp_mask);   /* 分配skb结构和线性数据缓冲区,数据缓冲区size为skb->end的值 */
#else
    n = alloc_skb(skb->end - skb->head, gfp_mask);
#endif
    if (!n)
        goto out;
    /* Set the data pointer */
    skb_reserve(n, skb->data - skb->head); /* skb_reserve预留skb headroom空间 */
    /* Set the tail pointer and length */
    skb_put(n, skb_headlen(skb)); /* skb_put下移skb->tail指针,预留skb_headlen(skb)大小的空间,即数据线性缓冲区大小的空间 */
    /* Copy the bytes */
    skb_copy_from_linear_data(skb, n->data, n->len);  /* skb->data拷贝n->len长度的数据到n->data指向的空间 */

    n->truesize += skb->data_len;
    n->data_len  = skb->data_len;
    n->len       = skb->len;

    if (skb_shinfo(skb)->nr_frags) {
        int i;

        for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
            skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];
            get_page(skb_shinfo(n)->frags[i].page);   /* 只增加refcnt */
        }
        skb_shinfo(n)->nr_frags = i;
    }

    if (skb_has_frags(skb)) {
        skb_shinfo(n)->frag_list = skb_shinfo(skb)->frag_list;
        skb_clone_fraglist(n);
    }
    copy_skb_header(n, skb);  /* copy_skb_header只复制sk_buff结构内容 */
out:
    return n;
}

pskb_copy只复制skb和数据线性缓冲区的部分,不包括frag list以及skb list


skb_copy

struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)
{
    int headerlen = skb->data - skb->head;
    /*
     *  Allocate the copy buffer
     */
    struct sk_buff *n;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
    n = alloc_skb(skb->end + skb->data_len, gfp_mask);
#else
    n = alloc_skb(skb->end - skb->head + skb->data_len, gfp_mask);
#endif
    if (!n)
        return NULL;

    /* Set the data pointer */
    skb_reserve(n, headerlen);
    /* Set the tail pointer and length */
    skb_put(n, skb->len);

    if (skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len))
        BUG();

    copy_skb_header(n, skb);
    return n;
}

skb_copy是把skb的所有数据全部拷贝到新skb的线性数据缓冲区中,之前即使有frag list或者skb list,也会一并merge到线性内存中


pskb_trim:

pskb_trim把skb的长度trim到指定的len。如果len > skb->len,那么直接返回0,否则调用__pskb_trim

static inline int __pskb_trim(struct sk_buff *skb, unsigned int len)
{
    /* 如果skb->data_len不为0,说明有非线性部分,此时调用 ___pskb_trim */
    if (skb->data_len)
        return ___pskb_trim(skb, len);
    /* 否则skb只有线性部分,调用__skb_trim */
    __skb_trim(skb, len);
    return 0;
}
__skb_trim 调用 skb_set_tail_pointer 把 skb->tail 赋值给 skb->data + len 的位置
static inline void __skb_trim(struct sk_buff *skb, unsigned int len)
{
    if (unlikely(skb->data_len)) {
        WARN_ON(1);
        return;
    }
    skb->len = len;
    skb_set_tail_pointer(skb, len);
}
__pskb_trim 有一点复杂

int ___pskb_trim(struct sk_buff *skb, unsigned int len)
{
    struct sk_buff **fragp;
    struct sk_buff *frag;
    int offset = skb_headlen(skb);  /* offset设置为skb header长度 */
    int nfrags = skb_shinfo(skb)->nr_frags;
    int i;
    int err;

    /* 
      如果skb是克隆过的,尝试调用pskb_expand_head分离出一个独立的skb,此时skb->head指向新分配的header线性区域
      这里阐述下skb clone的细节:

      1) skb可以从两种slab allocator中来分配,skbuff_head_cache分配的是一个struct sk_buff,而skbuff_fclone_cache则是两个struct sk_buff加一个atomic_t的结构
      2) skbuff_fclone_cache分配出来的第一个skb是主体,skb->fclone = SKB_FCLONE_ORIG,第二个skb是留给未来的cloned skb,skb->fclone = SKB_CLONE_UNAVAILABLE,如果未来调用了skb_clone,则把第二个skb->fclone设为SKB_FCLONE_CLONE。最后一个atomic_t* fclone_ref,记录了克隆次数
      3) skbuff_head_cache分配的skb,有skb->flone = SKB_FCLONE_UNAVAILABLE
      4) 无论是哪种skb,都会通过__skb_clone,把orig skb内容复制到dst skb中,两个skb会指向同一个skb header线性数据区域(包括同样的frag和frag list)。这样header区域的dataref会增加(skb_shinfo(skb)->dataref),orig skb的skb->cloned设为1,dst skb的skb->users设为1
      5) skb->users和skb克隆没半毛钱关系,是表示skb被引用的次数(只有skb->users为0才可以释放)

      总结下来:
      1) skb_clone克隆一个skb出来,和老的skb共享skb header和skb frag, skb fraglist
      2) pskb_copy复制一个skb连同skb header出来,但skb frag, skb fraglist依旧共享
      3) skb_copy完全复制一个skb的内容,包括skb frag, skb fraglist
    */
    if (skb_cloned(skb) &&
        unlikely((err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC))))
        return err;

    i = 0;
    if (offset >= len)  /* 如果offset >= len,说明trim的目的length范围已经在skb header之内了,那么可以drop所有的frag, fraglist */
        goto drop_pages;

    /*
      下面的循环,遍历skb frag,找到len之后的frag后全部trim掉
    */
    for (; i < nfrags; i++) {
        int end = offset + skb_shinfo(skb)->frags[i].size;

        if (end < len) {
            offset = end;
            continue;
        }

        /* 最后一个frag只取其长度0 -> len - offset的部分 */
        skb_shinfo(skb)->frags[i++].size = len - offset;

drop_pages:
        skb_shinfo(skb)->nr_frags = i;

        /* 对接下来的frag,调用put_page,最后调用skb_drop_fraglist释放skb fraglist */
        for (; i < nfrags; i++)
            put_page(skb_shinfo(skb)->frags[i].page);

        if (skb_has_frags(skb))
            skb_drop_fraglist(skb);
        goto done;
    }

    /*
      遍历frag list,trim掉多余的skb
    */
    for (fragp = &skb_shinfo(skb)->frag_list; (frag = *fragp);
         fragp = &frag->next) {
        int end = offset + frag->len;

        /* 如果skb有多个引用,此时skb_clone出一个nskb,但skb, nskb还指向同一个线性区域 */
        if (skb_shared(frag)) {
            struct sk_buff *nfrag;

            nfrag = skb_clone(frag, GFP_ATOMIC);
            if (unlikely(!nfrag))
                return -ENOMEM;

            nfrag->next = frag->next;
            kfree_skb(frag);
            frag = nfrag;
            *fragp = frag;
        }

        if (end < len) {
            offset = end;
            continue;
        }

        /* 递归调用pskb_trim,裁剪最后一个fraglist skb的长度 */
        if (end > len &&
            unlikely((err = pskb_trim(frag, len - offset))))
            return err;

        /* drop掉剩余的fraglist */
        if (frag->next)
            skb_drop_list(&frag->next);
        break;
    }

done:
    /* 如果 len > skb_headlen(skb),说明还有非线性部分,这时修正一下skb->data_len, skb->len的值即可 */
    if (len > skb_headlen(skb)) {
        skb->data_len -= skb->len - len;
        skb->len       = len;
    } else {
        /* 此时只有skb header了,那么skb->data_len自然为0,修正skb->len的值,调整skb->tail指针位置 */
        skb->len       = len;
        skb->data_len  = 0;
        skb_set_tail_pointer(skb, len);
    }

    return 0;
}
EXPORT_SYMBOL(___pskb_trim);


skb_cow:

skb_cow实际调用了__skb_cow。skb_cow可以用在skb_clone之后,如果新clone的skb不想和orig skb共享skb header

static inline int __skb_cow(struct sk_buff *skb, unsigned int headroom,
                int cloned)
{
    int delta = 0;

    /* 计算出新的headroom大小 */
    if (headroom < NET_SKB_PAD)
        headroom = NET_SKB_PAD;
    if (headroom > skb_headroom(skb))
        delta = headroom - skb_headroom(skb);

    /* 如果headroom有变化,或者skb是skb_clone生成的,那么调用pskb_expand_head重新生成skb header */
    if (delta || cloned)
        return pskb_expand_head(skb, ALIGN(delta, NET_SKB_PAD), 0,
                    GFP_ATOMIC);
    return 0;
}

/**
 *  skb_cow - copy header of skb when it is required
 *  @skb: buffer to cow
 *  @headroom: needed headroom
 *
 *  If the skb passed lacks sufficient headroom or its data part
 *  is shared, data is reallocated. If reallocation fails, an error
 *  is returned and original skb is not changed.
 *
 *  The result is skb with writable area skb->head...skb->tail
 *  and at least @headroom of space at head.
 */
static inline int skb_cow(struct sk_buff *skb, unsigned int headroom)
{
    return __skb_cow(skb, headroom, skb_cloned(skb));
}


你可能感兴趣的:(linux内核网络协议栈学习笔记(1))