TCP的Slow Path处是常规输入数据包的处理方式,根据套接字的状态来确定数据包的处理方式,在Slow Path处理方式中当套接字接受缓冲区已经满就不在接受新的Socket Buffer或当套接字忙(被其他进程锁住)时就将收到的数据包放入套接字阻塞等待队列backlog queue中,将数据包加入backlog queue队列的条件
(1)、输入的数据包包含数据段,不是ACK段。
(2)、数据包完好无损。
(3)、套接字缓冲区已满或者套接字被其他进程锁住,无法获取。
当数据包加入到backog queue队列后套接字就会被唤醒,进程调度器调度应用进程,开始从backlog queue队列中读取数据包缓冲区,如果缓冲区没有满、或者套接字没有被锁住就将数据包加入到receive_queue队列中,Slow Path处理由tcp_v4_do_rcv函数完成。
1、套接字的状态是ESTABLISHED
首先检查套接字的状态,如果套接字状态处于连接状态ESTABLISHED数据包就可以通过Fast Path路径处理,调用tcp_rcv_established函数完成连接状态的数据包处理,处理失败调转到复位标签reset处,回复对端一个复位消息。
...
//套接字状态是ESTABLISHED
if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
sock_rps_save_rxhash(sk, skb->rxhash);
TCP_CHECK_TIMER(sk);
//套接字状态为连接状态,缓冲区可以通过Fast Path路径处理
//由tcp_rcv_established函数处理, 将数据包加入到recieve_queue队列中
if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
//处理失败回复一个复位请求
goto reset;
}
TCP_CHECK_TIMER(sk);
return 0;
}
...
2、套接字状态是LISTEN
当套接字状态处于LISTEN监听状态,这时要查看接受的数据包是否是一个请求连接的SYN数据包,如果是一个有效的SYN,就将套接字的状态转变为接受状态,这个过程由tcp_v4_hnd_req完成,这个函数返回建立连接的自套接字struct sock *snk,如果snk不为NULL,就调用tcp_child_process函数在子套接字nsk上处理函数,如处理成功自套接字状态就编程ESTABLISHED,就可以接受发送数据包了。
...
//检查tcp头部长度,检查校验和
if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
goto csum_err;
//套接字是监听状态
if (sk->sk_state == TCP_LISTEN) {
//处理SYN请求,处理成功表示连接建立成功
//返回子套接字数据结构,此时套接字状态变成ESTABLISHED状态
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
if (!nsk)
goto discard;
if (nsk != sk) {
//在子套接字nsk上处理
//处理成功套接字状态变成ESTABLISHED,套接字就可以接受发送数据了
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return 0;
}
} else
sock_rps_save_rxhash(sk, skb->rxhash);
...
3、其他状态处理
如果套接字的状态不是ESTABLISHED、LISTEN就调用tcp_rev_state_precess处理套接字状态切换,如处理失败就返回一个复位数据包给对端,错误处理标志有三个:reset:复位套接字连接、discard:扔掉数据包、csumm_err更新接受错误统信息。
...
TCP_CHECK_TIMER(sk);
//套接字不是监听状态,状态切换处理
if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
TCP_CHECK_TIMER(sk);
return 0;
reset:
//复位请求
tcp_v4_send_reset(rsk, skb);
discard:
//扔掉数据包
kfree_skb(skb);
/* Be careful here. If this function gets more complicated and
* gcc suffers from register pressure on the x86, sk (in %ebx)
* might be destroyed here. This current version compiles correctly,
* but you have been warned.
*/
return 0;
csum_err:
//更新错误统计信息
TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
goto discard;
下图是TCP套接字状态切换图:
tcp_v4_do_rcv完整代码:
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk;
#ifdef CONFIG_TCP_MD5SIG
/*
* We really want to reject the packet as early as possible
* if:
* o We're expecting an MD5'd packet and this is no MD5 tcp option
* o There is an MD5 option and we're not expecting one
*/
if (tcp_v4_inbound_md5_hash(sk, skb))
goto discard;
#endif
if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
sock_rps_save_rxhash(sk, skb->rxhash);
TCP_CHECK_TIMER(sk);
//套接字状态为连接状态,缓冲区可以通过Fast Path路径处理
//由tcp_rcv_established函数处理
if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
//处理失败回复一个复位请求
goto reset;
}
TCP_CHECK_TIMER(sk);
return 0;
}
//检查tcp头部长度,检查校验和
if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
goto csum_err;
//套接字是监听状态
if (sk->sk_state == TCP_LISTEN) {
//处理SYN请求,处理成功表示连接建立成功
//返回子套接字数据结构,此时套接字状态变成ESTABLISHED状态
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
if (!nsk)
goto discard;
if (nsk != sk) {
//在子套接字nsk上处理
//处理成功套接字状态变成ESTABLISHED,套接字就可以接受发送数据了
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return 0;
}
} else
sock_rps_save_rxhash(sk, skb->rxhash);
TCP_CHECK_TIMER(sk);
//套接字不是监听状态,状态切换处理
if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
TCP_CHECK_TIMER(sk);
return 0;
reset:
//复位请求
tcp_v4_send_reset(rsk, skb);
discard:
//扔掉数据包
kfree_skb(skb);
/* Be careful here. If this function gets more complicated and
* gcc suffers from register pressure on the x86, sk (in %ebx)
* might be destroyed here. This current version compiles correctly,
* but you have been warned.
*/
return 0;
csum_err:
//更新错误统计信息
TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}