TCP/IP协议栈初始化
程序员文章站
2022-04-24 10:04:21
这已经是第六篇了。但协议栈的初始化还没有说完。不得不承认协议栈还是很复杂的。越是牛B的东西,就越复杂。就像一门手艺一样,当你可以做到别人都不能达到的复杂度的时候,你就是大师了。还有人说,想要精...
这已经是第六篇了。但协议栈的初始化还没有说完。不得不承认协议栈还是很复杂的。越是牛B的东西,就越复杂。就像一门手艺一样,当你可以做到别人都不能达到的复杂度的时候,你就是大师了。还有人说,想要精通一样技术,你必须重复它10万次以上。子曰:“温故而知新”,代码看多了,就能明白其中的奥秘了。当然一些实践还是必不可少的。这个系列一开始,我就说,协议栈很牛,所以它复杂也在情理之中。学习到现在,对协议栈,感觉自己刚入门。继续看代码。
上回说完了ARP协议。回到inet_init函数的第1414行。看到了期待已久的IP协议的初始化函数。
1414 ip_init();
函数定义在net/ipv4/ip_output.c中。
梳理初始化的过程,重点在于协议栈数据结构网络是如何构建的。这对于后续协议栈的工作流学习是个基础。
1 它定义了IP层的设备事件处理函数。ip_netdev_notifier中的inetdev_event(net/ipv4/devinet.c中)。这个函数不展开说,只需要知道,当网卡启用,MTU变化等事件发生时,是由inetdev_event函数处理的。与之前在ARP中遇到的类似。
2 为IP层注册了链路层传来消息时的处理函数。如有新地址、删除地址、请求地址等。
2964 devinet_init();
继续2965行。fib是forward information base的缩写。也是IP层完成路由转发的重要数据信息。
2965 ip_fib_init();
因为此函数会新增一个重要数据结构,所以把它的源码列出。位于net/ipv4/fib_frontend.c中。fib_table_hash是串联起fib信息的数据结构。FIB_TABLE_HASHSZ被固定编码为1。所以只会有一个元素。只是这个列表会怎么用到,现在还不知道。
923行,为内核管理 FIB创建了一个内核socket。
925-927注册 路由事件的处理函数例程。
回到ip_rt_init函数中。
2967-2980 初始化路由表的两个维护定时器。分别是rt_flush_timer,更新定时器,rt_secret_rebuild,重建路由定时器。
最后3000行。为路由注册路由请求消息的处理例程。到这里ip_rt_init返回了。
总结下:ip_rt_init为内核建立了路由表、FIB表、路由的链路层处理函数等主要内容,为IP协议的正常工作做好了基础工作。
回到ip_init函数中。1407 inet_initpeers()。peer这个单词很有趣,意思是同龄人,地位相似的人。那么和IP协议在协议栈中有相同地位的东西是什么呢?然而看了源码中的注释后,这个函数的用途是用来为一个IP子组件申请内存资源。这个子组件就是inet_peer结构体实现的,用AVL树组织的。组件的目的是为了防范dos攻击。其原理没有细看。与理解协议关系不大。跳过。
至此IP层的初始化完成了。但网卡到IP层的接口还没有看到。尽管如此,我们协议栈的结构关系图又可以更新一些了,添加上IP层的一些数据结构。到现在为止,图中的大部分数据结构还是孤立的。等初始化完成后,一次socket调用就可以把它们基本串联起来了。
上回说完了ARP协议。回到inet_init函数的第1414行。看到了期待已久的IP协议的初始化函数。
1414 ip_init();
函数定义在net/ipv4/ip_output.c中。
1404 void __init ip_init(void) 1405 { 1406 ip_rt_init(); 1407 inet_initpeers(); 1409 #if defined(CONFIG_IP_MULTICAST) && defined(CONFIG_PROC_FS) 1410 igmp_mc_proc_init(); 1411 #endif 1412 }很简单的三个函数调用。第三个函数只是为igmp协议在虚拟文件系统proc中创建目录,不再讨论。前两个函数依次展开讨论下。第一个是ip_rt_init()。在TCP/IP协议族里,rt就代表route,路由的意思。ip_rt_init就是ip系统里路由子系统的初始化。因为路由是IP协议的一个主要功能,没有这个功能,互联网就不存在了。可见其重要性。这个函数位于net/ipv4/route.c中。是目前看到的比较复杂的一个函数。为了方便查看,我把其中预编译的部分去掉了。
梳理初始化的过程,重点在于协议栈数据结构网络是如何构建的。这对于后续协议栈的工作流学习是个基础。
2922 int __init ip_rt_init(void) 2923 { 2924 int rc = 0; 2926 rt_hash_rnd = (int) ((num_physpages ^ (num_physpages>>8)) ^ 2927 (jiffies ^ (jiffies >> 7))); 2942 ipv4_dst_ops.kmem_cachep = 2943 kmem_cache_create("ip_dst_cache", sizeof(struct rtable), 0, 2944 SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); 2946 ipv4_dst_blackhole_ops.kmem_cachep = ipv4_dst_ops.kmem_cachep; 2948 rt_hash_table = (struct rt_hash_bucket *) 2949 alloc_large_system_hash("IP route cache", 2950 sizeof(struct rt_hash_bucket), 2951 rhash_entries, 2952 (num_physpages >= 128 * 1024) ? 2953 15 : 17, 2954 0, 2955 &rt_hash_log, 2956 &rt_hash_mask, 2957 0); 2958 memset(rt_hash_table, 0, (rt_hash_mask + 1) * sizeof(struct rt_hash_bucket)); 2959 rt_hash_lock_init(); 2961 ipv4_dst_ops.gc_thresh = (rt_hash_mask + 1); 2962 ip_rt_max_size = (rt_hash_mask + 1) * 16; 2964 devinet_init(); 2965 ip_fib_init(); 2967 init_timer(&rt_flush_timer); 2968 rt_flush_timer.function = rt_run_flush; 2969 init_timer(&rt_secret_timer); 2970 rt_secret_timer.function = rt_secret_rebuild; 2975 schedule_delayed_work(&expires_work, 2976 net_random() % ip_rt_gc_interval + ip_rt_gc_interval); 2978 rt_secret_timer.expires = jiffies + net_random() % ip_rt_secret_interval + 2979 ip_rt_secret_interval; 2980 add_timer(&rt_secret_timer); 3000 rtnl_register(PF_INET, RTM_GETROUTE, inet_rtm_getroute, NULL); 3002 return rc; 3003 }2924-2962 分配了资源,初始化了路由的散列表,初始化路由表的一些限制(最大数量等)。可以看到同tcp_proto一样,ipv4_dst_ops是路由表(struct rt_table)内存资源的持有者,它还负责dst路由表的维护。真正把路由表组织起来的是rt_hash_table。因为内核要经常访问路由表,修改它,所以要用到散列的实现高效的查找访问。这两个数据结构都位于route.c文件中。让我们暂时记下这些数据结构,以后会再次遇到它们。
158 static struct dst_ops ipv4_dst_ops = { 159 .family = AF_INET, 160 .protocol = __constant_htons(ETH_P_IP), 161 .gc = rt_garbage_collect, 162 .check = ipv4_dst_check, 163 .destroy = ipv4_dst_destroy, 164 .ifdown = ipv4_dst_ifdown, 165 .negative_advice = ipv4_negative_advice, 166 .link_failure = ipv4_link_failure, 167 .update_pmtu = ip_rt_update_pmtu, 168 .entry_size = sizeof(struct rtable), 169 }; 247 static struct rt_hash_bucket *rt_hash_table; 207 struct rt_hash_bucket { 208 struct rtable *chain; 209 };继续看,2964,完成与协议栈有关的设备初始化。这个函数不复杂,源码不再列出,对于协议栈的数据结构没有什么补充,需要注意到以下事情:
1 它定义了IP层的设备事件处理函数。ip_netdev_notifier中的inetdev_event(net/ipv4/devinet.c中)。这个函数不展开说,只需要知道,当网卡启用,MTU变化等事件发生时,是由inetdev_event函数处理的。与之前在ARP中遇到的类似。
2 为IP层注册了链路层传来消息时的处理函数。如有新地址、删除地址、请求地址等。
2964 devinet_init();
继续2965行。fib是forward information base的缩写。也是IP层完成路由转发的重要数据信息。
2965 ip_fib_init();
因为此函数会新增一个重要数据结构,所以把它的源码列出。位于net/ipv4/fib_frontend.c中。fib_table_hash是串联起fib信息的数据结构。FIB_TABLE_HASHSZ被固定编码为1。所以只会有一个元素。只是这个列表会怎么用到,现在还不知道。
912 void __init ip_fib_init(void) 913 { 914 unsigned int i; 916 for (i = 0; i < FIB_TABLE_HASHSZ; i++) 917 INIT_HLIST_HEAD(&fib_table_hash[i]); 919 fib4_rules_init(); 921 register_netdevice_notifier(&fib_netdev_notifier); 922 register_inetaddr_notifier(&fib_inetaddr_notifier); 923 nl_fib_lookup_init(); 925 rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL); 926 rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL); 927 rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib); 928 }919行 fib4_rules_init(); 使用过linux iptables的人都知道,iptables是基于规则对IP数据包进行过滤,实现防火墙的功能。那么猜测这里的fib很可能与之有关系。因为带了一个rule。fib4_rules_init内容很简单,其源码不再列出,只是注册了但又会出现一个数据结构,就是fib4_rules_ops,其内容如下,在/net/ipv4/fib_rules.c中定义。全部是关于fib4_rules的操作,具体成员函数不再分析。fib4_rules_ops通过注册,被挂靠在了双向链表rules_ops上。OS以后也就是通过这个链表来搜寻到fib4_rules_ops,完成规则的适配。
277 static struct fib_rules_ops fib4_rules_ops = { 278 .family = AF_INET, 279 .rule_size = sizeof(struct fib4_rule), 280 .addr_size = sizeof(u32), 281 .action = fib4_rule_action, 282 .match = fib4_rule_match, 283 .configure = fib4_rule_configure, 284 .compare = fib4_rule_compare, 285 .fill = fib4_rule_fill, 286 .default_pref = fib4_rule_default_pref, 287 .nlmsg_payload = fib4_rule_nlmsg_payload, 288 .flush_cache = fib4_rule_flush_cache, 289 .nlgroup = RTNLGRP_IPV4_RULE, 290 .policy = fib4_rule_policy, 291 .rules_list = LIST_HEAD_INIT(fib4_rules_ops.rules_list), 292 .owner = THIS_MODULE, 293 };继续ip_fib_init函数。921-922为FIB注册了网卡事件处理函数和地址事件处理函数的接口数据结构。挂靠的上级数据结构,分别是netdev_chain(net/core/dev.c),inetaddr_chain(net/ipv4/devinet.c)。当对应类别的事件发生时,两个chain中所有处理全程,都会得到通知。
923行,为内核管理 FIB创建了一个内核socket。
925-927注册 路由事件的处理函数例程。
回到ip_rt_init函数中。
2967-2980 初始化路由表的两个维护定时器。分别是rt_flush_timer,更新定时器,rt_secret_rebuild,重建路由定时器。
最后3000行。为路由注册路由请求消息的处理例程。到这里ip_rt_init返回了。
总结下:ip_rt_init为内核建立了路由表、FIB表、路由的链路层处理函数等主要内容,为IP协议的正常工作做好了基础工作。
回到ip_init函数中。1407 inet_initpeers()。peer这个单词很有趣,意思是同龄人,地位相似的人。那么和IP协议在协议栈中有相同地位的东西是什么呢?然而看了源码中的注释后,这个函数的用途是用来为一个IP子组件申请内存资源。这个子组件就是inet_peer结构体实现的,用AVL树组织的。组件的目的是为了防范dos攻击。其原理没有细看。与理解协议关系不大。跳过。
至此IP层的初始化完成了。但网卡到IP层的接口还没有看到。尽管如此,我们协议栈的结构关系图又可以更新一些了,添加上IP层的一些数据结构。到现在为止,图中的大部分数据结构还是孤立的。等初始化完成后,一次socket调用就可以把它们基本串联起来了。
下一篇: 自主研发芯片 真是手机厂商的灵丹妙药?