理解内核TCP定时器实现
为什么内核TCP需要定时器
TCP协议中的一些过程是需要在一段特定时间间隔后被处罚, 包括基本的报文 retransmit、delay ACK、timewait 等等.
另外, 定时器触发后的响应函数都是在 bottom half 上下文运行, 整个过程无需用户程序参与.
定时器分类
站在一个tcp连接的视角,内核为其准备了以下几个定时器: reqsk、retransmit、delack、keepalive、timewait、pacing.
接下来分别进行解释
reqsk timer
这是一个仅用于服务端的定时器, 它在收到 SYN 报文后被创建和启动,如果收不到对端 SYN ACK 的确认,
超时响应函数reqsk_timer_handler
会被调用, 重传 SYN ACK, 并重新 rearm 定时器, 如果超过一定次数, 半连接将被释放.
创建定时器
tcp_conn_request
|- inet_csk_reqsk_queue_hash_add
|- reqsk_queue_hash_req
|-- timer_setup(&req->rsk_timer, reqsk_timer_handler, TIMER_PINNED);
retransmit timer
retransmit timer 用于报文重传. 然后, 稍微有点复杂的是,内核tcp的多个特性都可以去设置其超时时间.
- ICSK_TIME_RETRANS: 最基本的超过 RTO 后重传报文
- ICSK_TIME_PROBE0: 超时后发送零窗口探测报文, 参考 zero-window-size 死锁
- ICSK_TIME_EARLY_RETRANS: 超时后进行 early 重传 — 参考 early retrans 特性
- ICSK_TIME_LOSS_PROBE: 超时后发送 TLP 探测报文 — 参考 TLP 特性
- ICSK_TIME_REO_TIMEOUT: 超时后标记 lost — 参考 RACK 特性
其中, RETRANS 表示通常意义上的超时重传, PROBE0 表示进行零窗口探测, 余下三个则是一些针对快速重传和快速恢复的优化
retransmit timer 在客户端和服务端的创建时机稍有不同, 前者在tcp socket创建时, 后者在三次握手成功后.
客户端创建定时器
tcp_init_sock
|- tcp_init_xmit_timers
|- inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer, &tcp_keepalive_timer);
|- timer_setup(&icsk->icsk_retransmit_timer, retransmit_handler, 0);
服务端创建定时器
tcp_v4_syn_recv_sock
|- tcp_create_openreq_child
|-tcp_init_xmit_timers
|- inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer, &tcp_keepalive_timer);
|- timer_setup(&icsk->icsk_retransmit_timer, retransmit_handler, 0);
启动定时器时, 需要额外将来源记录在 icsk->icsk_pending
中, 之后在超时响应函数tcp_write_timer()
中, 根据不同的来源进行不同处理.
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);
...
if (what == ICSK_TIME_RETRANS || what == ICSK_TIME_PROBE0 ||
what == ICSK_TIME_EARLY_RETRANS || what == ICSK_TIME_LOSS_PROBE ||
what == ICSK_TIME_REO_TIMEOUT) {
icsk->icsk_pending = what; // 记录来源
icsk->icsk_timeout = jiffies + when;
sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
}
...
}
delack timer
delack timer 用于控制 delay ack 的发送, 其创建时机与 retransmit timer
一致
创建定时器
tcp_init_xmit_timers
|- inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer, &tcp_keepalive_timer);
|- timer_setup(&icsk->icsk_delack_timer, delack_handler, 0);
其启动时机有多个, 比如收到报文、发送ack后.
启动定时器
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, TCP_DELACK_MAX, TCP_RTO_MAX);
|- inet_csk_reset_xmit_timer
|- 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);
keepalive timer
keepalive timer 用于防止 TCP 连接长时间闲置, 其创建时机与 retransmit timer
一致.
每当tcp连接双方有交互, inet_csk_reset_keepalive_timer()
会将定时器重置.
创建定时器
tcp_init_xmit_timers
|- inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer, &tcp_keepalive_timer);
|- timer_setup(&sk->sk_timer, keepalive_handler, 0);
timewait timer
timewait timer 用于 TIME-WAIT 或 FIN-WAIT-2 状态的计时, TIME-WAIT 超时后连接被彻底释放.
创建定时器
tcp_time_wait
|- inet_twsk_alloc
|- timer_setup(&tw->tw_timer, tw_timer_handler, TIMER_PINNED);
在创建后, 该定时器便会被启动
tcp_time_wait
|- inet_twsk_schedule
|- __inet_twsk_schedule
|- mod_timer(&tw->tw_timer, jiffies + timeo)
pacing timer
pacing timer 用于在 BBR 之类的拥塞控制算法中控制报文的发送. 其创建时机与 retransmit timer
一致.
创建定时器
tcp_init_xmit_timers
|- hrtimer_init(&tcp_sk(sk)->pacing_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_SOFT);
而在报文发送时, 启动该定时器
启动定时器
tcp_write_xmit
|- tcp_pacing_check
|- hrtimer_start(&tp->pacing_timer,ns_to_ktime(tp->tcp_wstamp_ns), HRTIMER_MODE_ABS_PINNED_SOFT);