TCP/IP协议栈初始化(六) 终于见到IP

时间:2022-05-18 19:47:53
    这已经是第六篇了。但协议栈的初始化还没有说完。不得不承认协议栈还是很复杂的。越是牛B的东西,就越复杂。就像一门手艺一样,当你可以做到别人都不能达到的复杂度的时候,你就是大师了。还有人说,想要精通一样技术,你必须重复它10万次以上。子曰:“温故而知新”,代码看多了,就能明白其中的奥秘了。当然一些实践还是必不可少的。这个系列一开始,我就说,协议栈很牛,所以它复杂也在情理之中。学习到现在,对协议栈,感觉自己刚入门。继续看代码。
    上回说完了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调用就可以把它们基本串联起来了。
TCP/IP协议栈初始化(六) 终于见到IP