您的位置:首页 > 理论基础 > 计算机网络

kernel网络协议栈初始化

2016-11-07 22:02 399 查看
kernel的网络初始化顺序:

core_initcall : sock_init

fs_initcall : inet_init

subsys_initcall : net_dev_init

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中存在的数据包最大可扩展的空间范围。

以下为网络协议栈的内存管理函数

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  kernel 网络协议