上回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。