Linux TCP/IP协议栈学习
(二)协议栈各部分初始化1
Daniel Wood 20110314
转载时请注明出处和作者
文章出处:http://danielwood.cublog.cn
作者:Daniel Wood
------------------------------------------------------------------------------
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_initcall,subsys_initcall,fs_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_cache和skbuff_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_header,network_header,mac_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, end,head,data。
head指针指向用来存储网络报文的存储空间的起始地址,在分配sk_buff之后,就固定不变了。这个存储空间也可以被称为数据缓冲区。
end指针指向用来存储网络报文的存储空间的结尾,和head相对应,在分配sk_buff之后也固定不变。
data指针指向的是各不同协议层的网络报文的头部,指针随着拥有sk_buff的协议层的不同而不同。
tail指针指向的是当前协议层网络报文的结尾地址,与data对应。具体可以见下图:
From:understanding linux network internals
有了上述四个指针的认识以后我们可以来看成员变量len,data_len以及truesize。len表示网络报文有效数据长度,包括协议头和数据,在不同的协议层,它的值也不同。data_len表示分片的长度。truesize是存储网络数据的存储 空间的大小,它的值比len大,以16字节对齐。
阅读(3119) | 评论(0) | 转发(0) |