• 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
    ",
    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 }
  • 相关阅读:
    内网邮件服务器搭建
    solr的命令
    solr的post.jar
    Java IO(四--字符流基本使用
    Java IO(三)--字节流基本使用
    Java IO(二)--RandomAccessFile基本使用
    Java集合(三)--Collection、Collections和Arrays
    Java集合(二)--Iterator和Iterable
    Java IO(一)--File类
    Java基础(十四)--装箱、拆箱详解
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/11752173.html
Copyright © 2020-2023  润新知