kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

Linux TCP数据包接收处理 tcp_recvmsg

http://blog.csdn.net/mrpre/article/details/33347221

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
/* 
 *    This routine copies from a sock struct into the user buffer.
 *
 *    Technical note: in 2.3 we work on _locked_ socket, so that
 *    tricks with *seq access order and skb->users are not required.
 *    Probably, code can be easily improved even more.
 */

int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		size_t len, int nonblock, int flags, int *addr_len)
{
	struct tcp_sock *tp = tcp_sk(sk);
	int copied = 0;
	u32 peek_seq;
	u32 *seq;
	unsigned long used;
	int err;
	int target;    /* Read at least this many bytes */
	long timeo;
	struct task_struct *user_recv = NULL;
	int copied_early = 0;
	struct sk_buff *skb;
	u32 urg_hole = 0;

	//功能:“锁住sk”,并非真正的加锁,而是执行sk->sk_lock.owned = 1 
	//目的:这样软中断上下文能够通过owned ,判断该sk是否处于进程上下文。
	//提供一种同步机制。
	lock_sock(sk);

	TCP_CHECK_TIMER(sk);

	err = -ENOTCONN;
	if (sk->sk_state == TCP_LISTEN)
		goto out;

	//获取延迟,如果用户设置为非阻塞,那么timeo ==0000 0000 0000 0000
	//如果用户使用默认recv系统调用
	//则为阻塞,此时timeo ==0111 1111 1111 1111
	//timeo 就2个值
	timeo = sock_rcvtimeo(sk, nonblock);

	/* Urgent data needs to be handled specially. */
	if (flags & MSG_OOB)
		goto recv_urg;

	//待拷贝的下一个序列号
	seq = &tp->copied_seq;

	//设置了MSG_PEEK,表示不让数据从缓冲区移除,目的是下一次调用recv函数
	//仍然能够读到相同数据
	if (flags & MSG_PEEK) {
		peek_seq = tp->copied_seq;
		seq = &peek_seq;
	}

	//如果设置了MSG_WAITALL,则target  ==len,即recv函数中的参数len
	//如果没设置MSG_WAITALL,则target  == 1
	target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);

	//大循环
	do {
		u32 offset;

		/* Are we at urgent data? Stop if we have read anything or have SIGURG pending. */
		if (tp->urg_data && tp->urg_seq == *seq) {
			if (copied)
				break;
			if (signal_pending(current)) {
				copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
				break;
			}
		}

		/* Next get a buffer. */

		//小循环
		skb_queue_walk(&sk->sk_receive_queue, skb) {
			/* Now that we have two receive queues this
				* shouldn't happen.
				*/
			if (WARN(before(*seq, TCP_SKB_CB(skb)->seq),
								KERN_INFO "recvmsg bug: copied %X "
									      "seq %X rcvnxt %X fl %X\n", *seq,
									      TCP_SKB_CB(skb)->seq, tp->rcv_nxt,
									      flags))
				break;

			//如果用户的缓冲区(即用户malloc的buf)长度够大,offset一般是0。
			//即 “下次准备拷贝数据的序列号”==此时获取报文的起始序列号
			//什么情况下offset >0呢?很简答,如果用户缓冲区12字节,而这个skb有120字节
			//那么一次recv系统调用,只能获取skb中的前12个字节,下一次执行recv系统调用
			//offset就是12了,offset表示从第12个字节开始读取数据,前12个字节已经读取了。
			//那这个"已经读取12字节"这个消息,存在哪呢?
			//在*seq = &tp->copied_seq;中
			offset = *seq - TCP_SKB_CB(skb)->seq;
			if (tcp_hdr(skb)->syn)
				offset--;
			if (offset < skb->len)
				goto found_ok_skb;
			if (tcp_hdr(skb)->fin)
				goto found_fin_ok;
			WARN(!(flags & MSG_PEEK), KERN_INFO "recvmsg bug 2: "
					"copied %X seq %X rcvnxt %X fl %X\n",
					*seq, TCP_SKB_CB(skb)->seq,
					tp->rcv_nxt, flags);
		}

		//执行到了这里,表明小循环中break了,既然break了,说明sk_receive_queue中
		//已经没有skb可以读取了
		//如果没有执行到这里说明前面的小循环中执行了goto,读到有用的skb,或者读到fin都会goto。
		//没有skb可以读取,说明什么?
		//可能性1:当用户第一次调用recv时,压根没有数据到来
		//可能性2:skb->len一共20字节,假设用户调用一次 recv,读取12字节,再调用recv,
		//读取12字节,此时skb由于上次已经被读取了12字节,只剩下8字节。
		//于是代码的逻辑上,再会要求获取skb,来读取剩下的8字节。

		//可能性1的情况下,copied == 0,肯定不会进这个if。后续将执行休眠
		//可能性2的情况下,情况比较复杂。可能性2表明数据没有读够用户想要的len长度
		//虽然进程上下文中,没有读够数据,但是可能我们在读数据的时候
		//软中断把数据放到backlog队列中了,而backlog对队列中的数据或许恰好让我们读够数
		//据。

		//copied了数据的,copied肯定>=1,而target 是1或者len
		//copied只能取0(可能性1),或者0~len(可能性2)
		//copied >= target 表示我们取得我们想要的数据了,何必进行休眠,直接return
		//如果copied 没有达到我们想要的数据,则看看sk_backlog是否为空
		//空的话,尽力了,只能尝试休眠
		//非空的话,还有一线希望,我们去sk_backlog找找数据,看看是否能够达到我们想要的
		//数据大小

		//我觉得copied == target是会出现的,但是出现的话,也不会进现在这个流程
		//,如有不对,请各位大神指正,告诉我
		//说明情况下copied == target

		/* Well, if we have backlog, try to process it now yet. */
		if (copied >= target && !sk->sk_backlog.tail)
			break;


		if (copied) {
			//可能性2,拷贝了数据,但是没有拷贝到指定大小
			if (sk->sk_err ||
							sk->sk_state == TCP_CLOSE ||
							(sk->sk_shutdown & RCV_SHUTDOWN) ||
							!timeo ||
							signal_pending(current))
				break;
		} else {
			//可能性1
			if (sock_flag(sk, SOCK_DONE))
				break;

			if (sk->sk_err) {
				copied = sock_error(sk);
				break;
			}

			if (sk->sk_shutdown & RCV_SHUTDOWN)
				break;

			if (sk->sk_state == TCP_CLOSE) {
				if (!sock_flag(sk, SOCK_DONE)) {
					/* This occurs when user tries to read
						* from never connected socket.
						*/
					copied = -ENOTCONN;
					break;
				}
				break;
			}

			//是否是阻塞的,不是,就return了。
			if (!timeo) {
				copied = -EAGAIN;
				break;
			}

			if (signal_pending(current)) {
				copied = sock_intr_errno(timeo);
				break;
			}
		}

		tcp_cleanup_rbuf(sk, copied);

		//sysctl_tcp_low_latency 默认0tp->ucopy.task == user_recv肯定也成立

		if (!sysctl_tcp_low_latency && tp->ucopy.task == user_recv) {
			/* Install new reader */
			if (!user_recv && !(flags & (MSG_TRUNC | MSG_PEEK))) {
				user_recv = current;
				tp->ucopy.task = user_recv;
				tp->ucopy.iov = msg->msg_iov;
			}

			tp->ucopy.len = len;

			WARN_ON(tp->copied_seq != tp->rcv_nxt &&
				!(flags & (MSG_PEEK | MSG_TRUNC)));

			/* Ugly... If prequeue is not empty, we have to
				* process it before releasing socket, otherwise
				* order will be broken at second iteration.
				* More elegant solution is required!!!
				*
				* Look: we have the following (pseudo)queues:
				*
				* 1. packets in flight
				* 2. backlog
				* 3. prequeue
				* 4. receive_queue
				*
				* Each queue can be processed only if the next ones
				* are empty. At this point we have empty receive_queue.
				* But prequeue _can_ be not empty after 2nd iteration,
				* when we jumped to start of loop because backlog
				* processing added something to receive_queue.
				* We cannot release_sock(), because backlog contains
				* packets arrived _after_ prequeued ones.
				*
				* Shortly, algorithm is clear --- to process all
				* the queues in order. We could make it more directly,
				* requeueing packets from backlog to prequeue, if
				* is not empty. It is more elegant, but eats cycles,
				* unfortunately.
				*/

			if (!skb_queue_empty(&tp->ucopy.prequeue))
				goto do_prequeue;

			/* __ Set realtime policy in scheduler __ */
		}

		if (copied >= target) {
			/* Do not sleep, just process backlog. */
			release_sock(sk);
			lock_sock(sk);
		} else
					sk_wait_data(sk, &timeo); 
		//在此处睡眠了,将在tcp_prequeue函数中调用wake_up_interruptible_poll唤醒
		
		//软中断会判断用户是正在读取检查并且睡眠了,如果是的话,就直接把数据拷贝
		//到prequeue队列,然后唤醒睡眠的进程。因为进程睡眠,表示没有读到想要的字节数
		//此时,软中断有数据到来,直接给进程,这样进程就能以最快的速度被唤醒。


		if (user_recv) {
			int chunk;

			/* __ Restore normal policy in scheduler __ */

			if ((chunk = len - tp->ucopy.len) != 0) {
				NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMBACKLOG, chunk);
				len -= chunk;
				copied += chunk;
			}

			if (tp->rcv_nxt == tp->copied_seq &&
							!skb_queue_empty(&tp->ucopy.prequeue)) {
do_prequeue:
				tcp_prequeue_process(sk);

				if ((chunk = len - tp->ucopy.len) != 0) {
					NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
					len -= chunk;
					copied += chunk;
				}
			}
		}
		if ((flags & MSG_PEEK) &&
						(peek_seq - copied - urg_hole != tp->copied_seq)) {
			if (net_ratelimit())
				printk(KERN_DEBUG "TCP(%s:%d): Application bug, race in MSG_PEEK.\n",
									      current->comm, task_pid_nr(current));
			peek_seq = tp->copied_seq;
		}
		continue;

	found_ok_skb:
		/* Ok so how much can we use? */
		//skb中还有多少聚聚没有拷贝。
		//正如前面所说的,offset是上次已经拷贝了的,这次从offset开始接下去拷贝
				used = skb->len - offset;
		//很有可能used的大小,即skb剩余长度,依然大于用户的缓冲区大小(len)。所以依然
		//只能拷贝len长度。一般来说,用户还得执行一次recv系统调用。直到skb中的数据读完
		if (len < used)
			used = len;

		/* Do we have urgent data here? */
		if (tp->urg_data) {
			u32 urg_offset = tp->urg_seq - *seq;
			if (urg_offset < used) {
				if (!urg_offset) {
					if (!sock_flag(sk, SOCK_URGINLINE)) {
						++*seq;
						urg_hole++;
						offset++;
						used--;
						if (!used)
							goto skip_copy;
					}
				} else
					used = urg_offset;
			}
		}

		if (!(flags & MSG_TRUNC)) {
			{
				//一般都会进这个if,进行数据的拷贝,把能够读到的数据,放到用户的缓冲区
				err = skb_copy_datagram_iovec(skb, offset,
						msg->msg_iov, used);
				if (err) {
					/* Exception. Bailout! */
					if (!copied)
						copied = -EFAULT;
					break;
				}
			}
		}

		//更新标志位,seq 是指针,指向了tp->copied_seq
		//used是我们有能力拷贝的数据大小,即已经拷贝到用户缓冲区的大小
		//正如前面所说,如果用户的缓冲区很小,一次recv拷贝不玩skb中的数据,
		//我们需要保存已经拷贝了的大小,下次recv时,从这个大小处继续拷贝。
		//所以需要更新copied_seq。
		*seq += used;
		copied += used;
		len -= used;

		tcp_rcv_space_adjust(sk);

skip_copy:
		if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
			tp->urg_data = 0;
			tcp_fast_path_check(sk);
		}

		//这个就是判断我们是否拷贝完了skb中的数据,如果没有continue
		//这种情况下,len经过 len -= used; ,已经变成0,所以continue的效果相当于
		//退出了这个大循环。可以理解,你只能拷贝len长度,拷贝完之后,那就return了。

		//还有一种情况used + offset ==  skb->len,表示skb拷贝完了。这时我们只需要释放skb
		//下面会讲到
		if (used + offset < skb->len)
			continue;

		//看看这个数据报文是否含有fin,含有fin,则goto到found_fin_ok
		if (tcp_hdr(skb)->fin)
			goto found_fin_ok;

		//执行到这里,标明used + offset ==  skb->len,报文也拷贝完了,那就把skb摘链释放
		if (!(flags & MSG_PEEK)) {
			sk_eat_skb(sk, skb, copied_early);
			copied_early = 0;
		}
		//这个cintinue不一定是退出大循环,可能还会执行循环。
		//假设用户设置缓冲区12字节,你skb->len长度20字节。
		//第一次recv读取了12字节,skb剩下8,下一次调用recv再想读取12,
		//但是只能读取到这8字节了。
		//此时len 变量长度为4,那么这个continue依旧在这个循环中,
		//函数还是再次从do开始,使用skb_queue_walk,找skb
		//如果sk_receive_queue中skb仍旧有,那么继续读,直到len == 0
		//如果没有skb了,我们怎么办?我们的len还有4字节怎么办?
		//这得看用户设置的recv函数阻塞与否,即和timeo变量相关了。
		continue;

	found_fin_ok:
		/* Process the FIN. */
		++*seq;
		if (!(flags & MSG_PEEK)) {
			//把skb从sk_receive_queue中摘链
			sk_eat_skb(sk, skb, copied_early);
			copied_early = 0;
		}
		break;
	} while (len > 0);

	//到这里是大循环退出
	//休眠过的进程,然后退出大循环 ,才满足 if (user_recv) 条件
	if (user_recv) {
		if (!skb_queue_empty(&tp->ucopy.prequeue)) {
			int chunk;

			tp->ucopy.len = copied > 0 ? len : 0;

			tcp_prequeue_process(sk);

			if (copied > 0 && (chunk = len - tp->ucopy.len) != 0) {
				NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
				len -= chunk;
				copied += chunk;
			}
		}

		//数据读取完毕,清零
		tp->ucopy.task = NULL;
		tp->ucopy.len = 0;
	}

	/* According to UNIX98, msg_name/msg_namelen are ignored
		* on connected socket. I was just happy when found this 8) --ANK
		*/

	/* Clean up data we have read: This will do ACK frames. */
	//很重要,将更新缓存,并且适当的时候发送ack
	tcp_cleanup_rbuf(sk, copied);

	TCP_CHECK_TIMER(sk);
	release_sock(sk);
	return copied;

out:
	TCP_CHECK_TIMER(sk);
	release_sock(sk);
	return err;

recv_urg:
	err = tcp_recv_urg(sk, msg, len, flags);
	goto out;
}