概述
tcp握手完成后,收到数据包后,调用路径为tcp_v4_rcv->tcp_v4_do_rcv->tcp_rcv_established
在tcp_rcv_established中处理TCP_ESTABLISHED状态的包。 并分为快速路径和慢速路径。
快速路径只进行非常少量的处理。
快速路径:用于处理预期的,理想情况下的数据段,在这种情况下,不会对一些边缘情形进行检测,进而达到快速处理的目的;
慢速路径:用于处理那些非预期的,非理想情况下的数据段,即不满足快速路径的情况下数据段的处理;
首部预测字段格式:首页预测字段,实际上是与TCP首部中的【头部长度+保留字段+标记字段+窗口值】这个32位值完全对应的;进行快速路径判断的时候,只需要将该预测值与TCP首部中的对应部分进行比对即可,具体见tcp_rcv_established;
快速路径(Fast Path)
内核使用tcp_sock中的pred_flags作为判断条件,0表示使用慢速路径,非0则表示快速的判断条件,值为tcp首部的第13-16字节,包含首部长度,标记位,窗口大小。
因此使用pred_flags来检查tcp头就能避免了头部的一些控制信息的处理。
static inline void __tcp_fast_path_on(struct tcp_sock *tp, u32 snd_wnd)
{//tcphdr首部的第13-16字节,包含首部长度,标记位,窗口大小
tp->pred_flags = htonl((tp->tcp_header_len << 26) |
ntohl(TCP_FLAG_ACK) |
snd_wnd);
}
static inline void tcp_fast_path_on(struct tcp_sock *tp)
{//snd_wnd已经缩放过,要还原tcp头信息这里缩放回去
__tcp_fast_path_on(tp, tp->snd_wnd >> tp->rx_opt.snd_wscale);
}
static inline void tcp_fast_path_check(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
/*
1 没有乱序数据包
2 接收窗口不为0
3 还有接收缓存空间
4 没有紧急数据
*/
if (RB_EMPTY_ROOT(&tp->out_of_order_queue) &&
tp->rcv_wnd &&
atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf &&
!tp->urg_data)
tcp_fast_path_on(tp);
}
__tcp_fast_path_on调用时机
在tcp_finish_connect中没有开启wscale的时候,会调用__tcp_fast_path_on来设置快速路径条件。
因为没有开启wscale,所以不需要调用tcp_fast_path_on。
为什么开启wscale-窗口因子 后就要关闭快速路径呢?
这时候只是客户端进入TCP_ESTABLISHED状态,服务端还在等待客户端最后一次ack才能发送数据。
因此不会收到服务端的数据,也就不用考虑快速路径了。
void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
tcp_set_state(sk, TCP_ESTABLISHED);
...
if (!tp->rx_opt.snd_wscale) //对方没有开启wscale,则开启快速路径
__tcp_fast_path_on(tp, tp->snd_wnd);
else
tp->pred_flags = 0; //目前不会收到服务端数据,不用开启快速路径
if (!sock_flag(sk, SOCK_DEAD)) {
sk->sk_state_change(sk); //唤醒connect
sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
}
}
tcp_fast_path_on调用时机
跟tcp_finish_connect一样,服务端进入TCP_ESTABLISHED状态的时候,也要尝试开启快速路径,因此调用tcp_fast_path_on设定快速路径判断条件
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
...
/* step 5: check the ACK field */
acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
FLAG_UPDATE_TS_RECENT) > 0;
switch (sk->sk_state) {
case TCP_SYN_RECV: //握手完成时的新建连接的初始状态
if (!acceptable)
return 1;
...
tcp_set_state(sk, TCP_ESTABLISHED);
sk->sk_state_change(sk);
...
tp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale; //snd_wnd已经缩放过
tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
if (tp->rx_opt.tstamp_ok)
tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;
...
tcp_fast_path_on(tp);
break;
}
...
}
tcp_fast_path_check调用时机
相比起前两个进入TCP_ESTABLISHED就设置pred_flags,因为建立连接前没有其他数据包作为判定依据。
tcp_fast_path_check主要是在连接过程中,有其他数据包作为判定依据的条件下调用:
- 没有乱序的数据包
- 接收窗口不为0
- 接收缓存未用完
- 非紧急数据
完成紧急数据的读取
紧急数据是由慢速路径处理,需要保持在慢速路径模式直到收完紧急数据,读完后就能检测是否能够开启fast path
int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t len, int nonblock, int flags, int *addr_len)
{
...
if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
tp->urg_data = 0;
tcp_fast_path_check(sk);
}
...
}
在慢速路径收到非乱序包的时候
tcp_data_queue是在慢速路径,对数据部分进行处理。
只有当前包是非乱序包,且接收窗口非0的时候,才能调用tcp_fast_path_check尝试开启快速路径
static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
bool fragstolen = false;
int eaten = -1;
if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq) { //没有数据部分,直接释放
__kfree_skb(skb);
return;
}
...
if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) { //非乱序包
if (tcp_receive_window(tp) == 0) //接受窗口满了,不能接受
goto out_of_window;
...
tcp_fast_path_check(sk); //当前是slow path, 尝试开启快速路径
...
}
...
}
当收到新的通告窗口值时
因为pred_flags中包含了窗口值,显然收到新的通告窗口时,需要更新
static int tcp_ack_update_window(struct sock *sk, const struct sk_buff *skb, u32 ack,
u32 ack_seq)
{
struct tcp_sock *tp = tcp_sk(sk);
int flag = 0;
u32 nwin = ntohs(tcp_hdr(skb)->window);
if (likely(!tcp_hdr(skb)->syn))
nwin <<= tp->rx_opt.snd_wscale;
if (tcp_may_update_window(tp, ack, ack_seq, nwin)) { //更新滑动窗口,或者收到新的窗口通知
flag |= FLAG_WIN_UPDATE;
tcp_update_wl(tp, ack_seq); //snd_wl1=ack_seq
if (tp->snd_wnd != nwin) { //窗口更新
tp->snd_wnd = nwin;
/* Note, it is the only place, where
* fast path is recovered for sending TCP.
*/
tp->pred_flags = 0;
tcp_fast_path_check(sk); //窗口更新了,要重新设置fast path检测条件
...
}
}
...
return flag;
}
快速路径包处理
在tcp_rcv_established中,通过快速路径判断后,
/*
* TCP receive function for the ESTABLISHED state.
*
* It is split into a fast path and a slow path. The fast path is
* disabled when:
* - A zero window was announced from us - zero window probing
* is only handled properly in the slow path.
* - Out of order segments arrived.
* - Urgent data is expected.
* - There is no buffer space left
* - Unexpected TCP flags/window values/header lengths are received
* (detected by checking the TCP header against pred_flags)
* - Data is sent in both directions. Fast path only supports pure senders
* or pure receivers (this means either the sequence number or the ack
* value must stay constant)
* - Unexpected TCP option.
*
* When these conditions are not satisfied it drops into a standard
* receive procedure patterned after RFC793 to handle all cases.
* The first three cases are guaranteed by proper pred_flags setting,
* the rest is checked inline. Fast processing is turned on in
* tcp_data_queue when everything is OK.
*/
void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, unsigned int len)
{
struct tcp_sock *tp = tcp_sk(sk);
skb_mstamp_get(&tp->tcp_mstamp);
if (unlikely(!sk->sk_rx_dst)) /* 路由为空,则重新设置路由 */
inet_csk(sk)->icsk_af_ops->sk_rx_dst_set(sk, skb);
/*
* Header prediction.
* The code loosely follows the one in the famous
* "30 instruction TCP receive" Van Jacobson mail.
*
* Van's trick is to deposit buffers into socket queue
* on a device interrupt, to call tcp_recv function
* on the receive process context and checksum and copy
* the buffer to user space. smart...
*
* Our current scheme is not silly either but we take the
* extra cost of the net_bh soft interrupt processing...
* We do checksum and copy also but from device to kernel.
*/
tp->rx_opt.saw_tstamp = 0;
/* pred_flags is 0xS?10 << 16 + snd_wnd
* if header_prediction is to be made
* 'S' will always be tp->tcp_header_len >> 2
* '?' will be 0 for the fast path, otherwise pred_flags is 0 to
* turn it off (when there are holes in the receive
* space for instance)
* PSH flag is ignored.
*/
/* 快路检查&& 序号正确 && ack序号正确 */
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&
!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {
int tcp_header_len = tp->tcp_header_len; /* tcp头部长度 */
/* Timestamp header prediction: tcp_header_len
* is automatically equal to th->doff*4 due to pred_flags
* match.
*/
/* Check timestamp */ /* 有时间戳选项 */
if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
/* No? Slow path! /* 解析时间戳选项失败,执行慢路 */
if (!tcp_parse_aligned_timestamp(tp, th))
goto slow_path;
/* If PAWS failed, check it more carefully in slow path
*/
/* 序号回转,执行慢路 */
if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)
goto slow_path;
/* DO NOT update ts_recent here, if checksum fails
* and timestamp was corrupted part, it will result
* in a hung connection since we will drop all
* future packets due to the PAWS test.
*/
}
if (len <= tcp_header_len) { /* 无数据 */
/* Bulk data transfer: sender */
if (len == tcp_header_len) {
/* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*//*
有时间戳选项
&& 所有接收的数据段均确认完毕
保存时间戳
*/
if (tcp_header_len ==
(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp);
/* We know that such packets are checksummed
* on entry.
*/ /* 输入/快速路径ack处理 */
tcp_ack(sk, skb, 0);
__kfree_skb(skb);
/* 检查是否有数据要发送,并检查发送缓冲区大小
收到ack了,给数据包一次发送机会,tcp_push_pending_frames*/
tcp_data_snd_check(sk);
return;
} else { /* Header too small */
/* 数据多小,比头部都小,错包 */
TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}
} else { /* 有数据 */
int eaten = 0;
bool fragstolen = false;
/* 进程上下文 */
if (tp->ucopy.task == current &&
/* 期待读取的和期待接收的序号一致 */
tp->copied_seq == tp->rcv_nxt &&
len - tcp_header_len <= tp->ucopy.len && /* 数据<= 待读取长度 */
/* 控制块被用户空间锁定 */
sock_owned_by_user(sk)) {
__set_current_state(TASK_RUNNING); /* 设置状态为running??? */
/* 拷贝数据到msghdr */
if (!tcp_copy_to_iovec(sk, skb, tcp_header_len)) {
/* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*/ /* 有时间戳选项&& 收到的数据段均已确认,更新时间戳 */
if (tcp_header_len ==
(sizeof(struct tcphdr) +
TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp);
tcp_rcv_rtt_measure_ts(sk, skb); /* 接收端RTT估算 */
__skb_pull(skb, tcp_header_len);
/* 更新期望接收的序号 */
tcp_rcv_nxt_update(tp, TCP_SKB_CB(skb)->end_seq);
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPHPHITSTOUSER);
eaten = 1;
}
}
/* 未拷贝数据到用户空间,或者拷贝失败----没有把数据放到ucopy中 */
if (!eaten) {
if (tcp_checksum_complete(skb))
goto csum_error;
/* skb长度> 预分配长度 */
if ((int)skb->truesize > sk->sk_forward_alloc)
goto step5;
/* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*/ /* 有时间戳选项,且数据均已确认完毕,则更新时间戳 */
if (tcp_header_len ==
(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)//在收到这个数据包之前,没有发送包也没有收到其他数据包,并且这个包不是乱序包
tcp_store_ts_recent(tp);
tcp_rcv_rtt_measure_ts(sk, skb);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPHPHITS);
/* Bulk data transfer: receiver */ /* 数据加入接收队列 添加数据到sk_receive_queue中 */
eaten = tcp_queue_rcv(sk, skb, tcp_header_len,
&fragstolen);
}
tcp_event_data_recv(sk, skb);//inet_csk_schedule_ack, 更新rtt
/* 确认序号确认了数据 */
if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
/* Well, only one small jumplet in fast path... */
tcp_ack(sk, skb, FLAG_DATA);/* 处理ack */
tcp_data_snd_check(sk); /* 检查是否有数据要发送,需要则发送 */
if (!inet_csk_ack_scheduled(sk)) /* 没有ack要发送 在tcp_event_data_recv标记过,但可能ack已经发出了,就不用检测是否要发送了*/
goto no_ack;
}
/* 检查是否有ack要发送,需要则发送 */
__tcp_ack_snd_check(sk, 0);
no_ack:
if (eaten)
kfree_skb_partial(skb, fragstolen);
sk->sk_data_ready(sk);
return;
}
------------------------------
慢速路径
pred_flags=0或者tcp包头匹配pred_flags失败的时候则为slow path处理
- 本地接受缓存不足通告0窗口的时候, 因为0窗口探测包需要在慢速路径处理
static u16 tcp_select_window(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
u32 old_win = tp->rcv_wnd;
u32 cur_win = tcp_receive_window(tp); //根据过去接收窗口值,当前还能通告给对方的接收窗口配额
u32 new_win = __tcp_select_window(sk); //根据接收缓存计算出的新窗口值
...
/* If we advertise zero window, disable fast path. */
if (new_win == 0) { //cur_win scale也为0
tp->pred_flags = 0; //开启0窗口, 关闭快速路径
if (old_win)
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPTOZEROWINDOWADV);
} else if (old_win == 0) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPFROMZEROWINDOWADV);
}
return new_win;
}
- 收到乱序包的时候
收到乱序包后会调用tcp_data_queue_ofo添加skb到ofo队列中
static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb)
{
...
/* Disable header prediction. */
tp->pred_flags = 0;
...
}
- 收到紧急数据
static void tcp_check_urg(struct sock *sk, const struct tcphdr *th)
{
...
tp->urg_data = TCP_URG_NOTYET;
tp->urg_seq = ptr;
/* Disable header prediction. */
tp->pred_flags = 0;
}
- 接受缓存不足
static int tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb,
unsigned int size)
{
if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || //接收缓存不够
!sk_rmem_schedule(sk, skb, size)) { //并且超过系统设置的最大分配空间了
if (tcp_prune_queue(sk) < 0) //尝试合并ofo/sk_receive_queue来腾出空间
return -1; //还是不够,超过sk_rcvbuf, 返回失败
while (!sk_rmem_schedule(sk, skb, size)) { //再次确认
if (!tcp_prune_ofo_queue(sk)) //释放ofo队列中的数据
return -1;
}
}
return 0;
}
static int tcp_prune_queue(struct sock *sk)
{
...
/* Massive buffer overcommit. */
tp->pred_flags = 0; //接收缓存还是不足,关闭快速路径
return -1;
}
慢速路径包处理
void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, unsigned int len)
{
...
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags && // 快速路径包头检测
TCP_SKB_CB(skb)->seq == tp->rcv_nxt && // 非乱序包
!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) { // 确认的序号是已经发送的包
//快速路径处理
...
}
slow_path:
/* 长度错误|| 校验和错误 */
if (len < (th->doff << 2) || tcp_checksum_complete(skb))
goto csum_error;
/* 无ack,无rst,无syn */
if (!th->ack && !th->rst && !th->syn)
goto discard;
/*
* Standard slow path.
/* 种种校验
*/
if (!tcp_validate_incoming(sk, skb, th, 1))
return;
step5:
/* 处理ack */
if (tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) < 0)
goto discard;
/* 计算rtt */
tcp_rcv_rtt_measure_ts(sk, skb);
/* Process urgent data. */
/* 处理紧急数据 */
tcp_urg(sk, skb, th);
/* step 7: process the segment text数据段处理 */
tcp_data_queue(sk, skb);
tcp_data_snd_check(sk);/* 发送数据检查,有则发送 */
tcp_ack_snd_check(sk);/* 发送ack检查,有则发送 */
return;
tcp_data_queue
- tcp_data_queue主要把非乱序包copy到ucopy或者sk_receive_queue中,并调用tcp_fast_path_check尝试开启快速路径
- 对重传包设置dsack,并快速ack回去
- 对于乱序包,如果包里有部分旧数据也设置dsack,并把乱序包添加到ofo队列中
tcp_data_queue_ofo
在新内核的实现中ofo队列实际上是一颗红黑树。
在tcp_data_queue_ofo中根据序号,查找到合适位置,合并或者添加到rbtree中。
同时设置dsack和sack,准备ack给发送方。
static int tcp_ack_update_window(struct sock *sk, const struct sk_buff *skb, u32 ack,
u32 ack_seq)
{
struct tcp_sock *tp = tcp_sk(sk);
int flag = 0;
u32 nwin = ntohs(tcp_hdr(skb)->window);
if (likely(!tcp_hdr(skb)->syn))
nwin <<= tp->rx_opt.snd_wscale;
if (tcp_may_update_window(tp, ack, ack_seq, nwin)) { //更新滑动窗口,或者收到新的窗口通知
flag |= FLAG_WIN_UPDATE;
tcp_update_wl(tp, ack_seq); //snd_wl1=ack_seq
if (tp->snd_wnd != nwin) { //窗口更新
tp->snd_wnd = nwin;
/* Note, it is the only place, where
* fast path is recovered for sending TCP.
*/
tp->pred_flags = 0;
tcp_fast_path_check(sk); //窗口更新了,要重新设置fast path检测条件
...
}
}
...
return flag;
}
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子