kernel网络协议栈初始化

来源:互联网 发布:扳机在淘宝叫什么 编辑:程序博客网 时间:2024/05/05 22:02

kernel的网络初始化顺序:

  1. core_initcall : sock_init
  2. fs_initcall : inet_init
  3. subsys_initcall : net_dev_init
  4. device_initcall : 设备驱动初始化

网络基础系统初始化
第一步,使用core_initcall初始化宏修饰sock_init函数,这个宏指定了sock_init函数放在级别为1的代码中,也就是说它的执行时最先进的一部分,此函数只是分配一些内存空间,以及创建了一个sock_fs_type的文件系统。在do_basic_setup中调用sock_init先于internet协议注册被调用,因此基本的socket初始化必须在每一个TCP/IP成员协议能注册到socket层之前完成。

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(),其实不做什么实质性的事,只是对一些变量进行赋值。

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函数

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_cache和skbuff_fclone_cache。协议中相关的数据都在这两个缓存中创建。

sk_buff结构
数据包在应用层成为data,在TCP层成为segment,在IP层成为packet,在数据链路层成为frame。linux内核中sk_buff{}结构来存放数组,在INET socket和它以下的层次中用来存放网络接收到或需要发送的数据,因此它需要设计的有足够扩展性。
这里写图片描述

sk_buff结构:

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;};
  • next和prev,这两个域是用来连接相关的skb的(例如如果有分片,将这些分片连接在一起可以)
  • sk,指向报文所属的套接字指针
  • stamp,记录接收或者传输报文的时间戳
  • dev和input_dev,记录接收或者发送的设备
  • union u,对于一个层次,例如tcp层,可能有很多不同的协议,他们的协议头不一样,那么这个联合体就是记录这些协议头的。
    此处u就是代表传输层
  • union nh,代表网络层头
  • union mac,代表链路层头
  • dst,指向des_entry结构,记录了到达目的地的路由信息,以及其他的一些网络特征信息。
  • sp:安全路径,用于xfrm
  • cb[],保存与协议相关的控制信息,每个协议可能独立使用这些信息。
  • 重要的字段 len 和 data_len:
    len代: 表整个数据区域的长度!这里要提前解释几个定义,skb的组成是有sk_buff控制 + 线性数据 + 非线性数据
    (skb_shared_info) 组成!
    后面会具体解释是什么意思!在sk_buff这个里面没有实际的数据,这里仅仅是控制信息,数据是通过后面的data指针指向其他内
    存块的!那个内存块中是线性数据和
    非线性数据!那么len就是length(线性数据) + length(非线性数据)!
    data_len: 指的是length(非线性数据)!那么可以知道:length(线性数据) = skb->len - skb->data_len
  • mac_len,指的是mac头长度
  • csum,某时刻协议的校验和
  • priority,报文排队优先级,取决于ip中的tos域
  • local_df,允许在本地分配
  • cloned,保存当前的skb_buff是克隆的还是原始数据
  • ip_summed,是否计算ip校验和
  • nohdr,仅仅引用数据区域
  • pkt_type,报文类型,例如广播,多播,回环,本机,传出…
  • fclone,skb_buff克隆状态
  • ipvs_property,skb_buff是否属于ipvs
  • protocal,协议信息
  • nfmark,用于钩子之间通信
  • nfct_reasm,netfilter的跟踪连接重新组装指针
  • nf_bridge,保存桥接信息
  • tc_index: Traffic control index,tc_verd: traffic control verdict
  • truesize,该缓冲区分配的所有总的内存,包括:skb_buff + 所有数据大小
  • users,保存引用skb_buff的数量
  • 重要数据字段:head,data,tail,end
    head:指向分配给的线性数据内存首地址( 建立起一个观念:并不是分配这么多内存,就都能被使用作为数据存储,可能没这么多
    数据也有可能!但是也不要认为分配这么多 就足够了,也不一定(非线性数据就是例子) )
    data:指向保存数据内容的首地址!我们由head可以知道,head和data不一定就是指在同一个位置!
    tail:指向数据的结尾!
    end:指向分配的内存块的结尾! ( 由上面我们知道数据结尾 != 分配的内存块的结尾 )

这里写图片描述
sk_buff.pkt_type是指该数据包的类型,定义如下:
这里写图片描述
为了使用套接字缓冲区,内核创建了两个后备高速缓存lookaside cache,他们分别是skbuff_head_cache和skbuff_fclone_cache,协议栈中所使用到的所有sk_buff结构都是从这两个后备高速缓存中分配出来的。两者的区别在于skbuff_head_cache在创建时指定的单位内存区域的大小是sizeof(struct sk_buff),可以容纳任意数目的struct sk_buff,而skbuff_fclone_cache在创建时指定的单位内存区域大小是2*sizeof(struct sk_buff)+sizeof(atomic_t),它的最小区域单位是一对struct sk_buff和一个引用计数,这一对sk_buff是克隆的,即它们指向同一数据缓冲区,引用计数值是0,1,或2,表示这一对中有几个sk_buff已被调用。
创建一个套接字缓冲区,最常用的操作是alloc_skb,它在skbuff_head_cache中创建一个struct sk_buff,如果要在skbuff_fclone_cache中创建,可以调用__alloc_skb,通过特定参数进行。
再end后面还有一个结构体struct skb_shared_info:

struct skb_shared_info {           atomic_t        dataref;        // 对象被引用次数           unsigned short  nr_frags;       // 分页段数目,即frags数组元素个数           unsigned short  tso_size;                  unsigned short  tso_segs;           unsigned short  ufo_size;           unsigned int    ip6_frag_id;           struct sk_buff  *frag_list;    // 一般用于分段(还没有非常清楚的理解)           skb_frag_t      frags[MAX_SKB_FRAGS]; // 保存分页数据(skb->data_len=所有的数组数据长度之和)  };  

这个结构体存放分隔存储的数据片段,将数据分解为多个数据片段是为了使用分散/聚集I/O。

内存管理函数
在sk_buff{}中4个指针data、head、tail、end初始化的时候,data,head,tail都是指向申请到数据区的头部,end指向数据区的尾部。在以后的操作中,一般都是通过data和tail来获得在sk_buff中可用的数据区的开始和结尾。而head和end就表示sk_buff中存在的数据包最大可扩展的空间范围。
以下为网络协议栈的内存管理函数
这里写图片描述

0 0
原创粉丝点击