TCP协议ESTABLISHED状态处理

TCP协议是可靠的、快速传递数据的协议,当套接字状态是ESTABLISHED状态表明两端已经建立连接,可以互相传送数据了,tcp_v4_do_rcv接受到数据后首先检查套接字状态,如果是ESTABLISHED就交给tcp_rcv_established函数处理具体数据接受过程。如果是LISTEN就由tcp_v4_hnd_req处理,如果是其他状态就由tcp_rcv_state+process处理,关系图如下。

TCP协议ESTABLISHED状态处理_第1张图片 tcp建立连接和数据接受关系图

1、数据走Fast Path路径的条件

linux提供了Fast Path处理路径来加速TCP数据传送,linux使用协议头预定向来选择那些数据包应用放在Fast Path路径处理,条件:

a、数据包接受顺序正确

b、数据包不需要进一步分析,可以直接复制到应用程序的接受缓冲区中。

Fast Path处理过程是在应用程序进程的现场执行,在应用进程现场执行一个套接字的读操作函数,通过Fast Path处理速度是最快的。正确进入Fast Path路径的关键是选择最有可能包含常规数据的数据包,这样的数据包不需要额外的处理,为了完成这个选择,linux内核使用协议头预定向处理,在收到数据包之前跨苏标记这些候选的数据包,协议头预定向处理是计算协议头中预定向标志,该标志决定数据包是走Fast Path处理还是Slow Path处理,协议头预定向值由以下值计算:prediction flags = hlen << 26^ackw^SND.WND,hlen是协议头的长度,ackw是ACK<<<20位后的布尔变量。预定向值保存在struct tcp_sock数据结构的tp->pred_flags数据域中,预定向标志计算函数是__tcp_fast_path_on。虽然是在ESTABLISHED状态中处理但还是要做以下检查

a、TCP连接的接受段接受窗口大小是0,对于窗口0的探测处理走Slow Path处理。

b、数据包的顺序不正确,也会走Slow Path处理。

c、如果用户地址接受缓冲区没有空间也会走Slow Path处理。

d、协议头预定向过程失败也会走Slow Path处理。

e、Fast Path只支持不会在改向的数据传输,如果要把数据包传送到别的路径,就会走Slow Path处理。

f、数据包除了时间戳选项还有其他选项,也会走Slow Path处理。

2、tcp_rcv_established函数

(1)、检查数据包是否满足Fast Path处理条件

主要检查以上Fast Path处理的条件,只要其中一项不满足就走Slow Path处理。

int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
			struct tcphdr *th, unsigned len)
{
	struct tcp_sock *tp = tcp_sk(sk);
	int res;
	tp->rx_opt.saw_tstamp = 0;

	//预定向标志和输入数据段的标志比较
	//数据段序列号是否正确
	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;

		/* Check timestamp */
		//时间戳选项之外如果还有别的选项就送给Slow Path处理
		if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
			/* No? Slow path! */
			if (!tcp_parse_aligned_timestamp(tp, th))
				goto slow_path;

			//对数据包做PAWS快速检查,如果检查走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;
		}

		//数据包长度太小
		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.
				 */
				tcp_ack(sk, skb, 0);
				__kfree_skb(skb);
				tcp_data_snd_check(sk);
				return 0;
			} else { /* Header too small */
				TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
				goto discard;
			}
		} else {
			int eaten = 0;
			int copied_early = 0;

...

}

(2)确定运行现场

Fast Path路径运行在应用程序进程现场,查看当前应用进程是否是等待数据的进程,linux内核为当前进程的全局指针current总是指向当前正在运行的进程,当前运行的进程是:进程状态是TASK_RUNNING,放在运行进程链表中的第一个进程。查看当前套接字是否运行在当前用户进程现场,通过比较ucopy数据结构中的进程指针是否与当前运行进程相同。存放在struct ucopy数据结构中的当前进程是tcp_recvmsg,tcp_recvmsg是套接字层的接受函数,由应用层调用,最后调用tcp_copy_to_iovec将数据包直接复制到应用层,如果复制成功要清除prequeue队列中已经复制的数据,腾出空间。

...

//当前进程是否有锁定
				//当前进程的全局指针current
				//tp->ucopy.task指针是否等于当前进程
				if (tp->ucopy.task == current &&
				    sock_owned_by_user(sk) && !copied_early) {
					__set_current_state(TASK_RUNNING);
				//将数据包复制到应用层空间
					if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
						eaten = 1;
				}
				//复制成功
				if (eaten) {
					/* 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);

					__skb_pull(skb, tcp_header_len);
					tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
					NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);
				}
				//清除prequeue队列中已经复制的数据包,并回复ack
				if (copied_early)
					tcp_cleanup_rbuf(sk, skb->len);       

...

(3)、复制不成功

如果复制不能可能是应用层没有进程运行、向用户层复制数据失败,如是向用户层复制数据失败可能是校验和不正确,就需要重新计算校验和,如果套接字没有多余空间就走Slow Path路劲处理。

...

            //复制不成功
			if (!eaten) {
				//从新计算校验和
				if (tcp_checksum_complete_user(sk, skb))
					goto csum_error;

				/* 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);
                    
                  //套接字空间不足
				if ((int)skb->truesize > sk->sk_forward_alloc)
					goto step5;

...

(4)连续大量复制数据

LINUX_MIB_TCPHPHITS是大块数据传送,应将协议头从skb中移走,把数据段中的数据部分放入到sk_receive_queue常规接受缓冲区队列中。

...

                //大块数据传送
				NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);

				/* Bulk data transfer: receiver */
				//去掉tcp头部
				__skb_pull(skb, tcp_header_len);
				//将数据包加入到sk_receive_queue队列中
				__skb_queue_tail(&sk->sk_receive_queue, skb);
				skb_set_owner_r(skb, sk);
				tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;

...

(5)数据接受后处理

到目前接受的是有效数据,tcp_event_data_recv更新延迟回答时钟超时间隔值,tcp_ack处理输入ack,如果不需要发送ack就调转到no_ack标签处,sk->sk_data_read函数指针指向data_ready,表明套接字已经准备号下一次应用读。

...

        //更新延迟回答时钟超时间隔值
			tcp_event_data_recv(sk, skb);

			if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
				/* Well, only one small jumplet in fast path... */
            //收到数据后回复ack确认
				tcp_ack(sk, skb, FLAG_DATA);
				tcp_data_snd_check(sk);
				if (!inet_csk_ack_scheduled(sk))
					goto no_ack;
			}

			
			if (!copied_early || tp->rcv_nxt != tp->rcv_wup)
				__tcp_ack_snd_check(sk, 0);
no_ack:
#ifdef CONFIG_NET_DMA
			if (copied_early)
				__skb_queue_tail(&sk->sk_async_wait_queue, skb);
			else
#endif
			if (eaten)
				__kfree_skb(skb);
			else
				//no_ack标签表明套接字已经准备好下一次应用读
				sk->sk_data_ready(sk, 0);
			return 0;
		}
	}

...

(6)、Slow Path处理

函数由内核内部bottom half调用或者是prequeue队列不能处理就会进入Slow Path处理,调用tcp_validate_incoming做检验。

...

slow_path:
    //重新计算校验和
	if (len < (th->doff << 2) || tcp_checksum_complete_user(sk, skb))
		goto csum_error;

	/*
	 *	Standard slow path.
	 */

	res = tcp_validate_incoming(sk, skb, th, 1);
	if (res <= 0)
		return -res;

...

(7)、最后处理

处理URG标志,然后调用tcp_data_queue将数据复制到应用层或者放入提哦啊接字常规接受队列中。

...

step5:
	if (th->ack && tcp_ack(sk, skb, FLAG_SLOWPATH) < 0)
		goto discard;

	tcp_rcv_rtt_measure_ts(sk, skb);

	/* Process urgent data. */
	//紧急数据段处理
	tcp_urg(sk, skb, th);

	/* step 7: process the segment text */
	//根据情况将数据复制到应用层或者
	//将数据加入sk_receive_queue常规队列中
	tcp_data_queue(sk, skb);

	tcp_data_snd_check(sk);
	tcp_ack_snd_check(sk);
	return 0;


...

tcp_rcv_established完整代码

int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
			struct tcphdr *th, unsigned len)
{
	struct tcp_sock *tp = tcp_sk(sk);
	int res;

	/*
	 *	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.
	 */
	//预定向标志和输入数据段的标志比较
	//数据段序列号是否正确
	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;

		/* Timestamp header prediction: tcp_header_len
		 * is automatically equal to th->doff*4 due to pred_flags
		 * match.
		 */

		/* Check timestamp */
		//时间戳选项之外如果还有别的选项就送给Slow Path处理
		if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
			/* No? Slow path! */
			if (!tcp_parse_aligned_timestamp(tp, th))
				goto slow_path;

			//对数据包做PAWS快速检查,如果检查走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.
				 */
				tcp_ack(sk, skb, 0);
				__kfree_skb(skb);
				tcp_data_snd_check(sk);
				return 0;
			} else { /* Header too small */
				TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
				goto discard;
			}
		} else {
			int eaten = 0;
			int copied_early = 0;

			//tp->copied_seq表示未读的数据包序列号
			//tp->rcv_nxt表示下一个期望读取的数据包序列号
			//len-tcp_header_len小于tp->ucpoy.len表示数据包还没有复制完
			if (tp->copied_seq == tp->rcv_nxt &&
			    len - tcp_header_len <= tp->ucopy.len) {
#ifdef CONFIG_NET_DMA
				if (tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {
					copied_early = 1;
					eaten = 1;
				}
#endif
				//当前进程是否有锁定
				//当前进程的全局指针current
				//tp->ucopy.task指针是否等于当前进程
				if (tp->ucopy.task == current &&
				    sock_owned_by_user(sk) && !copied_early) {
					__set_current_state(TASK_RUNNING);
				//将数据包复制到应用层空间
					if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
						eaten = 1;
				}
				//复制成功
				if (eaten) {
					/* 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);

					__skb_pull(skb, tcp_header_len);
					tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
					NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);
				}
				//清除prequeue队列中已经复制的数据包,并回复ack
				if (copied_early)
					tcp_cleanup_rbuf(sk, skb->len);
			}
			//复制不成功
			if (!eaten) {
				//从新计算校验和
				if (tcp_checksum_complete_user(sk, skb))
					goto csum_error;

				/* 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);

				if ((int)skb->truesize > sk->sk_forward_alloc)
					goto step5;

				//大块数据传送
				NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);

				/* Bulk data transfer: receiver */
				//去掉tcp头部
				__skb_pull(skb, tcp_header_len);
				//将数据包加入到sk_receive_queue队列中
				__skb_queue_tail(&sk->sk_receive_queue, skb);
				skb_set_owner_r(skb, sk);
				tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
			}

			//更新延迟回答时钟超时间隔值
			tcp_event_data_recv(sk, skb);

			if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
				/* Well, only one small jumplet in fast path... */
				tcp_ack(sk, skb, FLAG_DATA);
				tcp_data_snd_check(sk);
				if (!inet_csk_ack_scheduled(sk))
					goto no_ack;
			}

			//收到数据后回复ack确认
			if (!copied_early || tp->rcv_nxt != tp->rcv_wup)
				__tcp_ack_snd_check(sk, 0);
no_ack:
#ifdef CONFIG_NET_DMA
			if (copied_early)
				__skb_queue_tail(&sk->sk_async_wait_queue, skb);
			else
#endif
			if (eaten)
				__kfree_skb(skb);
			else
				//no_ack标签表明套接字已经准备好下一次应用读
				sk->sk_data_ready(sk, 0);
			return 0;
		}
	}

slow_path:
	if (len < (th->doff << 2) || tcp_checksum_complete_user(sk, skb))
		goto csum_error;

	/*
	 *	Standard slow path.
	 */

	res = tcp_validate_incoming(sk, skb, th, 1);
	if (res <= 0)
		return -res;

step5:
	if (th->ack && tcp_ack(sk, skb, FLAG_SLOWPATH) < 0)
		goto discard;

	tcp_rcv_rtt_measure_ts(sk, skb);

	/* Process urgent data. */
	//紧急数据段处理
	tcp_urg(sk, skb, th);

	/* step 7: process the segment text */
	//根据情况将数据复制到应用层或者
	//将数据加入sk_receive_queue常规队列中
	tcp_data_queue(sk, skb);

	tcp_data_snd_check(sk);
	tcp_ack_snd_check(sk);
	return 0;

csum_error:
	TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);

discard:
	__kfree_skb(skb);
	return 0;
}

 

你可能感兴趣的:(网络,tcp,协议栈,个人笔记)