网络初始化函数调用顺序
《Linux系统启动那些事—基于Linux 3.10内核》提到系统启动时会调用一系列的初始化函数,初始化函数使用include/init.h中的宏定义,这些宏的顺序显示了初始化函数调用的顺序。即由pure_initcall函数定义的函数先于core_initcall定义的函数,依此类推。
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
网络子系统代码定义于net目录和drivers/net目录下,前一个net/目录包含偏协议栈上层的代码,而drivers/net目录下的代码则更多的偏于协议栈下层,即数据链路层和物理层(主要MAC和PHY驱动,链路层协议的其它部分由硬件实现)。千兆网卡的数据链路层和物理层包含的内容如下图所示。
图1.1 GMII OSI参考模型,摘自IEEE802.3_2012section3
在上述的两个目录下找到如下和网络初始化相关的函数:
net/目录下:
./core/net_namespace.c:436:pure_initcall(net_ns_init);
./core/sock.c:2566:core_initcall(net_inuse_init);
./core/netpoll.c:1218:core_initcall(netpoll_init);
./socket.c:2654:core_initcall(sock_init); /* early initcall */
./netlink/af_netlink.c:2940:core_initcall(netlink_proto_init);
./core/fib_rules.c:772:subsys_initcall(fib_rules_init);
./core/sock.c:2844:subsys_initcall(proto_init);
./core/neighbour.c:3036:subsys_initcall(neigh_init);
./core/dev.c:6336:subsys_initcall(net_dev_init);
./nfc/core.c:973:subsys_initcall(nfc_init);
./nfc/hci/core.c:950:subsys_initcall(nfc_hci_init);
./mac80211/main.c:1157:subsys_initcall(ieee80211_init);
./irda/irmod.c:205:subsys_initcall(irda_init);
./atm/common.c:906:subsys_initcall(atm_init);
./ieee802154/wpan-class.c:213:subsys_initcall(wpan_phy_class_init);
./wireless/core.c:1152:subsys_initcall(cfg80211_init);
./wireless/wext-core.c:366:subsys_initcall(wireless_nlevent_init);
./rfkill/core.c:1292:subsys_initcall(rfkill_init);
./sched/act_api.c:1138:subsys_initcall(tc_action_init);
./sched/sch_api.c:1831:subsys_initcall(pktsched_init);
./sched/cls_api.c:627:subsys_initcall(tc_filter_init);
./iucv/iucv.c:2122:subsys_initcall(iucv_init);
./netlink/genetlink.c:1012:subsys_initcall(genl_init);
./bluetooth/af_bluetooth.c:723:subsys_initcall(bt_init);
./ipv4/cipso_ipv4.c:2365:subsys_initcall(cipso_v4_init);
./netlabel/netlabel_kapi.c:1100:subsys_initcall(netlbl_init);
./core/sysctl_net_core.c:266:fs_initcall(sysctl_core_init);
./ipv6/ip6_offload.c:281:fs_initcall(ipv6_offload_init);
./sunrpc/sunrpc_syms.c:126:fs_initcall(init_sunrpc);
./ipv4/af_inet.c:1691:fs_initcall(ipv4_offload_init);
./ipv4/af_inet.c:1826:fs_initcall(inet_init);
./unix/af_unix.c:2454:fs_initcall(af_unix_init);
./ipv4/tcp_fastopen.c:92:late_initcall(tcp_fastopen_init);
./ipv4/tcp_cong.c:145:late_initcall(tcp_congestion_default);
./ipv4/ipconfig.c:1543:late_initcall(ip_auto_config);
drivers/net目录下:
./phy/phy_device.c:1146:subsys_initcall(phy_init);
phy的初始化见第十一章。
还有一类函数也会在系统初始化时被调用,这些函数使用module_init宏进行定义了,基于tcp/ip V4协议的module_init宏在/net/ipv4/目录下,module_init定义的相关函数如下。
//cubic算法是Linux现在采用的拥塞控制算法。
./tcp_cubic.c:488:module_init(cubictcp_register);
//TCPW是专门针对无线网环境提出的协议,针对ACK估算流量。
./tcp_westwood.c:299:module_init(tcp_westwood_register);
./xfrm4_mode_transport.c:77:module_init(xfrm4_transport_init);
// tunnel即隧道,被用于在公网内传输私网数据,也就是VPN。实现类似于数据结构中的栈,把数据报文封装在新的报文中,通过第三方协议(比如IP协议)传输到对端,对端进行解封,重新路由。
./tunnel4.c:190:module_init(tunnel4_init);
./esp4.c:739:module_init(esp4_init);
./ipcomp.c:192:module_init(ipcomp4_init);
./tcp_veno.c:229:module_init(tcp_veno_register);
./tcp_bic.c:237:module_init(bictcp_register);
./xfrm4_tunnel.c:114:module_init(ipip_init);
./gre.c:247:module_init(gre_init);
./tcp_vegas.c:334:module_init(tcp_vegas_register);
./xfrm4_mode_beet.c:153:module_init(xfrm4_beet_init);
./ah4.c:553:module_init(ah4_init);
./tcp_yeah.c:255:module_init(tcp_yeah_register);
./tcp_illinois.c:352:module_init(tcp_illinois_register);
./tcp_hybla.c:187:module_init(hybla_register);
./netfilter.c:206:module_init(ipv4_netfilter_init);
./inet_diag.c:1199:module_init(inet_diag_init);
./tcp_scalable.c:57:module_init(tcp_scalable_register);
./ip_vti.c:899:module_init(vti_init);
./tcp_diag.c:66:module_init(tcp_diag_init);
./tcp_lp.c:339:module_init(tcp_lp_register);
./tcp_htcp.c:310:module_init(htcp_register);
./udp_diag.c:212:module_init(udp_diag_init);
./ip_gre.c:1018:module_init(ipgre_init);
./xfrm4_mode_tunnel.c:190:module_init(xfrm4_mode_tunnel_init);
./tcp_probe.c:252:module_init(tcpprobe_init);
./tcp_highspeed.c:182:module_init(hstcp_register);
netfilter是基于包过滤防火墙相关内容。其内容见第十章。
./netfilter/nf_nat_pptp.c:310:module_init(nf_nat_helper_pptp_init);
./netfilter/ipt_ah.c:90:module_init(ah_mt_init);
./netfilter/iptable_raw.c:88:module_init(iptable_raw_init);
./netfilter/nf_conntrack_l3proto_ipv4.c:552:module_init(nf_conntrack_l3proto_ipv4_init);
./netfilter/ipt_CLUSTERIP.c:748:module_init(clusterip_tg_init);
./netfilter/iptable_security.c:109:module_init(iptable_security_init);
./netfilter/arp_tables.c:1913:module_init(arp_tables_init);
./netfilter/ipt_ULOG.c:496:module_init(ulog_tg_init);
./netfilter/ipt_MASQUERADE.c:177:module_init(masquerade_tg_init);
./netfilter/iptable_mangle.c:147:module_init(iptable_mangle_init);
./netfilter/ipt_ECN.c:137:module_init(ecn_tg_init);
./netfilter/ip_tables.c:2269:module_init(ip_tables_init);
./netfilter/iptable_nat.c:333:module_init(iptable_nat_init);
./netfilter/nf_nat_proto_gre.c:142:module_init(nf_nat_proto_gre_init);
./netfilter/ipt_REJECT.c:212:module_init(reject_tg_init);
./netfilter/nf_nat_snmp_basic.c:1311:module_init(nf_nat_snmp_basic_init);
./netfilter/arpt_mangle.c:90:module_init(arpt_mangle_init);
./netfilter/iptable_filter.c:109:module_init(iptable_filter_init);
./netfilter/nf_defrag_ipv4.c:125:module_init(nf_defrag_init);
./netfilter/nf_nat_l3proto_ipv4.c:280:module_init(nf_nat_l3proto_ipv4_init);
./netfilter/ipt_rpfilter.c:146:module_init(rpfilter_mt_init);
./netfilter/nf_nat_h323.c:622:module_init(init);
./netfilter/arptable_filter.c:90:module_init(arptable_filter_init);
./ipip.c:481:module_init(ipip_init);
在net/xfrm目录下的各文件大致功能如下,该目录内核主要实现IPsec协议,和TCP、IP协议位置是等同的一个协议。
xfrm_state.c: xfrm状态管理
xfrm_policy.c: xfrm策略管理
xfrm_algo.c: 算法管理
xfrm_hash.c: HASH计算函数
xfrm_input.c: 安全路径(sec_path)处理,用于进入的ipsec包
xfrm_user.c: netlink接口的SA和SP管理
在net/ipv4目录下的和ipsec相关各文件大致功能说明如下:
ah4.c: IPV4的AH协议处理
esp4.c: IPV4的ESP协议处理
ipcomp.c: IP压缩协议处理
xfrm4_input: 接收的IPV4的IPSEC包处理
xfrm4_output: 发出的IPV4的IPSEC包处理
xfrm4_state: IPV4的SA处理
xfrm4_policy: IPV4的策略处理
xfrm4_tunnel: IPV4的通道处理
xfrm4_mode_transport: 传输模式
xfrm4_mode_tunnel: 通道模式
xfrm4_mode_beet: BEET模式
1.2 调用函数浅析
1.2.1 Core目录下
net_ns_init是网络命名空间初始化函数,Linux目前实现了六种命名空间,分别是mount命名空间,UTS命名空间,IPC命名空间,PID命名空间,网络命名空间和user命名空间,这些命名空间目前最主要的应用是在虚拟化技术上,关于命名空间入门文章,可以参看《linuxnamespace-之使用》一文,这里初始化只完成了root用户命名空间的初始化。进程的 struct nsproxy *nsproxy;字段指向命名空间。
net_inuse_init注册一个网络命名空间子系统,将该子系统挂载在first_device表示的链表上,以后在创建新的命名空间时也会挂接到该链表上,并将该网络命名空间子系统和net_inuse_ops包含的init和exit函数进行绑定,后面再创建或者销毁网络命名空间是相应的调用这里的init和exit函数。
netpoll_init初始化内核下的sk_buff(skb_pool),netpoll是一个框架和一些接口,其和网络的关系非常类似于VFS和文件系统的关系,主要用于网络控制台net console和内核远程调试KGDBoE。
sock_init完成
1、 网络sysctl接口注册,sysctl是将内核参数导出到proc目录下,并且数可以在用户空间修改,Linux服务器性能调优时对该sysctl参数设置的比较多。
2、 该函数还会初始化两个slab缓存,它们是skbuff_head_cache和skbuff_fclone_cache,使用slab缓存而不是kmalloc动态申请内存的原因是速度,Linux网络通信使用的是socket编程,用户空间的数据以及网络协议栈的各种头信息在数据在协议栈中传递时使用上述两种cache,这两种cache的区别体现在sk_buff内的数据是否需要改变,如果需要改变,那么其它使用该sk_buff的进程将可能看到不一致的sk_buff,skbuff_fclone_cache就是针对这种场景而生的。
3、 套接字文件系统初始化,对于网络数据和其它类型的数据一样都是通过文件系统方式存取的,将sock_fs_type结构体表示的类型文件系统注册到file_systems文件系统全局链表上。注册成功后会挂载该文件系统。
4、 注册netfilter的hook函数,netfilter和iptables被称为Linux防火墙,iptables是用户空间配置网络防火墙规则的接口,这些用户空间的规则会转换为内核空间netfilter的规则表中的规则,netfilter基于包过滤原则,涉及规则、表以及hook检测点三个部分。
netlink_proto_init:初始化netlink机制,该机制用于应用程序和内核通信之用。该函数将netlink协议初始化到网络协议链表中,然后为netlink机制分配32个netlink_table表结构,并且初始化该结构的hash表头。然后调用netlink_add_usersock_entry将上面32个netlink_table表中的一个初始化为NETLINK_USERSOCK类型的表,该netlink表用户和用户空间通信,此外还会调用sock_register注册netlink协议族操作集,该操作集中的netlink_create用于初始化一个sock,最后改函数会在proc文件系统下注册netlink文件。netlink的用户空间编程可以参考generic_netlink_howto。
fib_rules_init路由初始化,路由在内核中称为FIB(forward information base),该函数除了对每一个网络命名空间初始化rules_ops和rules_mod_lock字段,该函数首先利用上面的netlink机制注册三个用户空间和内核路由子系统通信的辅助函数,它们是新路由规则的添加,路由规则的删除和路由规则导出。然后使用通知链机制(notification chain)注册一个有关路由的回调函数,当网络设备注册和注销时,该网络设备对应的路由项相应的会需要相应的添加和删除操作,这个功能就是这里注册的回调函数完成的。proto_init,在proc目录下为每一个网络命名空间创建protocols目录。
neigh_init:邻居协议初始化。
net_dev_init函数初始化DEV模块,在系统启动阶段调用该函数遍历设备列表并且过滤掉初始化失败的设备。
在/proc/net目录下创建ptype,softnet_stat,dev;这三个文件是全局性的,反映了系统网络情况,dev是针对设备的统计,有几张网络对应几个eth端口,外加lo(回环),其反映的是系统接收收数据包的情况,包括,接收到的总字节数、包数量、错误数等;ptype是按照包的类型统计的,ipv4、arp、ipv6等。内核打印ptype信息的函数是:
static int ptype_seq_show(struct seq_file *seq, void *v)
{
struct packet_type *pt = v;
if (v == SEQ_START_TOKEN)
seq_puts(seq, "Type Device Function\n");
else if (pt->dev == NULL || dev_net(pt->dev) == seq_file_net(seq)) {
if (pt->type == htons(ETH_P_ALL))
seq_puts(seq, "ALL ");
else
seq_printf(seq, "%04x", ntohs(pt->type));
seq_printf(seq, " %-8s %pf\n",
pt->dev ? pt->dev->name : "", pt->func);
}
return 0;
}
softnet_stat则是区分CPU个数的,几个就对应几行,每一行是一颗CPU的统计数据。其打印这些信息的函数是:
static int softnet_seq_show(struct seq_file *seq, void *v)
{
struct softnet_data *sd = v;
seq_printf(seq, "%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
sd->processed, sd->dropped, sd->time_squeeze, 0,
0, 0, 0, 0, /* was fastroute */
sd->cpu_collision, sd->received_rps);
return 0;
}
该函数接下来是ptype_base和ptype_all链表的初始化,这些链表用于组织协议处理函数,见图1.2,dev_add_pack函数会将协议添加到该链表上,哈希的方法是去主机序的低四个比特作为hash的键值,这些协议类型如下:
* 0800 IP
* 8100 802.1Q VLAN
* 0001 802.3
* 0002 AX.25
* 0004 802.2
* 8035 RARP
* 0005 SNAP
* 0805 X.25
* 0806 ARP
* 8137 IPX
* 0009 Localtalk
* 86DD IPv6
可以看到,冲突的只有RARP/RARP/X.25,查找冲突时会遍历该链表。
offload_base是和gso/gro有关的队列,将分段操作推迟到网卡硬件完成。见十五章。
调用netdev_init函数对网络命名空间中的每一个网络struct net中的dev_name_head和dev_index_head链表初始化。一个网络子命名空间可能使用多个网卡,这里的name和index就是用来跟踪这些网卡信息的,回环也被作为一个网卡来看待。
接下来会完成接收队列的初始化,这个初始化是针对per-CPU类型的变量softnet_data,softnet_stat的统计信息源于此。
dev_boot_phase赋值成零,这个变量存在没有什么意义了,初始化完成就将其设置成0,如果下一次再次进入net_dev_init()函数,可以断定出错了,这也是该函数开始处BUG_ON(!dev_boot_phase)的意义所在。
注册回环设备,回环设备在调试网络协议栈的正确性还是挺有帮助的,另外还注册了一个网络设备被remove了的操作函数集。
然后,注册了两个软中断服务函数,它们分别是数据包发送和数据包接收服务函数:
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
最后注册了两个通知链,一个用于CPU的热插拔事件,一个用于网卡注销或者down的事件处理,主要工作就是将该网卡对应的路由项禁掉:
hotcpu_notifier(dev_cpu_callback,0); //注册CPU状态变化时的回调函数。
dst_init();注册一个通知函数,当网络设备或者端口状态变化时会回调dst_dev_event函数,该函数根据端口可用与否,对缓存的路由项进行管理。
tc_action_init()注册三个netlink函数,
tc_filter_init
用于流量控制,
cipso_v4_init
netlbl_init:早期Linux关注于本地数据安全性,并不太关注网络上数据通信的安全。netlabel增加了内核对数据包打标签的功能。其采用CIPSO(Common IP Security Option)标签方法。CIPSO是系统间协议,包括一系列描述发送数据包进程的安全等级或者内容。CIPSO用户定义一个DOI(domain of interpretation),DOI其解释这些标签的意义,这样就可以让通信的两端确定对方的进程是否有权限进行通信。DOI和标签被放在每个IP包的可选字段。Netlabel的作用是将CIPSO的信息放在发送出去的数据包中,并且检查收到的数据包的标间。其使用Linux Security Module(LSM)钩子函数实现加标签和标签检查。Netlabel的管理通过netlink套接字,也有一些用户空间的配置工具http://netlabel.sourceforge.net/,netlbl_init两个重要的工作一个是使用netlbl_domhsh_init初始化DOI,一个是netlbl_netlink_init创建netlink套接字初始化。
sysctl_core_init:调用__register_sysctl_table在/proc/sys目录下注册controltable的叶子,即/proc/sys/net/core目录,目录的内容是net_core_table决定的,而第一个参数init_net决定了所注册的网络空间是初始网络空间。core目录下的内容是一些有关系统性能的参数,如wmem_max、rmem_max等,根据使用场景和服务器硬件资源的不同,最优参数也不一样,这些参数可用来系统性能调优,而sysctl提供了一个方便的方法,使应用程序空间用户能够方便的修改内核一些参数。
static __init int sysctl_core_init(void)
{
register_net_sysctl(&init_net, "net/core", net_core_table);
return register_pernet_subsys(&sysctl_core_ops);
}
ipv6_offload_init/ipv4_offload_init用来注册UDP和TCP协议下的分段操作,用于将分段操作提交给网卡完成,分段操作推迟执行能够减少网络协议栈上的开销。TSO是针对tcp协议的,即使没有网卡的支持,推迟分片操作总是能减小系统开销。scatter/gatherIO。GSO是generic segment offload简写,其在MTU(maximumTransmit unit)远远小于64K时才更加有效。
static int __init ipv4_offload_init(void)
{
if (inet_add_offload(&udp_offload, IPPROTO_UDP) < 0)
pr_crit("%s: Cannot add UDP protocol offload\n", __func__);
if (inet_add_offload(&tcp_offload, IPPROTO_TCP) < 0)
pr_crit("%s: Cannot add TCP protocol offlaod\n", __func__);
dev_add_offload(&ip_packet_offload);
return 0;
}
inet_add_offload用于将协议对应的回调函数添加到inet_offloads数组中,cmpxchg的意义是将第一个和第二个参数比较,如果相等就把第三个参数赋给第一个,如果不相等,返回第一个参数的内容。在更新nexthop路由缓存项时也会调用cmpxchg以确定是否需要更新缓存项,这个函数这里的意义就是根据协议号,将回调函数添加到net_offloads上,如果该协议号上已经有回调函数则什么也不做。
int inet_add_offload(const struct net_offload *prot, unsigned char protocol)
{
return !cmpxchg((const struct net_offload **)&inet_offloads[protocol],
NULL, prot) ? 0 : -1;
}
ip_packet_offload是gso方式下的回调函数集,dev_add_offload用于注册offload处理函数,其实就是将回调函数集添加到内核链表offload_base上。
static struct packet_offload ip_packet_offload __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.callbacks = {
.gso_send_check = inet_gso_send_check,
.gso_segment = inet_gso_segment,
.gro_receive = inet_gro_receive,
.gro_complete = inet_gro_complete,
},
};
tcp_fastopen_init:这里的fast open是针对TCP建立连接的三次握手而言的,其主要特性是在利用握手是的SYN报文来传输应用数据,这样客户端和服务器之间通信时可以减少一次往返时间。由此可见这是一种非标准的方式,但是其确确实实提高了用户体验,正逐渐流行起来。其详细内容可以参看rfc7413标准。
tcp_congestion_default:设置默认的tcp拥塞控制算法,目前Linux使用的是一个称之为三次方的cubic算法。
static int __init tcp_congestion_default(void)
{
return tcp_set_default_congestion_control(CONFIG_DEFAULT_TCP_CONG);
}
1.2.2drivers/net目录下
phy_init是初始化链路层芯片用的,PHY就是physical的简称,其作用有两个,一个是mdio总线的初始化,mdio总线是802.3协议规定的总线,所有PHY设备必须提供该接口,该接口用于PHY工作状态的设置,比如速率、双工、link以及自协商等。另外一个就是注册PHY设备的驱动,由于PHY在802.3中的规定很详细,其最长用的前十五个寄存器PHY规范已经定好用途了,所以这里注册了一个通用的PHY驱动,称为genphy_driver,其包括对PHY设置,状态获取等操作,操作的最终落实是通过mdio总线完成的。关于PHY专门有一章,之所以专门设置一章是因为,在Linux网络驱动这块,分为两大类一类是协议栈,这类通常关注OSI七层模型中的IP层级以上,另一类是链路层及以下,就是MAC层和PHY层,嵌入式底层驱动中又常常和PHY或者switch打交道,所以专门对PHY的方方面面给出了一章的篇幅。
inet_init:这个函数的工作是非常重要的,后面几章关于TCP/IP收发的章节内容就依赖于这里inet_init构建的协议栈基本框架。所以个部分单独作为一个小节来讨论了。
1.3 inet_init
inet_init函数初始化internet 协议族的协议栈。
net/ipv4/af_inet.c
1696 static int __init inet_init(void)
1697 {
1698 struct inet_protosw *q; //ip协议注册套接字接口之用
1699 struct list_head *r;
1700 int rc = -EINVAL;
1701
//判断sk_buff的cb成员是否小于inet_skb_parm的size,如果是则BUG,cb的定义是charcb[48] __aligned(8);该字段被称
//为控制块,协议栈的每一层都可以使用该字段,在IP分片时就会使用该字段。inet_skb_parm是定义于include/net/ip.h文件
//的结构体,该结构体flags成员用于标记packet的状态,frag_max_size记录的是数据包的不分片的最大size,如果该值大
//于MTU,maximum Transmit Unit,那么对于发送出去的数据包是会进行分片操作的。
1702 BUILD_BUG_ON(sizeof(struct inet_skb_parm) > FIELD_SIZEOF(struct sk_buff, cb));
1703
//sysctl_local_reserved_ports 是ip_local_reserved_ports,ip_local_reserved_ports是在/proc/sys/net/ipv4/ip_local_reserved_ports,它是控制表的名字,其对应的内容是sysctl_local_reserved_ports 的内容,该文件作用是用来预留网络端口,就是为使用固定端口的第三方应用程序预留。ip_local_reserved_ports是针对系统管理员(用户空间间),而sysctl_local_reserved_ports是针对内核开发人员定义的变量。它们本质上值的是同一个意思,sysctl的很多接口,它们用户空间和内核空间的命名方法和这里的很相似。
1704 sysctl_local_reserved_ports = kzalloc(65536 / 8, GFP_KERNEL);
1705 if (!sysctl_local_reserved_ports)
1706 goto out;
1707
//下面是协议的注册,tcp、udp、raw和ping。它们会组成一张和其它部分有着千丝万缕的大大的表。
1708 rc = proto_register(&tcp_prot, 1);
1709 if (rc)
1710 goto out_free_reserved_ports;
1711
1712 rc = proto_register(&udp_prot, 1);
1713 if (rc)
1714 goto out_unregister_tcp_proto;
1715
1716 rc = proto_register(&raw_prot, 1);
1717 if (rc)
1718 goto out_unregister_udp_proto;
1719
1720 rc = proto_register(&ping_prot, 1);
1721 if (rc)
1722 goto out_unregister_raw_proto;
1723
1724 /*
1725 * Tell SOCKET that we are alive...
1726 */
1727
1728 (void)sock_register(&inet_family_ops);
1729
1730 #ifdef CONFIG_SYSCTL
1731 ip_static_sysctl_init();
1732 #endif
1733
1734 tcp_prot.sysctl_mem = init_net.ipv4.sysctl_tcp_mem;
1735
1736 /*
1737 * Add all the base protocols.
1738 */
1739
1740 if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
1741 pr_crit("%s: Cannot add ICMP protocol\n", __func__);
1742 if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
1743 pr_crit("%s: Cannot add UDP protocol\n", __func__);
1744 if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
1745 pr_crit("%s: Cannot add TCP protocol\n", __func__);
1746 #ifdef CONFIG_IP_MULTICAST
1747 if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
1748 pr_crit("%s: Cannot add IGMP protocol\n", __func__);
1749 #endif
1750
1751 /* Register the socket-side information for inet_create. */
1752 for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
1753 INIT_LIST_HEAD(r);
1754
1755 for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
1756 inet_register_protosw(q);
1757
1758 /*
1759 * Set the ARP module up
1760 */
1761
1762 arp_init();
1763
1764 /*
1765 * Set the IP module up
1766 */
1767
1768 ip_init();
1769
1770 tcp_v4_init();
1771
1772 /* Setup TCP slab cache for open requests. */
1773 tcp_init();
1774
1775 /* Setup UDP memory threshold */
1776 udp_init();
1777
1778 /* Add UDP-Lite (RFC 3828) */
1779 udplite4_register();
1780
1781 ping_init();
1782
1783 /*
1784 * Set the ICMP layer up
1785 */
1786
1787 if (icmp_init() < 0)
1788 panic("Failed to create the ICMP control socket.\n");
1789
1790 /*
1791 * Initialise the multicast router
1792 */
1793 #if defined(CONFIG_IP_MROUTE)
1794 if (ip_mr_init())
1795 pr_crit("%s: Cannot init ipv4 mroute\n", __func__);
1796 #endif
1797 /*
1798 * Initialise per-cpu ipv4 mibs
1799 */
1800
1801 if (init_ipv4_mibs())
1802 pr_crit("%s: Cannot init ipv4 mibs\n", __func__);
1803
1804 ipv4_proc_init();
1805
1806 ipfrag_init();
1807
1808 dev_add_pack(&ip_packet_type);
1809
1810 rc = 0;
1811 out:
1812 return rc;
1813 out_unregister_raw_proto:
1814 proto_unregister(&raw_prot);
1815 out_unregister_udp_proto:
1816 proto_unregister(&udp_prot);
1817 out_unregister_tcp_proto:
1818 proto_unregister(&tcp_prot);
1819 out_free_reserved_ports:
1820 kfree(sysctl_local_reserved_ports);
1821 goto out;
1822 }
1728 行用于,注册inet协议族。
static const struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
sock_register获得net_family_lock自旋锁在下面局部全局数组net_families中添加PF_INET对应的成员。
static const struct net_proto_family__rcu *net_families[NPROTO] __read_mostly;
1731 ip_static_sysctl_init(),其作用是创建/proc/sys/net/ipv4/route文件,从该文件的名称可以知道是和路由相关的,该文件包含路由参数包括垃圾回收的时间间隔等,系统管理员可以通过这里对系统性能进行调优。inet_init函数的初衷是初始化inet协议,但把路由sysctl参数接口初始化放在这里显得有点不伦不类,至少其它的/proc/sys/net/ipv4/下的sysctl接口的初始化方法和这里的不一样。
1734 行内核空间称为sysctl_mem ,而用户空间的对应的是tcp_mem,这个变量是个有三个元素的数组,其三个值是tcp占用内存和系统压力关系的描述,对于server而言,该值相关是需要关注的,mysql有一个参数就是用于限定MySQL连接数的,其意义就是在限制内存上。
1740-1749行是基础协议的初始化,它们都是调用inet_add_protocol函数将对应的协议添加到inet_protos的hash表上。tcp_protocol 结构体,这里先留个印象,后面还会在见到的。
static const struct net_protocol tcp_protocol = {
.early_demux = tcp_v4_early_demux,
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
.no_policy = 1,
.netns_ok = 1,
};
1752行,为inet_create注册套接字侧信息,在用户空间编程时创建inet协议族套接字到内核里就会调用该函数完成实际的创建工作。不同的协议族套接字创建函数不一样。1756行的函数就会使用到inetsw这个链表。可以先看一下图1.2,建立一个它们之间组织架构的关系印象。
1762行,arp协议初始化。根据IP地址获取物理地址的协议,在使用ping命令也许你会注意到一个现象,有时使用ping命令时,ping的第一次输出延迟是后续延迟的几十倍,后续再ping,第一次延迟和后续的延迟相差无几,第一次延迟较大是知道IP但是不知道MAC地址原因,所以先发送了一个地址解析请求,获得目标ip对应的物理地址之后,设备才是真正发送ICMPping包。
1768 ip路由和peer子系统初始化,因为ip是无状态连接,内核为了提升性能,路由子系统会使用peer保存一些信息,peer子系统使用avi树保存这些信息。
1770 tcp_v4_init初始化tcp_hashinfo结构体,该结构用于根据套接字状态,其有三个hash链表用于该套接字状态管理:
ehash记录已经建立连接的套接字,
bhash用于记录正在绑定的套接字,
listening_hash记录正在侦听的套接字;
它的一个使用实例是tcp接收函数tcp_v4_rcv会依据tcp_hashinfo查找对应的套接字,查找的那行代码如下:
sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
此外,还会注册tcp下的skb的管理结构体tcp_sk_ops,主要设置ipv4.sysctl_tcp_ecn字段,ecn是 (explicit congestion notification)用于拥塞功能,使用到IP首部的89bit的TOS字段。
1773行tcp_init,为打开的tcp请求建立slab缓存。这里缓存是上面tcp_hashinfo的三个成员ehash、bhash、listening_hash建立缓存,套接字的操作比较频繁,使用cache能够提高效率。初始化tcp的sysctl一些参数值,另外使用如下函数:
tcp_metrics_init();
tcp_register_congestion_control();
tcp_metrics_init();初始化tcp metrics接口以及netlink接口,tcp metrics的目标是tcp的吞吐量*。tcp metrics有三个基本的规则[1]:
传输时间比: 实际传输时间/理想传输时间,实际传输时间通过传递数据包的时间戳获得,理想传输时间由最大tcp吞吐量获得。
tcp_register_congestion_control:根据函数命名知道这是在注册tcp拥塞控制接口,前面提到过内核现在使用的tcp拥塞控制算法“cubic”,这里注册的使拥塞控制处理函数,包括:
l tcp_reno_ssthresh,慢启动函数,设置慢启动阈值,初始值为拥塞窗口的一半,但最小值是2.
l tcp_reno_cong_avoid 拥塞控制函数,包括慢启动和拥塞避免两个部分。
l tcp_reno_min_cwnd 拥塞窗长最小化,实际上是就是对当前套接字对应的慢启动阈值减半。
tcp_tasklet_init:初始化一个tasklet,内核把这个tasklet称之为TSQ(TCP SMALL QUEUES),其作用是保持每个cpu的tcp发送队列里的skb尽量的少,以减少RTT和bufferbloat。
udp_init:udp协议的初始化,初始化udp的控制表的hash成员,其有两个hash表,第一个表用于本地端口套接字的hash,第二个表用于本地端口、本地地址套接字的hash;此外,和tcp一样初始化了若干sysctl控制接口。
udplite4_register:轻量级用户数据包协议,rfc3828协议规范。该功能从2.6.20开始支持。其将对数据包的校验以及校验推迟到用户决定,其对于网络不是很好的视频监控应用场景轻量级udp比对载荷进行完成校验的UDP协议具有一定的优势。
1781行ping_init:初始化ping哈希表。
1787 行icmp_init,初始化icmp协议,即初始化struct net关于icmp协议的相关成员,这些成员包括:
l icmp套接字成员icmp_sk。
l 发送缓存sk_sndbuf为2*64K, sk_buff和skb_shared_info大小均为64K。
l 标记sk_flags设置成SOCK_USE_WRITE_QUEUE,这就意味着sock_wfree函数会调用sk->sk_write_space 方法来完成。
l 将pmtudisc(路径最大传输单元)规则设置成不分片。
l 若干sysctl接口。
1794 ip_mr_init多播路由初始化。
1801 init_ipv4_mibs,该函数调用参数ipv4_mib_ops的init函数ipv4_mib_init_netipv4进行MIB(Management Information Bases)初始化,该函数调用的大多数函数都有一个snmp前缀,snmp(Simple Network Management Protocol)源于IETI(Internet Engineering Task Force)规范,SNMP主要用于管理和监控网络设备的状态,通过这些状态可以知道这些网络设备的状态如(接口状态、IP地址、流量等),双十一时网络流量的冲击导致网卡爆掉的可能性会比平时高些,就算不是双十一,网卡也是可能变得有问题的,如果不监控网络设备的状态,很可能导致库存中心和各地缓存不一致性,导致秒杀超卖等。当然电商公司有专门的图像监控管理工具。这些网卡等的信息都存储在MIB里,获得每台服务器的MIB信息(SNMP协议)。
1804 ipv4_proc_init在/proc/net目录下创建若干文件。raw,tcp,udp,icmp,sockstat,netstat和snmp文件,这些文件统计了网络上的信息,netstat命令输出的信息通过解析/proc/net/netstat文件获得。
1806 ipfrag_init,ip分片操作初始化,在以太网上1500是定义的最大数据包长,通常不会产生分片,产生分片多半源于PMTU(路径最大MTU),不能满足该值时,可能会产生分片操作,新的技术会不在ip层分片,而将分片推迟到网卡才实际进行,见十五章。
ip4_frags_ctl_register在/proc/sys/net/ipv4目录下创建两个ip分片参数,ip4_frags_ctl_register和ipfrag_max_dist;分片操作的初始化如下:
void __init ipfrag_init(void)
{
ip4_frags_ctl_register();
register_pernet_subsys(&ip4_frags_ops);
ip4_frags.hashfn = ip4_hashfn;
ip4_frags.constructor = ip4_frag_init;
ip4_frags.destructor = ip4_frag_free;
ip4_frags.skb_free = NULL;
ip4_frags.qsize = sizeof(struct ipq);
ip4_frags.match = ip4_frag_match;
ip4_frags.frag_expire = ip_expire;
ip4_frags.secret_interval = 10 * 60 * HZ;
inet_frags_init(&ip4_frags);
}
1808:dev_add_pack注册Ip包处理函数到网络协议栈,其参数会被链接到内核列表上。
图1.2 INET协议栈初始化。
1.4 总结
本章根据linux内核下网络相关代码的初始化顺序浏览了各初始化函数的作用和意义,最后将inet_init函数完成inet协议族的注册过程进行了细致的分析,并以一张图给出了协议族、协议类型和协议之间的初始化以及它们和套接字的关系,但是这里并没有深入协议的各个字段去看各个字段的作用和意义。这些字段的作用和意义在后面的章节会看到。