TCP/IP协议栈初始化(八) TCP马上准备进入状态

时间:2022-07-01 19:49:36
TCP马上要准备好了。

上回tcp_init函数并没有说完。因为这个函数做了许多重要的事情。这次接着说。

2474 	tcp_hashinfo.bhash_size = 1 << tcp_hashinfo.bhash_size;
2475 	for (i = 0; i < tcp_hashinfo.bhash_size; i++) {
2476 		spin_lock_init(&tcp_hashinfo.bhash[i].lock);
2477 		INIT_HLIST_HEAD(&tcp_hashinfo.bhash[i].chain);
2478 	}
2483 	for (order = 0; ((1 << order) << PAGE_SHIFT) <
2484 			(tcp_hashinfo.bhash_size * sizeof(struct inet_bind_hashbucket));
2485 			order++)
2486 		;
2487 	if (order >= 4) {
2488 		tcp_death_row.sysctl_max_tw_buckets = 180000;
2489 		sysctl_tcp_max_orphans = 4096 << (order - 4);
2490 		sysctl_max_syn_backlog = 1024;
2491 	} else if (order < 3) {
2492 		tcp_death_row.sysctl_max_tw_buckets >>= (3 - order);
2493 		sysctl_tcp_max_orphans >>= (3 - order);
2494 		sysctl_max_syn_backlog = 128;
2495 	}
2501 	limit = min(nr_all_pages, 1UL<<(28-PAGE_SHIFT)) >> (20-PAGE_SHIFT);
2502 	limit = (limit * (nr_all_pages >> (20-PAGE_SHIFT))) >> (PAGE_SHIFT-11);
2503 	limit = max(limit, 128UL);
2504 	sysctl_tcp_mem[0] = limit / 4 * 3;
2505 	sysctl_tcp_mem[1] = limit;
2506 	sysctl_tcp_mem[2] = sysctl_tcp_mem[0] * 2;
2509 	limit = ((unsigned long)sysctl_tcp_mem[1]) << (PAGE_SHIFT - 7);
2510 	max_share = min(4UL*1024*1024, limit);
2512 	sysctl_tcp_wmem[0] = SK_STREAM_MEM_QUANTUM;
2513 	sysctl_tcp_wmem[1] = 16*1024;
2514 	sysctl_tcp_wmem[2] = max(64*1024, max_share);
2516 	sysctl_tcp_rmem[0] = SK_STREAM_MEM_QUANTUM;
2517 	sysctl_tcp_rmem[1] = 87380;
2518 	sysctl_tcp_rmem[2] = max(87380, max_share);
2524 	tcp_register_congestion_control(&tcp_reno);
2525 }
2474 对bhash_size重新定义了大小,变成了2^bhash_size大小。
2475 初始化了tcp_hashinfo中的bhash的锁和其各个hash元素对应的链表。不难发现这里解决散列冲突的算法应该是开放地址法。
2483-2495 这里别漏掉了中间2486行的那个分号。加了分号说明前面的for循环只是个空循环,什么都不做。是为了找到一个order值,熟悉内存buddy算法的人一看就知道,这里找的order其实就是buddy算法中的内存页面的等级。buddy中,将内存页面以PAGE_SIZE为单元为分1 2 4 8 16 32...这样的2的幂级数的数量的单元集合。这样做的目的,是便于当相邻的物理内存有释放时,可以就近合并到高阶内存表中,同时分配的时候,只找最小能满足要求的列表级别中的内存页面。整体上只是为了减少内存的碎片。这里我们找到的order就是这个意义。找到这个值时,就可以判断出,系统中为TCP分配的内存缓存量是大还是小。下面的if else就是这样处理的。三个变量分别是僵死连接、孤儿TCP连接、最大的SYN数量。意思就是,如果内存多,资源充足,就多给这三个分一些。这三个家伙管理的都不是正常的TCP,资源少时,就少一些吧。如sysctl_max_syn_backlog从1024直减到128。
2501-2518 也是根据当前系统中的资源,给sysctl_tcp_mem sysctl_tcp_rmem sysctl_tcp_wmem分配资源数量。这三个变量的用途暂时不明,可以大概根据名字测一下,是系统用来控制TCP的。注意,之前创建过一个控制socket,不过只是用来给TCP发送RST命令的。这里可能是补充。
2524 整个函数的最后一个语句。由函数的名称可以看出,这个是用来初始化TCP的拥堵控制算法相关的数据结构的。先看下传入的参数tcp_reno,Reno的拥堵避免算法。位于net/ipv4/tcp_cong.c。
376 struct tcp_congestion_ops tcp_reno = {
377 	.flags		= TCP_CONG_NON_RESTRICTED,
378 	.name		= "reno",
379 	.owner		= THIS_MODULE,
380 	.ssthresh	= tcp_reno_ssthresh,
381 	.cong_avoid	= tcp_reno_cong_avoid,
382 	.min_cwnd	= tcp_reno_min_cwnd,
383 };
看到它的类型,可以注意到内核专门为TCP的拥堵控制设计了一个数据类型。struct tcp_congestion_ops的具体成员可以不先不看,看下tcp_reno初始化了哪些?第一个标识flag,应该是算法的类型,tcp_reno属于无限制类型的。name owner,这个没有什么好说的。ssthresh,这个成员是个函数,看过协议的人应该知道,TCP通过滑动窗口来实现流量控制,拥堵控制包含2个方面,分别是缓启动和拥堵避免。tcp_reno_ssthresh,正是用来计算缓启动门限值。cong_avoid成员,tcp_reno_cong_avoid是处理发生拥堵时,计算当前应该使用的窗口大小。min_cwnd成员,tcp_reno_min_cwnd,用来计算拥堵窗口的大小。
而tcp_register_congestion_control这个函数,仅仅是把tcp_reno挂到tcp_cong_list的列表上了。
现在又可以在数据结构关系图中增加几个元素了。但整个数据结构关系仍然不明了。可以这么理解bind与hash的区别,bind状态的TCP是还没有建立连接,此时不需要给它建立散列,因为散列只是当有IP数据包到达时,需要根据目标IP、源IP、各自的端口组成的四元组来确定是哪个TCP连接的数据包,这时候才需要用到hash。

TCP/IP协议栈初始化(八) TCP马上准备进入状态