TCP/IP协议栈初始化(七)感觉快上康庄大道了

时间:2023-01-09 19:47:43
上回说到IP协议初始化完成了。为IP自己工作创造了条件,那么按道理来说,此时的协议栈可以走上大道奔小康了。但是一看源码,发现后面还有tcp_init,马上知道内核中故事从来都不容易讲。如果说之前的准备工作是协议栈的地基的话,下面要开始构建的则是整个大楼的骨架了。地基里的东西,人是看不到的,它很重要,但真正能吸引人眼睛的是地基以的建筑,也是整个协议栈的核心了。
细心的人不难发现,之前的初始化从来没有涉及到TCP的管理数据结构,TCP不会放任IP分组轻松地流过自己的关卡,它要对分组进行管理,对上层的socket负责,什么时候能发送,能发送多少,TCP精细管理。所以不能不管,但管理起来又需要有数据结构来支撑啊,于是就有了后面这些函数了。大眼看了下后面几个函数的调用流程,基本还是如之前一样。TCP、IP、硬件 这样的次序进行的。友情提醒,这以后遇到的数据结构对理解协议栈的工作流程的理解,非常重要,如系统是如何管理TCP的,TCP是在什么的基础上实现的流程控制的?
回到源码中。函数inet_init第1416行。
tcp_v4_init(&inet_family_ops);
有时候对linux内核的变量命名十分的困惑。比如之前说到的struct sock和struct socket,很容易弄混。个人觉得,linux不把数据结构名称写太长是照顾到有些平台中内存资源少,名称长了,编译时占用的资源就多了。这个函数中,传入的变量是之前遇到过的inet_family_ops,是上层socket和inet之间的接口数据结构实例,不用再介绍了。看函数tcp_v4_init。定义在net/ipv4/tcp_ipv4.c中,很简单只是一个包装,为tcp创建了一个控制socket叫tcp_socket,传入的inet_family_ops根本没有用。这里我们只需要先记着tcp_socket是用来向socket发送RST命令的,是tcp中的一个控制socket实例。
再往下到1419行。
tcp_init();
源码中的注释写的是,为请求准备好TCP的slab缓存。还记得tcp_prot中申请的kmem_cache类型的slab成员吗?这个缓存就是为TCP准备的slab缓存。当时觉得申请了这个缓存就可以用TCP了,现在看来,还是要进行其他的缓存设置的。进入tcp_init函数中看下。位于net/ipv4/tcp.c中。函数较长,分成两部分来看。
2427 void __init tcp_init(void)
2428 {
2429     struct sk_buff *skb = NULL;
2430     unsigned long limit;
2431     int order, i, max_share;
2433     if (sizeof(struct tcp_skb_cb) > sizeof(skb->cb))
2434         __skb_cb_too_small_for_tcp(sizeof(struct tcp_skb_cb),
2435                        sizeof(skb->cb));
2437     tcp_hashinfo.bind_bucket_cachep =
2438         kmem_cache_create("tcp_bind_bucket",
2439                   sizeof(struct inet_bind_bucket), 0,
2440                   SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
2447     tcp_hashinfo.ehash = alloc_large_system_hash("TCP established",sizeof(struct inet_ehash_bucket),
2450                     thash_entries, (num_physpages >= 128 * 1024) ?
2452                     13 : 15,0,
2454                     &tcp_hashinfo.ehash_size,NULL,
2456                     thash_entries ? 0 : 512 * 1024);
2457     tcp_hashinfo.ehash_size = 1 << tcp_hashinfo.ehash_size;
2458     for (i = 0; i < tcp_hashinfo.ehash_size; i++) {
2459         INIT_HLIST_HEAD(&tcp_hashinfo.ehash[i].chain);
2460         INIT_HLIST_HEAD(&tcp_hashinfo.ehash[i].twchain);
2461     }
2462     if (inet_ehash_locks_alloc(&tcp_hashinfo))
2463         panic("TCP: failed to alloc ehash_locks");
2464     tcp_hashinfo.bhash =
2465         alloc_large_system_hash("TCP bind",sizeof(struct inet_bind_hashbucket),
2467                     tcp_hashinfo.ehash_size, (num_physpages >= 128 * 1024) ?
2469                     13 : 15, 0,
2471                     &tcp_hashinfo.bhash_size,NULL,
2473                     64 * 1024);
前半部分负责,各个缓存的申请。后半部分负责初始化及一些参数的设置。
2433 如果skb中的cb数组的长度过小,就调用__skb_cb_too_small_for_tcp进行一些处理,这个函数的源码没有找到,猜测应该是会把cb指向一个新分配的更大的数组上。
2437 tcp_hashinfo这个数据结构实例,非常重要。可以先告诉大家,当IP分组到达协议栈时,正是这个数据结构可以决定到底是哪个TCP的数据。也就是说,它相当于是TCP层的一个全局变量。类型是struct inet_hashinfo。具体的成员可以遇到一个说一个。bind_bucket_cachep成员从名字和后面分配缓存时的函数调用不难看出,这是一个描述TCP连接的缓存空间。单元是以struct inet_bind_bucket长度为大小。
2447 ehash这是一个描述已经建立连接的散列表的缓存空间。以inet_ehash_bucket长度为单元大小。有机会可以看下这里是如何实现散列算法的。
简单说明下alloc_large_system_hash这个函数的执行过程。
thash_entries可以由用户在系统启动时指定,如果没有指定,会默认是0.我们假设它是0.它的含义是系统可以维持的已经建立的TCP连接的数量。如果是0,系统会根据内核内存页面的大小计算一个值。但最小会保证inet_bind_hashbucket的总数的大小达到一个内存页面。如果连一个内存页面都达不到,还哪门子的large system hash呢?:)
后面就是根据需要的内核内存大小,去对应的伙伴系统中索要内存了。这里我们可以看到,内核还是非常友好的,允许我们去配置TCP的容量。
不着急,后面的下次再说。协议栈的数据结构关系图也在下次更新了。