TCP层recvmsg系统调用的实现分析

概述

recvmsg系统调用在tcp层的实现是tcp_recvmsg函数,该函数完成从接收队列中读取数据复制到用户空间的任务;函数在执行过程中会锁定控制块,避免软中断在tcp层的影响;函数会涉及从接收队列receive_queue,预处理队列prequeue和后备队列backlog中读取数据;其中从prequeue和backlog中读取的数据,还需要经过sk_backlog_rcv回调,该回调的实现为tcp_v4_do_rcv,实际上是先缓存到队列中,然后需要读取的时候,才进入协议栈处理,此时,是在进程上下文执行的,因为会设置tp->ucopy.task=current,在协议栈处理过程中,会直接将数据复制到用户空间;

代码分析
  1 int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
  2         int flags, int *addr_len)
  3 {
  4     struct tcp_sock *tp = tcp_sk(sk);
  5     int copied = 0;
  6     u32 peek_seq;
  7     u32 *seq;
  8     unsigned long used;
  9     int err;
 10     int target;        /* Read at least this many bytes */
 11     long timeo;
 12     struct task_struct *user_recv = NULL;
 13     struct sk_buff *skb, *last;
 14     u32 urg_hole = 0;
 15 
 16     if (unlikely(flags & MSG_ERRQUEUE))
 17         return inet_recv_error(sk, msg, len, addr_len);
 18 
 19     if (sk_can_busy_loop(sk) && skb_queue_empty(&sk->sk_receive_queue) &&
 20         (sk->sk_state == TCP_ESTABLISHED))
 21         sk_busy_loop(sk, nonblock);
 22 
 23     /* 传输层上锁,避免软中断影响 */
 24     lock_sock(sk);
 25 
 26     err = -ENOTCONN;
 27     /* LISTEN状态,不允许读取数据 */
 28     if (sk->sk_state == TCP_LISTEN)
 29         goto out;
 30 
 31     /* 获取阻塞读取的超时时间,非阻塞为0 */
 32     timeo = sock_rcvtimeo(sk, nonblock);
 33 
 34     /* Urgent data needs to be handled specially. */
 35     /* 带外数据读取 */
 36     if (flags & MSG_OOB)
 37         goto recv_urg;
 38 
 39     /* 修复模式 */
 40     if (unlikely(tp->repair)) {
 41         err = -EPERM;
 42         if (!(flags & MSG_PEEK))
 43             goto out;
 44 
 45         if (tp->repair_queue == TCP_SEND_QUEUE)
 46             goto recv_sndq;
 47 
 48         err = -EINVAL;
 49         if (tp->repair_queue == TCP_NO_QUEUE)
 50             goto out;
 51 
 52         /* 'common' recv queue MSG_PEEK-ing */
 53     }
 54 
 55     /* 待读取的序号 */
 56     seq = &tp->copied_seq;
 57 
 58     /* 只查看数据 */
 59     if (flags & MSG_PEEK) {
 60         /* 复制一个序号用于记录 */
 61         peek_seq = tp->copied_seq;
 62         seq = &peek_seq;
 63     }
 64 
 65     /* 
 66         确定读取长度,设置了MSG_WAITALL则
 67         使用用户输入的len,否则使用低潮限度 
 68     */
 69     target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);
 70 
 71     do {
 72         u32 offset;
 73 
 74         /* Are we at urgent data? Stop if we have read anything or have SIGURG pending. */
 75         /* 读到了带外数据 */
 76         if (tp->urg_data && tp->urg_seq == *seq) {
 77             /* 之前已经读取了部分数据,跳出 */
 78             if (copied)
 79                 break;
 80             /* 用户进程有信号待处理,跳出 */
 81             if (signal_pending(current)) {
 82                 copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
 83                 break;
 84             }
 85         }
 86 
 87         /* Next get a buffer. */
 88 
 89         /* 获取队尾 */
 90         last = skb_peek_tail(&sk->sk_receive_queue);
 91 
 92         /* 遍历接收队列,找到满足读取的skb */
 93         skb_queue_walk(&sk->sk_receive_queue, skb) {
 94             last = skb;
 95             /* Now that we have two receive queues this
 96              * shouldn't happen.
 97              */
 98             /* 队列中序号比待读取的大 */
 99             if (WARN(before(*seq, TCP_SKB_CB(skb)->seq),
100                  "recvmsg bug: copied %X seq %X rcvnxt %X fl %X\n",
101                  *seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt,
102                  flags))
103                 break;
104 
105             /* 获取序号偏移*/
106             offset = *seq - TCP_SKB_CB(skb)->seq;
107 
108             /* 有syn标记,再减1 */
109             if (unlikely(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)) {
110                 pr_err_once("%s: found a SYN, please report !\n", __func__);
111                 offset--;
112             }
113             /* 偏移小于skb数据长度,找到 */
114             if (offset < skb->len)
115                 goto found_ok_skb;
116 
117             /* 有fin标记,跳转到fin处理 */
118             if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
119                 goto found_fin_ok;
120             WARN(!(flags & MSG_PEEK),
121                  "recvmsg bug 2: copied %X seq %X rcvnxt %X fl %X\n",
122                  *seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt, flags);
123         }
124 
125         /* Well, if we have backlog, try to process it now yet. */
126 
127         /* 读完目标数据&& backlog队列为空 */
128         if (copied >= target && !sk->sk_backlog.tail)
129             break;
130 
131         /* 未读完目标数据,或者读完目标数据,队列不为空 */
132 
133         /* 已经读取了数据 */
134         if (copied) {
135             /* 有错误或者关闭或者有信号,跳出 */
136             if (sk->sk_err ||
137                 sk->sk_state == TCP_CLOSE ||
138                 (sk->sk_shutdown & RCV_SHUTDOWN) ||
139                 !timeo ||
140                 signal_pending(current))
141                 break;
142         } else {
143             /* 会话终结*/
144             if (sock_flag(sk, SOCK_DONE))
145                 break;
146 
147             /* 有错误 */
148             if (sk->sk_err) {
149                 copied = sock_error(sk);
150                 break;
151             }
152 
153             /* 关闭接收端 */
154             if (sk->sk_shutdown & RCV_SHUTDOWN)
155                 break;
156 
157             /* 连接关闭 */
158             if (sk->sk_state == TCP_CLOSE) {
159                 /* 不在done状态,可能再读一个连接未建立起来的连接 */
160                 if (!sock_flag(sk, SOCK_DONE)) {
161                     /* This occurs when user tries to read
162                      * from never connected socket.
163                      */
164                     copied = -ENOTCONN;
165                     break;
166                 }
167                 break;
168             }
169 
170             /* 不阻塞等待 */
171             if (!timeo) {
172                 copied = -EAGAIN;
173                 break;
174             }
175 
176             /* 有信号待处理 */
177             if (signal_pending(current)) {
178                 copied = sock_intr_errno(timeo);
179                 break;
180             }
181         }
182 
183         /* 检查是否需要发送ack */
184         tcp_cleanup_rbuf(sk, copied);
185 
186         /* 未开启低延迟&& tp的任务为空或者是当前进程 */
187         if (!sysctl_tcp_low_latency && tp->ucopy.task == user_recv) {
188             /* Install new reader */
189             /* 注册当前进程任务 */
190             if (!user_recv && !(flags & (MSG_TRUNC | MSG_PEEK))) {
191                 user_recv = current;
192                 tp->ucopy.task = user_recv;
193                 tp->ucopy.msg = msg;
194             }
195 
196             /* 当前可以使用的用户缓存大小 */
197             tp->ucopy.len = len;
198 
199             WARN_ON(tp->copied_seq != tp->rcv_nxt &&
200                 !(flags & (MSG_PEEK | MSG_TRUNC)));
201 
202             /* Ugly... If prequeue is not empty, we have to
203              * process it before releasing socket, otherwise
204              * order will be broken at second iteration.
205              * More elegant solution is required!!!
206              *
207              * Look: we have the following (pseudo)queues:
208              *
209              * 1. packets in flight
210              * 2. backlog
211              * 3. prequeue
212              * 4. receive_queue
213              *
214              * Each queue can be processed only if the next ones
215              * are empty. At this point we have empty receive_queue.
216              * But prequeue _can_ be not empty after 2nd iteration,
217              * when we jumped to start of loop because backlog
218              * processing added something to receive_queue.
219              * We cannot release_sock(), because backlog contains
220              * packets arrived _after_ prequeued ones.
221              *
222              * Shortly, algorithm is clear --- to process all
223              * the queues in order. We could make it more directly,
224              * requeueing packets from backlog to prequeue, if
225              * is not empty. It is more elegant, but eats cycles,
226              * unfortunately.
227              */
228             /* prequeue不为空,处理prequeue */
229             if (!skb_queue_empty(&tp->ucopy.prequeue))
230                 goto do_prequeue;
231 
232             /* __ Set realtime policy in scheduler __ */
233         }
234 
235         /* 目标数据读取完,处理后备队列 */
236         if (copied >= target) {
237             /* Do not sleep, just process backlog. */
238             release_sock(sk);
239             lock_sock(sk);
240         } 
241         /* 未读取完,进入等待 */
242         else {
243             sk_wait_data(sk, &timeo, last);
244         }
245 
246         /* 用户空间接收数据 */
247         if (user_recv) {
248             int chunk;
249 
250             /* __ Restore normal policy in scheduler __ */
251 
252             /* 获取读取长度 */
253             chunk = len - tp->ucopy.len;
254 
255             /* 记录剩余读取长度和已经读取长度 */
256             if (chunk != 0) {
257                 NET_ADD_STATS(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMBACKLOG, chunk);
258                 len -= chunk;
259                 copied += chunk;
260             }
261 
262             /* 
263                 接收到的数据已经全部复制到用户空间
264                 && prequeue不为空
265             */
266             if (tp->rcv_nxt == tp->copied_seq &&
267                 !skb_queue_empty(&tp->ucopy.prequeue)) {
268 do_prequeue:
269                 /* 处理prequeue */
270                 tcp_prequeue_process(sk);
271 
272                 /* 获取读取长度和剩余长度 */
273                 chunk = len - tp->ucopy.len;
274                 if (chunk != 0) {
275                     NET_ADD_STATS(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
276                     len -= chunk;
277                     copied += chunk;
278                 }
279             }
280         }
281 
282         /* 只是查看数据,则更新peek_seq */
283         if ((flags & MSG_PEEK) &&
284             (peek_seq - copied - urg_hole != tp->copied_seq)) {
285             net_dbg_ratelimited("TCP(%s:%d): Application bug, race in MSG_PEEK\n",
286                         current->comm,
287                         task_pid_nr(current));
288             peek_seq = tp->copied_seq;
289         }
290         continue;
291 
292 /* 读取一个找到的合适的段 */
293     found_ok_skb:
294         /* Ok so how much can we use? */
295 
296         /* 获取该skb中可读的数据长度 */
297         used = skb->len - offset;
298 
299         /* 不需要读取那么多,则调整为需要的长度 */
300         if (len < used)
301             used = len;
302 
303         /* Do we have urgent data here? */
304         /* 有带外数据*/
305         if (tp->urg_data) {
306             /* 带外数据偏移 */
307             u32 urg_offset = tp->urg_seq - *seq;
308 
309             /* 偏移在我们要读取的数据范围内 */
310             if (urg_offset < used) {
311                 /* 当前正在读取的数据为带外数据 */
312                 if (!urg_offset) {
313                     /* 不允许放入正常数据流 */
314                     if (!sock_flag(sk, SOCK_URGINLINE)) {
315                         /* 调整序号和偏移 */
316                         ++*seq;
317                         urg_hole++;
318                         offset++;
319                         used--;
320                         /* 无可读数据 */
321                         if (!used)
322                             goto skip_copy;
323                     }
324                 } 
325                 /* 本次只能读到带外数据为止 */
326                 else
327                     used = urg_offset;
328             }
329         }
330 
331         /* 读取数据 */
332         if (!(flags & MSG_TRUNC)) {
333             err = skb_copy_datagram_msg(skb, offset, msg, used);
334             if (err) {
335                 /* Exception. Bailout! */
336                 if (!copied)
337                     copied = -EFAULT;
338                 break;
339             }
340         }
341 
342         /* 计算读取和待读取数据长度 */
343         *seq += used;
344         copied += used;
345         len -= used;
346 
347         tcp_rcv_space_adjust(sk);
348 
349 skip_copy:
350         /* 完成对带外数据的处理 */
351         if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
352             /* 标志清零 */
353             tp->urg_data = 0;
354             /* 快路检查 */
355             tcp_fast_path_check(sk);
356         }
357 
358         /* 满足继续读取 */
359         if (used + offset < skb->len)
360             continue;
361         /* fin处理 */
362         if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
363             goto found_fin_ok;
364 
365         /* 数据读取完,不是查看,则释放该skb */
366         if (!(flags & MSG_PEEK))
367             sk_eat_skb(sk, skb);
368         continue;
369 
370     found_fin_ok:
371         /* Process the FIN. */
372         /* 序号增加 */
373         ++*seq;
374         /* 不是查看,则释放skb */
375         if (!(flags & MSG_PEEK))
376             sk_eat_skb(sk, skb);
377         break;
378     } while (len > 0);
379 
380     /* 用户空间进程接收数据 */
381     if (user_recv) {
382         /* prequeue不为空 */
383         if (!skb_queue_empty(&tp->ucopy.prequeue)) {
384             int chunk;
385 
386             /* 调整剩余可用空间 */
387             tp->ucopy.len = copied > 0 ? len : 0;
388 
389             /* 处理prequeue */
390             tcp_prequeue_process(sk);
391 
392             /* 读取了数据,则重新计算下长度 */
393             if (copied > 0 && (chunk = len - tp->ucopy.len) != 0) {
394                 NET_ADD_STATS(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
395                 len -= chunk;
396                 copied += chunk;
397             }
398         }
399 
400         /* 用户空间结束读取 */
401         tp->ucopy.task = NULL;
402         tp->ucopy.len = 0;
403     }
404 
405     /* According to UNIX98, msg_name/msg_namelen are ignored
406      * on connected socket. I was just happy when found this 8) --ANK
407      */
408 
409     /* Clean up data we have read: This will do ACK frames. */
410     /* 检查是否有ack发送 */
411     tcp_cleanup_rbuf(sk, copied);
412 
413     release_sock(sk);
414     return copied;
415 
416 out:
417     release_sock(sk);
418     return err;
419 
420 recv_urg:
421     /* 带外数据 */
422     err = tcp_recv_urg(sk, msg, len, flags);
423     goto out;
424 
425 recv_sndq:
426     err = tcp_peek_sndq(sk, msg, len);
427     goto out;
428 }

猜你喜欢

转载自www.cnblogs.com/wanpengcoder/p/11752173.html
今日推荐