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);
注:其实还有一些其它的常用操作,但是都比较简单。