概述
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 ", 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 ! ", __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 ", 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 ", 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 }