Linux内核是支持多种拥塞控制算法并存的,而且支持为不同的TCP流使用不同的拥塞控制算法。这篇笔记就来介绍下内核是如何支持这种特性的。

1. 数据结构

每一种拥塞控制算法必须提供一个struct tcp_congestion_ops结构,然后向系统注册,系统将所有注册的拥塞控制算法组织成一个单链表。

/*
 * Interface for adding new TCP congestion control handlers
 */
#define TCP_CA_NAME_MAX	16
#define TCP_CA_MAX	128
#define TCP_CA_BUF_MAX	(TCP_CA_NAME_MAX*TCP_CA_MAX)

#define TCP_CONG_NON_RESTRICTED 0x1
#define TCP_CONG_RTT_STAMP	0x2

struct tcp_congestion_ops {
	//所有拥塞控制算法组织成单链表
	struct list_head	list;
	//当前只定义了两个标记:
	//TCP_CONG_NON_RESTRICTED: 如果没有设置该标记,表示算法的使用者需要有网络管理权限
	//TCP_CONG_RTT_STAMP: 
	unsigned long flags;

	/* initialize private data (optional) */
	void (*init)(struct sock *sk);
	/* cleanup private data  (optional) */
	void (*release)(struct sock *sk);

	/* return slow start threshold (required) */
	u32 (*ssthresh)(struct sock *sk);
	/* lower bound for congestion window (optional) */
	u32 (*min_cwnd)(const struct sock *sk);
	/* do new cwnd calculation (required) */
	void (*cong_avoid)(struct sock *sk, u32 ack, u32 in_flight);
	/* call before changing ca_state (optional) */
	void (*set_state)(struct sock *sk, u8 new_state);
	/* call when cwnd event occurs (optional) */
	void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev);
	/* new value of cwnd after loss (optional) */
	u32  (*undo_cwnd)(struct sock *sk);
	/* hook for packet ack accounting (optional) */
	void (*pkts_acked)(struct sock *sk, u32 num_acked, s32 rtt_us);
	/* get info for inet_diag (optional) */
	void (*get_info)(struct sock *sk, u32 ext, struct sk_buff *skb);

	//可以为每个拥塞控制算法提供一个名字
	char 		name[TCP_CA_NAME_MAX];
	struct module 	*owner;
};

2. 初始化

首先所有的拥塞控制算法被组织成一个单链表,链表的定义如下:

static DEFINE_SPINLOCK(tcp_cong_list_lock);
static LIST_HEAD(tcp_cong_list);

2.1 常驻内存拥塞控制算法

在系统启动时,tcp_init()会将Reno(实际上是New Reno)注册为默认的拥塞控制算法。当然,还有其它方式可以更改这种配置,但是无论如何,New Reno都是编译到内核镜像的,而其它算法都可以以模块的方式存在,在需要时动态加载即可。

void __init tcp_init(void)
{
...
	tcp_register_congestion_control(&tcp_reno);
...
}

3. 基本操作

3.1 算法的注册/注销

如tcp_init()所述,可以通过接口tcp_register_congestion_control()向内核注册拥塞控制算法。

/*
 * Attach new congestion control algorithm to the list
 * of available options.
 */
入参就是拥塞控制算法
int tcp_register_congestion_control(struct tcp_congestion_ops *ca)
{
	int ret = 0;

	//ssthresh()和cong_avoid()回调是必须提供的
	if (!ca->ssthresh || !ca->cong_avoid) {
		printk(KERN_ERR "TCP %s does not implement required ops\n",
		       ca->name);
		return -EINVAL;
	}

	spin_lock(&tcp_cong_list_lock);
	//如果算法已经注册,那么注册失败
	if (tcp_ca_find(ca->name)) {
		printk(KERN_NOTICE "TCP %s already registered\n", ca->name);
		ret = -EEXIST;
	} else {
		//将注册的算法添加到算法列表的末尾
		list_add_tail_rcu(&ca->list, &tcp_cong_list);
		printk(KERN_INFO "TCP %s registered\n", ca->name);
	}
	spin_unlock(&tcp_cong_list_lock);

	return ret;
}
EXPORT_SYMBOL_GPL(tcp_register_congestion_control);

类似的,注销是通过tcp_unregister_congestion_control()实现的,不再罗列。

3.2 选取算法

可以通过tcp_set_congestion_control()为某个套接字设置拥塞控制算法。用户空间程序可以通过TCP选项TCP_CONGESTION为套接字设置拥塞控制算法,最终调用的就是这个函数。

/* Change congestion control for socket */
int tcp_set_congestion_control(struct sock *sk, const char *name)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_congestion_ops *ca;
	int err = 0;

	rcu_read_lock();
	//查找算法是否已经注册
	ca = tcp_ca_find(name);

	/* no change asking for existing value */
	//如果当前TCB的算法就是要设定的,直接返回
	if (ca == icsk->icsk_ca_ops)
		goto out;

#ifdef CONFIG_KMOD
	//算法尚未注册,但是支持模块的动态加载,先尝试加载指定算法
	/* not found attempt to autoload module */
	if (!ca && capable(CAP_SYS_MODULE)) {
		rcu_read_unlock();
		request_module("tcp_%s", name);
		rcu_read_lock();
		ca = tcp_ca_find(name);
	}
#endif
	//最终还是没有找到指定的算法,失败
	if (!ca)
		err = -ENOENT;
	//如果算法的使用是受限的,但是当前进程又没有网络管理权限,失败
	else if (!((ca->flags & TCP_CONG_NON_RESTRICTED) || capable(CAP_NET_ADMIN)))
		err = -EPERM;
	//获取不到引用计数,失败
	else if (!try_module_get(ca->owner))
		err = -EBUSY;
	//设定成功
	else {
		//清理TCB中之前的拥塞控制算法
		tcp_cleanup_congestion_control(sk);
		//设置算法
		icsk->icsk_ca_ops = ca;
		//如果算法有提供初始化函数,调用初始化函数
		if (sk->sk_state != TCP_CLOSE && icsk->icsk_ca_ops->init)
			icsk->icsk_ca_ops->init(sk);
	}
 out:
	rcu_read_unlock();
	return err;
}

/* Manage refcounts on socket close. */
void tcp_cleanup_congestion_control(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);

	//调用release()回调
	if (icsk->icsk_ca_ops->release)
		icsk->icsk_ca_ops->release(sk);
	//释放对算法的引用计数
	module_put(icsk->icsk_ca_ops->owner);
}

3.3 设置默认算法

可以调用tcp_set_default_congestion_control()为系统设置默认的拥塞控制算法。

/* Used by sysctl to change default congestion control */
int tcp_set_default_congestion_control(const char *name)
{
	struct tcp_congestion_ops *ca;
	int ret = -ENOENT;

	spin_lock(&tcp_cong_list_lock);
	//类似tcp_set_congestion_control()中的算法查找部分
	ca = tcp_ca_find(name);
#ifdef CONFIG_KMOD
	if (!ca && capable(CAP_SYS_MODULE)) {
		spin_unlock(&tcp_cong_list_lock);

		request_module("tcp_%s", name);
		spin_lock(&tcp_cong_list_lock);
		ca = tcp_ca_find(name);
	}
#endif

	if (ca) {
		//默认的算法没有使用权限限制
		ca->flags |= TCP_CONG_NON_RESTRICTED;	/* default is always allowed */
		//链表中第一个算法就是默认算法
		list_move(&ca->list, &tcp_cong_list);
		ret = 0;
	}
	spin_unlock(&tcp_cong_list_lock);

	return ret;
}

/* Set default value from kernel configuration at bootup */
static int __init tcp_congestion_default(void)
{
	//可以通过配置CONFIG_DEFAULT_TCP_CONG为系统设置默认的拥塞控制算法
	return tcp_set_default_congestion_control(CONFIG_DEFAULT_TCP_CONG);
}
late_initcall(tcp_congestion_default);

注:其实还有一些其它的常用操作,但是都比较简单。