欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

TCP/IP协议栈初始化

程序员文章站 2022-07-10 10:48:17
这已经是第六篇了。但协议栈的初始化还没有说完。不得不承认协议栈还是很复杂的。越是牛B的东西,就越复杂。就像一门手艺一样,当你可以做到别人都不能达到的复杂度的时候,你就是大师了。还有人说,想要精...
这已经是第六篇了。但协议栈的初始化还没有说完。不得不承认协议栈还是很复杂的。越是牛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协议栈初始化