首页 > 代码库 > TCP:传输控制协议(一)

TCP:传输控制协议(一)

  相关协议分析参考tcp/ip协议学习笔记(8)TCP传输控制协议

TCP传输控制块的管理、套接口选项、ioctl、差错处理以及缓存管理涉及以下文件:

include/linux/tcp.h 定义TCP段的格式、TCP传输控制块等结构、宏和函数原型

include/net/sock.h 定义基本的传输控制块结构、宏和函数原型

include/net/inet_connection_sock.h 定义连接请求块等相关接口、宏和函数

include/net/inet_hashtables.h 定义管理传输控制块的散列表

net/ipv4/af_inet.c 网络层和传输层接口

net/ipv4/tcp_ipv4.c 传输控制块与网络层之间的接口实现

net/ipv4/tcp.c 传输控制块与应用层之间的接口实现

net/core/stream.c TCP中流内存管理的实现

TCP传输控制块

TCP传输控制块在TCP整个过程中起着核心的作用,包括建立连接、数据的传输、拥塞的控制以及连接的终止。在TCP连接的整个过程中,按顺序分别使用以下三种类型的TCP传输控制块:

1、第一种类型是tcp_request_sock,在建立连接的过程中使用,存在的时间是比较短的

2、第二种类型是tcp_sock,在连接建立之后终止之前使用,TCP状态为ESTABLISHED。这种传输控制块声明周期最长,发送和接收段都需要它进行控制

3、第三种类型是tcp_timewait_sock,在终止连接的过程中使用,其存在的过程也比较短

inet_connection_sock_af_ops

封装了一组与传输层相关的操作集,包括向网络层发送的接口、传输层的setsockopt接口等,TCP中的实例为ipv4_specific

tcp_options_received

主要用来保存接收到的TCP选项信息,如时间戳、SACK等,同时标志对端支持的特性,如对端是否支持窗口扩大因子、是否支持ACK等。

tcp_skb_cb

TCP层在SKB区中有一个私有信息控制块,即skb_buff结构的cb成员,TCP利用这个字段存储了一个tcp_skb_cb结构。在TCP层,用宏TCP_SKB_CB实现访问该信息块,以增强代码的可读性。对这个私有信息控制块的赋值一般在本层接收到段或发送段之前进行。例如tcp_v4_rcv()是TCP层接收入口函数,当接收到TCP段并对其进行必要校验后,就会对此段的tcp_skb_cb进行设置。而发送过程中,大多数是在生成TCP段时,或是在对TCP段进行分段时设置。

TCP的初始化

传输层TCP模块的初始化函数tcp_init()由IPv4协议族的初始化函数inet_init()调用

void __init tcp_init(void)
{
	struct sk_buff *skb = NULL;
	unsigned long nr_pages, limit;
	int i, max_share, cnt;

	BUILD_BUG_ON(sizeof(struct tcp_skb_cb) > sizeof(skb->cb));

	percpu_counter_init(&tcp_sockets_allocated, 0);
	percpu_counter_init(&tcp_orphan_count, 0);
	tcp_hashinfo.bind_bucket_cachep =
		kmem_cache_create("tcp_bind_bucket",
				  sizeof(struct inet_bind_bucket), 0,
				  SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);

	/* Size and allocate the main established and bind bucket
	 * hash tables.
	 *
	 * The methodology is similar to that of the buffer cache.
	 */
	tcp_hashinfo.ehash =
		alloc_large_system_hash("TCP established",
					sizeof(struct inet_ehash_bucket),
					thash_entries,
					(totalram_pages >= 128 * 1024) ?
					13 : 15,
					0,
					&tcp_hashinfo.ehash_size,
					NULL,
					thash_entries ? 0 : 512 * 1024);
	tcp_hashinfo.ehash_size = 1 << tcp_hashinfo.ehash_size;
	for (i = 0; i < tcp_hashinfo.ehash_size; i++) {
		INIT_HLIST_NULLS_HEAD(&tcp_hashinfo.ehash[i].chain, i);
		INIT_HLIST_NULLS_HEAD(&tcp_hashinfo.ehash[i].twchain, i);
	}
	if (inet_ehash_locks_alloc(&tcp_hashinfo))
		panic("TCP: failed to alloc ehash_locks");
	tcp_hashinfo.bhash =
		alloc_large_system_hash("TCP bind",
					sizeof(struct inet_bind_hashbucket),
					tcp_hashinfo.ehash_size,
					(totalram_pages >= 128 * 1024) ?
					13 : 15,
					0,
					&tcp_hashinfo.bhash_size,
					NULL,
					64 * 1024);
	tcp_hashinfo.bhash_size = 1 << tcp_hashinfo.bhash_size;
	for (i = 0; i < tcp_hashinfo.bhash_size; i++) {
		spin_lock_init(&tcp_hashinfo.bhash[i].lock);
		INIT_HLIST_HEAD(&tcp_hashinfo.bhash[i].chain);
	}


	cnt = tcp_hashinfo.ehash_size;

	tcp_death_row.sysctl_max_tw_buckets = cnt / 2;
	sysctl_tcp_max_orphans = cnt / 2;
	sysctl_max_syn_backlog = max(128, cnt / 256);

	/* Set the pressure threshold to be a fraction of global memory that
	 * is up to 1/2 at 256 MB, decreasing toward zero with the amount of
	 * memory, with a floor of 128 pages, and a ceiling that prevents an
	 * integer overflow.
	 */
	nr_pages = totalram_pages - totalhigh_pages;
	limit = min(nr_pages, 1UL<<(28-PAGE_SHIFT)) >> (20-PAGE_SHIFT);
	limit = (limit * (nr_pages >> (20-PAGE_SHIFT))) >> (PAGE_SHIFT-11);
	limit = max(limit, 128UL);
	limit = min(limit, INT_MAX * 4UL / 3 / 2);
	sysctl_tcp_mem[0] = limit / 4 * 3;
	sysctl_tcp_mem[1] = limit;
	sysctl_tcp_mem[2] = sysctl_tcp_mem[0] * 2;

	/* Set per-socket limits to no more than 1/128 the pressure threshold */
	limit = ((unsigned long)sysctl_tcp_mem[1]) << (PAGE_SHIFT - 7);
	max_share = min(4UL*1024*1024, limit);

	sysctl_tcp_wmem[0] = SK_MEM_QUANTUM;
	sysctl_tcp_wmem[1] = 16*1024;
	sysctl_tcp_wmem[2] = max(64*1024, max_share);

	sysctl_tcp_rmem[0] = SK_MEM_QUANTUM;
	sysctl_tcp_rmem[1] = 87380;
	sysctl_tcp_rmem[2] = max(87380, max_share);

	printk(KERN_INFO "TCP: Hash tables configured "
	       "(established %d bind %d)\n",
	       tcp_hashinfo.ehash_size, tcp_hashinfo.bhash_size);

	tcp_register_congestion_control(&tcp_reno);
}

TCP传输控制块的管理

在成功创建一个TCP传输控制块后,就需要对其进行合理的管理。TCP存在多个状态,有些状态存在的时间比较短暂,在TCP两端交互的过程中,这些状态很快会迁移到另一种状态。相比之下,LISTEN和ESTABLISHED这两种状态是一种常态。为了能对处于不同状态的传输控制块进行合理的管理和访问,TCP根据状态将传输控制块存储到多个不同的散列表中。

inet_hashinfo

struct inet_hashinfo {
	/* This is for sockets with full identity only.  Sockets here will
	 * always be without wildcards and will have the following invariant:
	 *
	 *          TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE
	 *
	 * TIME_WAIT sockets use a separate chain (twchain).
	 */
	struct inet_ehash_bucket	*ehash;
	spinlock_t			*ehash_locks;
	unsigned int			ehash_size;
	unsigned int			ehash_locks_mask;

	/* Ok, let's try this, I give up, we do need a local binding
	 * TCP hash as well as the others for fast bind/connect.
	 */
	struct inet_bind_hashbucket	*bhash;

	unsigned int			bhash_size;
	/* 4 bytes hole on 64 bit */

	struct kmem_cache		*bind_bucket_cachep;

	/* All the above members are written once at bootup and
	 * never written again _or_ are predominantly read-access.
	 *
	 * Now align to a new cache line as all the following members
	 * might be often dirty.
	 */
	/* All sockets in TCP_LISTEN state will be in here.  This is the only
	 * table where wildcard'd TCP sockets can exist.  Hash function here
	 * is just local port number.
	 */
	struct inet_listen_hashbucket	listening_hash[INET_LHTABLE_SIZE]
					____cacheline_aligned_in_smp;

	atomic_t			bsockets;
};
struct inet_ehash_bucket *ehash;

unsigned int ehash_size;

ehash指向一个大小为ehash_size的inet_ehash_bucket结构类型的散列表,用来管理TCP状态除LISTEN之外的传输控制块的散列表

struct inet_ehash_bucket {
struct hlist_nulls_head chain;
struct hlist_nulls_head twchain;
};
chain、twchain用于链接传输控制块

struct inet_bind_hashbucket *bhash;

unsigned int bhash_size;
大小为bhash_size的bhash散列表主要用来存储已绑定端口的信息

struct inet_bind_hashbucket {
spinlock_t lock;
struct hlist_headchain;
};

chain用于建立端口绑定信息块

struct inet_listen_hashbucket listening_hash[INET_LHTABLE_SIZE];

用来存储管理LISTEN状态的传输控制块的散列表

在成功创建一个传输控制块之后,就会调用传输接口层的hash接口,将该传输控制块添加到ehash散列表中,直至释放该传输控制块为止。在TCP中,实现hash接口的函数为tcp_v4_hash()。

当不需要某个传输控制块时,就会调用传输接口层的unhash接口,将该传输控制块从ehash散列表中删除。在TCP中,实现hash接口的函数为tcp_unhash()。

在调用listen系统调用之后,套接口就会进入LISTEN状态,此时会调用__inet_hash(),将该传输控制块添加到listening_hash散列表中,以便快速地查找处于侦听状态的套接口。

TCP:传输控制协议(一)