第一章 网络子系统初始化--基于Linux3.10

2023-10-28

下载地址《http://download.csdn.net/detail/shichaog/8620701》

网络初始化函数调用顺序

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协议族的注册过程进行了细致的分析,并以一张图给出了协议族、协议类型和协议之间的初始化以及它们和套接字的关系,但是这里并没有深入协议的各个字段去看各个字段的作用和意义。这些字段的作用和意义在后面的章节会看到。













本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

第一章 网络子系统初始化--基于Linux3.10 的相关文章

随机推荐

  • 高斯混合模型(GMM)先验的推断

    GMM先验的优化方程 假设图像降质模型为 Y A X N Y AX N Y AX N 我们希望恢复
  • 腾讯云SA3服务器AMD处理器CPU网络带宽性能详解

    腾讯云AMD服务器SA3实例CPU采用2 55GHz主频的AMD EPYCTM Milan处理器 睿频3 5GHz 搭载最新一代八通道DDR4 内存计算性能稳定 默认网络优化 最高内网收发能力达1900万pps 最高内网带宽可支持100Gb
  • 二极管常见分类及使用

    1 肖特基二极管 1 1概念 肖特基二极管 SBD 不是利用P型半导体与N型半导体接触形成PN结原理制作的 而是利用金属与半导体接触形成的金属 半导体结 肖特基势垒 原理制作的 因此 SBD也称为金属 半导体 接触 二极管或表面势垒二极管
  • 获取上个月的起止时间

    function 日期初始化 alert getStartDate alert getEndDate 获取开始时间 function getStartDate var date new Date var year date getFullY
  • Transform 基础知识

    Transform 变换 是场景中最常打交道的类 用于控制物体的位移 旋转 缩放等功能 Transform Class inherits from Component IEnumerable Position rotation and sc
  • HJ41 称砝码

    题目 HJ41 称砝码 题解 import java util 注意类名必须为 Main 不要有任何 package xxx 信息 public class Main public static void main String args
  • RBAC详解

    RBAC详解 1 RBAC模型的工作原理 2 RBAC模型的实现 3 总结 RBAC模型是一种基于角色的访问控制模型 它定义了一些规则和机制来控制用户对系统资源的访问 在本文中 我们将详细讨论RBAC模型的工作原理 并使用一个数据库示例来说
  • 剑指Offer - 面试题49:丑数

    题目 我们把只包含因子2 3 5的数称为丑数 Ugly Number 求按照从小到大的顺序的第1500个丑数 例如 6 8都是丑数 但14不是 因为它包含因子7 习惯上我们把1当作第一个丑数 分析 暴力法 从1开始每个数字都判断 若是丑数
  • 代码实现 —— 基于 STM32 的可见光通信系统课程设计

    目前课设已完成 2m距离 传输10000个连续数字 每个数字两字节大小 即总共20000个字节160000bit 用时7s 大约2 3万bit s 即22 4kB s 误码率为0 视频演示链接 另外 自己写了一个基于QT的串口上位机 结合U
  • 前端面试题汇总(vue+html基础)最新最全

    一 HTML基础部分 1 什么是盒子模型 重要 在网页中 一个元素占有空间的大小由几个部分构成 其中包括元素的内容 content 元素的内边距 padding 元素的边框 border 元素的外边距 margin 四个部分 这四个部分占有
  • FDbus

    文章目录 介绍 背景 特点 FDBus 中间件模型 FDBus 寻址和组网 Server地址 Server命名和地址分配 name server使用如下规则分配server地址 多主机组网 host server的工作原理 client 与
  • 时序分解

    时序分解 Matlab实现CEEMD互补集合经验模态分解时间序列信号分解 目录 时序分解 Matlab实现CEEMD互补集合经验模态分解时间序列信号分解 效果一览 基本介绍 程序设计 参考资料 效果一览 基本介绍 Matlab实现CEEMD
  • Spring源码之事件监听机制(下)

    文章目录 前言 一 手写事件监听机制框架 1 准备 2 事件监听接口 3 事件管理器 4 事件发布器 5 需求 6 编码 二 观察者模式 1 概述 2 UML图 3 Coding验证 小结 前言 这篇文章接的是上篇文章Spring源码之事件
  • java 抓取网页_JAVA使用爬虫抓取网站网页内容的方法

    本文实例讲述了JAVA使用爬虫抓取网站网页内容的方法 分享给大家供大家参考 具体如下 最近在用JAVA研究下爬网技术 呵呵 入了个门 把自己的心得和大家分享下 以下提供二种方法 一种是用apache提供的包 另一种是用JAVA自带的 代码如
  • 函数齐次性

    比如一个系统 输入为x 其响应为f x 当输入为ax 其响应为af x 即 f ax af x 则称系统具有一次齐次性 其中a为任意常数 一般地 在数学里面 如果一个函数的自变量乘以一个系数 那么这个函数将乘以这个系数的k次方 我们称这个函
  • Win10聚焦锁屏壁纸保存

    前言 Win10聚焦锁屏每天都会推荐新的壁纸 其中有些质量超高的优秀壁纸 用户自然想下载保存下来 下文介绍如何保存 若用户仅想保存当天的聚焦锁屏壁纸 则推荐方法1 若用户想保存以前的聚焦锁屏壁纸 则推荐方法2 方法1 从微软商店下载软件 注
  • 51单片机OLED收银电子秤称重计价清零去皮金额累计HX711

    实践制作DIY GC0061 收银电子秤称重计价清零去皮金额累计 一 功能说明 基于51单片机设计 收银电子秤称重计价清零去皮金额累计 二 功能介绍 STC89C52单片机 AT89C51 52 OLED HX711 5Kg电子秤 4 4矩
  • 解决ubuntu进行远程连接时出现密码认证失败的问题

    问题描述 1 当我们将mobaxterm 新建会话时 如果用户名设置为root超级用户 我们会发现 root用户登陆不进去 他会跳出来一个窗口 说是ssh服务器拒绝了密码 2 这个问题就困扰到我了 找了很多帖子 都说要改 etc ssh s
  • ps 和 kill 命令详解

    1 作用kill命令用来中止一个进程 2 格式kill s signal p a pid kill l signal 3 参数 s 指定发送的信号 p 模拟发送信号 l 指定信号的名称列表 pid 要中止进程的ID号 Signal 表示信号
  • 第一章 网络子系统初始化--基于Linux3.10

    下载地址 http download csdn net detail shichaog 8620701 网络初始化函数调用顺序 Linux系统启动那些事 基于Linux 3 10内核 提到系统启动时会调用一系列的初始化函数 初始化函数使用i