每条TCP连接都会维护一个超时重传定时器,该定时器是TCP保证可靠性的一个非常重要的手段,一旦该定时器超时,那么就会重传还未收到ACK的报文。这篇笔记就来看看该定时器相关的代码实现。
1. 相关数据结构
struct inet_connection_sock {
...
//icsk_retransmit_timer的超时时刻,jiffies超过该值时定时器超时
unsigned long icsk_timeout;
//超时重传定时器、持续定时器(还有其它)
struct timer_list icsk_retransmit_timer;
...
//拥塞状态
__u8 icsk_ca_state;
//发生超时重传的次数。当退出LOSS状态时该计数器清零
__u8 icsk_retransmits;
//icsk_retransmit_timer定时器函数可以处理4个定时器,
//当前应该处理哪个事件也需要区分,0表示没有事件需要处理
__u8 icsk_pending;
...
};
2. 初始化
超时重传定时器是在socket()系统调用执行过程中初始化的,具体在创建TCB后调用tcp_v4_sock_init()初始化其TCP相关字段时完成的,代码如下:
static int tcp_v4_init_sock(struct sock *sk)
{
...
tcp_init_xmit_timers(sk);
...
}
void tcp_init_xmit_timers(struct sock *sk)
{
//超时重传定时器的定时器处理函数为tcp_write_time()
inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,
&tcp_keepalive_timer);
}
/*
* Using different timers for retransmit, delayed acks and probes
* We may wish use just one timer maintaining a list of expire jiffies
* to optimize.
*/
//该函数安装三个定时器
void inet_csk_init_xmit_timers(struct sock *sk,
void (*retransmit_handler)(unsigned long),
void (*delack_handler)(unsigned long),
void (*keepalive_handler)(unsigned long))
{
struct inet_connection_sock *icsk = inet_csk(sk);
setup_timer(&icsk->icsk_retransmit_timer, retransmit_handler,
(unsigned long)sk);
setup_timer(&icsk->icsk_delack_timer, delack_handler,
(unsigned long)sk);
setup_timer(&sk->sk_timer, keepalive_handler, (unsigned long)sk);
//由于同一个定时器函数可以处理多个定时器,它们也需要进行区分,pending参数表示
//当前需要处理的是哪个定时事件,0表示没有事件需要处理
icsk->icsk_pending = icsk->icsk_ack.pending = 0;
}
//setup_timer()仅仅是初始化定时器参数,并没有启动定时器
static inline void setup_timer(struct timer_list * timer,
void (*function)(unsigned long),
unsigned long data)
{
timer->function = function;
//data参数就是TCB指针
timer->data = data;
init_timer(timer);
}
3. 启动定时器
启动定时器由函数inet_csk_reset_xmit_timer()完成。
/*
* Reset the retransmission timer
*/
@what: 表示要复位的定时器,对于超时重传定时器,该值为ICSK_TIME_RETRANS;
@when: 表示该定时器再过几个滴答超时;
@max_when: when可取的最大值,如果指定when超过了max_when,那么只取max_when.
static inline void inet_csk_reset_xmit_timer(struct sock *sk, const int what,
unsigned long when,
const unsigned long max_when)
{
struct inet_connection_sock *icsk = inet_csk(sk);
//矫正when参数
if (when > max_when) {
when = max_when;
}
//关注what为ICSK_TIME_RETRANS的情况
if (what == ICSK_TIME_RETRANS || what == ICSK_TIME_PROBE0) {
//将事件记录到icsk_pending中表示启动的定时器是超时重传定时器
icsk->icsk_pending = what;
//记录超时时间点到icsk_timeout
icsk->icsk_timeout = jiffies + when;
//重新启动定时器
sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
} else if (what == ICSK_TIME_DACK) {
icsk->icsk_ack.pending |= ICSK_ACK_TIMER;
icsk->icsk_ack.timeout = jiffies + when;
sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout);
}
}
void sk_reset_timer(struct sock *sk, struct timer_list* timer,
unsigned long expires)
{
//定时器启动期间会持有TCB的引用,防止其被释放
if (!mod_timer(timer, expires))
sock_hold(sk);
}
3.1 启动时机
超时重传定时器会在如下几种情况下激活:
- 发送了第一包新数据(第一次发送或者之间发送的已经全部被确认);
- 路径MTU探测失败后;
- 发现接收端将SACK确认过的数据丢掉后。
4. 超时处理
RTO超时后,由定时器函数tcp_write_timer()开始处理.
在整个超时处理过程中,当前只关注连接态的处理。
4.1 tcp_write_timer()
static void tcp_write_timer(unsigned