Linux TCP/IP协议栈学习笔记(2)

2.    协议栈各部分初始化

好吧,下面进入协议栈各部分的初始化。下面的章节会根据《书》中的顺序来讲解/学习。先总体说说协议栈初始化所涉及到的主要函数:

core_initcall(sock_init);    /* early initcall */ 定义在socket.c [net]

subsys_initcall(net_dev_init); 定义在dev.c [net\core]

fs_initcall(inet_init); 定义在af_inet.c [net\ipv4]

上述三个函数分别用core_initcallsubsys_initcallfs_initcall修饰,根据上节内容,他们分别处于.initcall.1.init.initcall.4.init.initcall.5.init内。从do_initcalls函数的for循环中我们可以得出这三个函数也是按照上面这个顺序调用的。

先来说说第一个函数sock_init

socket.c [net]

static int __init sock_init(void)
{
    /*
     * Initialize sock SLAB cache.
     */

    sk_init();
    /*
     * Initialize skbuff SLAB cache
     */

    skb_init();
    /*
     * Initialize the protocols module.
     */

    init_inodecache();
    register_filesystem(&sock_fs_type);
    sock_mnt = kern_mount(&sock_fs_type);
    /* The real protocol initialization is performed in later initcalls.
     */

#ifdef CONFIG_NETFILTER
    netfilter_init();
#endif
#ifdef CONFIG_NETWORK_PHY_TIMESTAMPING
    skb_timestamping_init();
#endif
    return 0;
}

 

sock_init函数看上去比较简单,其实里面完成了相当重要的工作。第一句调用sk_init(),其实不做什么实质性的事,只是对一些变量进行赋值。

sock.c [net\core]

void __init sk_init(void)
{
    if (totalram_pages <= 4096) {
        sysctl_wmem_max = 32767;
        sysctl_rmem_max = 32767;
        sysctl_wmem_default = 32767;
        sysctl_rmem_default = 32767;
    } else if (totalram_pages >= 131072) {
        sysctl_wmem_max = 131071;
        sysctl_rmem_max = 131071;
    }
}

 

下面一句函数调用skb_init,涉及到网络的内存管理。

2.1 网络内存管理

先来看看skb_init的代码:

skbuff.c [net\core]

void __init skb_init(void)
{
    skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
                     sizeof(struct sk_buff),
                     0,
                     SLAB_HWCACHE_ALIGN|SLAB_PANIC,
                     NULL);
    skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",
                        (2*sizeof(struct sk_buff)) +
                        sizeof(atomic_t),
                        0,
                        SLAB_HWCACHE_ALIGN|SLAB_PANIC,
                        NULL);
}

 

函数的作用就是创建了两个缓存,skbuff_head_cacheskbuff_fclone_cache。协议中相关的数据都在这两个缓存中创建。

2.1.1 sk_buff结构

下面我们引入协议栈中的数据载体sk_buff这个结构体,不管在应用层还是数据链路层,不管被称作帧(frame)还是数据包(data package),都用这个sk_buff表示。

skbuff.h [include\linux]


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;
    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;
    kmemcheck_bitfield_begin(flags2);
    __u16            queue_mapping:16;
#ifdef CONFIG_IPV6_NDISC_NODETYPE
    __u8            ndisc_nodetype:2,
                deliver_no_wcard:1;
#else
    __u8            deliver_no_wcard:1;
#endif
    kmemcheck_bitfield_end(flags2);
    /* 0/14 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;
};

 

在这里我想先说一下内核版本升级中对sk_buff的修改。在2.6.18中,sk_buff定义了三个协议头来对应不同的协议层,传输层协议头h,网络层协议头nh,链路层协议头mac,他们都分别被定义为联合体。

skbuff.c [include\linux] (2.6.18)

union {
        struct tcphdr    *th;
        struct udphdr    *uh;
        struct icmphdr    *icmph;
        struct igmphdr    *igmph;
        struct iphdr    *ipiph;
        struct ipv6hdr    *ipv6h;
        unsigned char    *raw;
    } h;

    union {
        struct iphdr    *iph;
        struct ipv6hdr    *ipv6h;
        struct arphdr    *arph;
        unsigned char    *raw;
    } nh;

    union {
         unsigned char     *raw;
    } mac;

 

而在2.6.36中,其实在2.6.25等版本中,具体从那个版本开始的未考证。这三个协议头被定义为sk_buff_data_t类型,变量名分别为:transport_headernetwork_headermac_header

skbuff.h [include\linux] (2.6.36)

    sk_buff_data_t        transport_header;
    sk_buff_data_t        network_header;
    sk_buff_data_t        mac_header;

 

下面简单的介绍下sk_buff这个结构体,从结构体的前两个成员中我们可以看出sk_buff会被保存在一个双向链表中。

还有就是四个成员变量分别指向的地址tail endheaddata

head指针指向用来存储网络报文的存储空间的起始地址,在分配sk_buff之后,就固定不变了。这个存储空间也可以被称为数据缓冲区。

end指针指向用来存储网络报文的存储空间的结尾,和head相对应,在分配sk_buff之后也固定不变。

data指针指向的是各不同协议层的网络报文的头部,指针随着拥有sk_buff的协议层的不同而不同。

tail指针指向的是当前协议层网络报文的结尾地址,与data对应。具体可以见下图:

Linux TCP/IP协议栈学习笔记

Fromunderstanding linux network internals

有了上述四个指针的认识以后我们可以来看成员变量lendata_len以及truesizelen表示网络报文有效数据长度,包括协议头和数据,在不同的协议层,它的值也不同。data_len表示分片的长度。truesize是存储网络数据的存储 空间的大小,它的值比len大,以16字节对齐。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwjyyf.html