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字节对齐。