Linux2.6内核协议栈系列--TCP协议2.接收

1.排队机制

接收输入TCP报文时,有三个队列:

● 待处理队列

● 预排队队列

● 接收队列

接收队列包含了处理过的TCP数据段,也就是说,去除了全部的协议头,正准备将数据复制到用户应用程序。接收队列包含了所有按顺序接收的数据段,在其他两个队列中的TCP数据段则需要进一步处理。

TCP报文首先由tcp_v4_rcv()进行处理。该函数要决定是否需要处理报文或者在待处理队列和预排队队列中排队。

/* 传输层报文处理入口 */
int tcp_v4_rcv(struct sk_buff *skb)
{
...
	/*首先获取一个套接字旋转锁,当进入该例程时,要禁用下半部功能,因为该
	例程是从NET SoftIRQ中断调用的。*/
	bh_lock_sock(sk);/* 在软中断中对套接口加锁 */
	ret = 0;
	/* 如果进程没有访问传输控制块,则进行正常接收 */
	/*接着检查套接字是否处于使用状态。当有人在使用该套接字时,(sk)->sk_lock.owner
	为1.当对套接字执行读、写、修改操作时,套接字就会处于使用状态。*/
	if (!sock_owned_by_user(sk)) {
		/*调用tcp_prequeue将该TCP报文发往预排队队列。*/
		if (!tcp_prequeue(sk, skb))
			/*如果无法将TCP排队,就直接处理该数据段。*/
			ret = tcp_v4_do_rcv(sk, skb);
	} else
		/* 将报文添加到后备队列中,待用户进程解锁控制块时处理 */
		sk_add_backlog(sk, skb);
	bh_unlock_sock(sk);
...
}

2.tcp_rcv_established()的处理

这里不介绍全部处理细节,仅介绍处理和排队机制。首先讨论直接将数据复制到用户缓冲区的可能性。如果不可能,就去除TCP头,将数据段发往接收队列排队。

/* 当连接已经正常建立时,处理接收到的TCP报文 */
int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
			struct tcphdr *th, unsigned len)
{
	struct tcp_sock *tp = tcp_sk(sk);
...
} 
		else {/* 有数据负荷 */
			int eaten = 0;

			/* 判断正在接收的段是否可以直接复制到用户空间 */
			/* 正在接收的段的序号是否与尚未从内核空间复制到用户空间的段最
			前面的序号相等,即接收队列应当是空的 */
			/* TCP段中的用户数据长度小于用户空间缓存的剩余可用量 */
			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);

				/* 将SKB的数据复制到用户缓冲区 */
				if (!tcp_copy_to_iovec(sk, skb, tcp_header_len)) {
					
					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(tp, skb);/* 更新往返时间 */

					__skb_pull(skb, tcp_header_len);
					/* 下一个预期接收的段序号 */
					/*将tp->rcv_nxt更新为已处理报文的结束序列号。*/
					tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
					NET_INC_STATS_BH(LINUX_MIB_TCPHPHITSTOUSER);
					eaten = 1;
				}
			}
			if (!eaten) {
...
				/* 移动指针,跳过TCP头部,也就是去除TCP头 */
				__skb_pull(skb,tcp_header_len);
				/* 将数据包添加到接收队列中缓存起来,等待进程主动读取 */
				__skb_queue_tail(&sk->sk_receive_queue, skb);
				/* 设置skb的属主为当前套口,更新使用的接收缓存总量及预分配缓存长度 */
				sk_stream_set_owner_r(skb, sk);
				/* 更新rcv_nxt为该分段的结束序列号 */
				tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
			}
...
	return 0;
}

3.

内容太多了,未完待续

posted @ 2016-09-25 11:46  是非猫  阅读(998)  评论(0编辑  收藏  举报