Linux内核的拥塞控制算法系列之linxu内核拥塞控制模块的实现
说实话,linux内核的拥塞控制算法,好像早就不是我关注的焦点,写这个系列的文章算是还自己两年前想写没写完的债吧,也正好今天在北京没事干,争取一气呵成把这个系列写完。
首先要说linux内核拥塞控制算法实现的框架,linux内核好像是在2.6.12以后实现了一个拥塞控制模块的框架,至此要实现拥塞控制算法只需要在模块里实现就ok了,如此实在是省了很多人的很多事,毕竟在内核里嵌套代码,打patch包确实 是件相对更危险的事。
首先让我们看看拥塞控制模块是怎么被使用的,让我们先去注意一下在struct inet_connection_sock里面有两个数据结构:
const struct tcp_congestion_ops *icsk_ca_ops;
u32 icsk_ca_priv[16];
其中icsk_ca_ops 包含了大量的处理拥塞控制算法的函数指针,icsk_ca_priv[16]的作用是放置拥塞控制模块需要的变量控制,它的大小已经规定死了,切莫超过了,我以前就犯过这样的错误;
有了这两个变量,先看看他是怎么被赋值的,在连接初始化的时候我们会调用
tcp_init_congestion_control这个函数,这里会把我们默认的struct tcp_congestion_ops的指针赋给icsk_ca_ops;
在icsk_ca_priv[16]存放的时候拥塞控制算法需要的变量,它的实现是这样的,我们为每个拥塞控制算法都会涉及数据结构,比如bic里面的数据结构:
struct bictcp {
u32 cnt; /* increase cwnd by 1 after ACKs */
u32 last_max_cwnd; /* last maximum snd_cwnd */
u32 loss_cwnd; /* congestion window at last loss */
u32 last_cwnd; /* the last snd_cwnd */
u32 last_time; /* time when updated last_cwnd */
u32 epoch_start; /* beginning of an epoch */
#define ACK_RATIO_SHIFT 4
u32 delayed_ack; /* estimate the ratio of Packets/ACKs << 4 */
};
我在使用这个数据结构的时候,通过inet_csk_ca(sk) 把数据结构的指针指向icsk_ca_priv实现的,这也是内核协议栈的常用用法了;
说完赋值,看看我们怎么使用,首先让我们看看tcp_congestion_ops的数据结构
struct tcp_congestion_ops {
struct list_head list;
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;
};
函数很多,我挑一些比较重要的函数拿出来说
void (*cong_avoid)(struct sock *sk, u32 ack, u32 in_flight);
这个函数调用的位置在且仅在tcp_ack()函数的这段代码
if (tcp_ack_is_dubious(sk, flag)) {
/* Advance CWND, if state allows this. */
if ((flag & FLAG_DATA_ACKED) && !frto_cwnd &&
tcp_may_raise_cwnd(sk, flag))
tcp_cong_avoid(sk, ack, prior_in_flight);
tcp_fastretrans_alert(sk, prior_packets - tp->packets_out,
flag);
} else {
if ((flag & FLAG_DATA_ACKED) && !frto_cwnd)
tcp_cong_avoid(sk, ack, prior_in_flight);
}
他的作用是,如果ack包确认的新的发送端发送的数据包,那么我要去这个函数里计算一下我是不是可以增加拥塞窗口了。(我们常说的AI就是在这里干的)。
u32 (*ssthresh)(struct sock *sk);
这个函数的调用位置好像有两个,一个是确认网络拥塞,需要减少拥塞窗口的时候,另外一个是在重传定时器超时,还是要减少拥塞窗口, 把计算的值赋给拥塞窗口慢启动阈值tp->snd_ssthresh tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk);
tcp_enter_cwr() 无视它,ECN是个很好但是却没人用的东东。
tcp_retransmit_timer() 这是重传定时器超时函数,它调用的
if (tcp_use_frto(sk)) {
tcp_enter_frto(sk);
} else {
tcp_enter_loss(sk, 0);
}
都会调用它
tcp_fastretrans_alert() 这个函数,在且仅在协议栈判断网络可能丢包的时候 才会调用,并确认网络丢包,也就是说网络拥塞的时候调用icsk->icsk_ca_ops->ssthresh(sk)
void (*pkts_acked)(struct sock *sk, u32 num_acked, s32 rtt_us);
这个函数大家也都用的比较多,其实有一个很重要的元素师因为它带入 了rtt_us这个变量,现在基于时延的拥塞控制算法已经成为了业界的共识和不可阻挡的趋势
他的调用位置是在有新的数据被确认后。
最后说一下算法的注册和删除。
其实就是个链表,把新注册的数据结构插在链表的第一位,连接建立的时候会把最新注册的算法数据结构赋给连接;当然我们是可以调整算法顺序的,/proc/sys/net/ipv4/tcp_congestion_contril 就是干这个事的。
删除也就是 在链表里删除,但是如果 还有连接在使用这个算法,是不能被删除
泓日天
阅读(5054) | 评论(1) | 转发(0) |