Chinaunix首页 | 论坛 | 博客
  • 博客访问: 217734
  • 博文数量: 30
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 476
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-07 18:15
个人简介

程序员一个。14年毕业。

文章分类

全部博文(30)

文章存档

2014年(13)

2013年(17)

我的朋友

分类: C/C++

2014-07-20 00:54:01

看了很多网上关于tcp_recvmsg的文章,感觉解释的不太到位,或者很多都是空口说白话,昨天分析了一下午tcp_recvmsg,感觉了解了十之八九,现在贴出来和大家分享一下。

需要背景:了解tcp三个接收队列  prequeue,backlog,receive的各自用处。


点击(此处)折叠或打开

  1. /*
  2.  *    This routine copies from a sock struct into the user buffer.
  3.  *
  4.  *    Technical note: in 2.3 we work on _locked_ socket, so that
  5.  *    tricks with *seq access order and skb->users are not required.
  6.  *    Probably, code can be easily improved even more.
  7.  */

  8. int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
  9.   size_t len, int nonblock, int flags, int *addr_len)
  10. {
  11.  struct tcp_sock *tp = tcp_sk(sk);
  12.  int copied = 0;
  13.  u32 peek_seq;
  14.  u32 *seq;
  15.  unsigned long used;
  16.  int err;
  17.  int target;     /* Read at least this many bytes */
  18.  long timeo;
  19.  struct task_struct *user_recv = NULL;
  20.  int copied_early = 0;
  21.  struct sk_buff *skb;
  22.  u32 urg_hole = 0;

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

  27.  TCP_CHECK_TIMER(sk);

  28.  err = -ENOTCONN;
  29.  if (sk->sk_state == TCP_LISTEN)
  30.   goto out;

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

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

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

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

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

  50. //大循环
  51.  do {
  52.   u32 offset;

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

  62.   /* Next get a buffer. */

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

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

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

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

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

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

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


  119.   if (copied) {
  120. //可能性2,拷贝了数据,但是没有拷贝到指定大小
  121.    if (sk->sk_err ||
  122.        sk->sk_state == TCP_CLOSE ||
  123.        (sk->sk_shutdown & RCV_SHUTDOWN) ||
  124.        !timeo ||
  125.        signal_pending(current))
  126.     break;
  127.   } else {
  128. //可能性1
  129.    if (sock_flag(sk, SOCK_DONE))
  130.     break;

  131.    if (sk->sk_err) {
  132.     copied = sock_error(sk);
  133.     break;
  134.    }

  135.    if (sk->sk_shutdown & RCV_SHUTDOWN)
  136.     break;

  137.    if (sk->sk_state == TCP_CLOSE) {
  138.     if (!sock_flag(sk, SOCK_DONE)) {
  139.      /* This occurs when user tries to read
  140.       * from never connected socket.
  141.       */
  142.      copied = -ENOTCONN;
  143.      break;
  144.     }
  145.     break;
  146.    }

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

  152.    if (signal_pending(current)) {
  153.     copied = sock_intr_errno(timeo);
  154.     break;
  155.    }
  156.   }

  157.   tcp_cleanup_rbuf(sk, copied);

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

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

  166.    tp->ucopy.len = len;

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

  169.    /* Ugly... If prequeue is not empty, we have to
  170.     * process it before releasing socket, otherwise
  171.     * order will be broken at second iteration.
  172.     * More elegant solution is
  173.     *
  174.     * Look: we have the following (pseudo)queues:
  175.     *
  176.     * 1. packets in flight
  177.     * 2. backlog
  178.     * 3. prequeue
  179.     * 4. receive_queue
  180.     *
  181.     * Each queue can be processed only if the next ones
  182.     * are empty. At this point we have empty receive_queue.
  183.     * But prequeue _can_ be not empty after 2nd iteration,
  184.     * when we jumped to start of loop because backlog
  185.     * processing added something to receive_queue.
  186.     * We cannot release_sock(), because backlog contains
  187.     * packets arrived _after_ prequeued ones.
  188.     *
  189.     * Shortly, algorithm is clear --- to process all
  190.     * the queues in order. We could make it more directly,
  191.     * requeueing packets from backlog to prequeue, if
  192.     * is not empty. It is more elegant, but eats cycles,
  193.     * unfortunately.
  194.     */
  195. //
  196.    if (!skb_queue_empty(&tp->ucopy.prequeue))
  197.     goto do_prequeue;

  198.    /* __ Set realtime policy in scheduler __ */
  199.   }

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


  211.   if (user_recv) {
  212.    int chunk;

  213.    /* __ Restore normal policy in scheduler __ */

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

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

  223.     if ((chunk = len - tp->ucopy.len) != 0) {
  224.      NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
  225.      len -= chunk;
  226.      copied += chunk;
  227.     }
  228.    }
  229.   }
  230.   if ((flags & MSG_PEEK) &&
  231.       (peek_seq - copied - urg_hole != tp->copied_seq)) {
  232.    if (net_ratelimit())
  233.     printk(KERN_DEBUG "TCP(%s:%d): Application bug, race in MSG_PEEK.\n",
  234.            current->comm, task_pid_nr(current));
  235.    peek_seq = tp->copied_seq;
  236.   }
  237.   continue;

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

  247.   /* Do we have urgent data here? */
  248.   if (tp->urg_data) {
  249.    u32 urg_offset = tp->urg_seq - *seq;
  250.    if (urg_offset < used) {
  251.     if (!urg_offset) {
  252.      if (!sock_flag(sk, SOCK_URGINLINE)) {
  253.       ++*seq;
  254.       urg_hole++;
  255.       offset++;
  256.       used--;
  257.       if (!used)
  258.        goto skip_copy;
  259.      }
  260.     } else
  261.      used = urg_offset;
  262.    }
  263.   }

  264.   if (!(flags & MSG_TRUNC)) {
  265.    {
  266.     //一般都会进这个if,进行数据的拷贝,把能够读到的数据,放到用户的缓冲区
  267.     err = skb_copy_datagram_iovec(skb, offset,
  268.       msg->msg_iov, used);
  269.     if (err) {
  270.      /* Exception. */
  271.      if (!copied)
  272.       copied = -EFAULT;
  273.      break;
  274.     }
  275.    }
  276.   }

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

  285.   tcp_rcv_space_adjust(sk);

  286. skip_copy:
  287.   if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
  288.    tp->urg_data = 0;
  289.    tcp_fast_path_check(sk);
  290.   }

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

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

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

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

  316.  found_fin_ok:
  317.   /* Process the FIN. */
  318.   ++*seq;
  319.   if (!(flags & MSG_PEEK)) {
  320. //把skb从sk_receive_queue中摘链
  321.    sk_eat_skb(sk, skb, copied_early);
  322.    copied_early = 0;
  323.   }
  324.   break;
  325.  } while (len > 0);

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

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

  332.    tcp_prequeue_process(sk);

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

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

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

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

  349.  TCP_CHECK_TIMER(sk);
  350.  release_sock(sk);
  351.  return copied;

  352. out:
  353.  TCP_CHECK_TIMER(sk);
  354.  release_sock(sk);
  355.  return err;

  356. recv_urg:
  357.  err = tcp_recv_urg(sk, msg, len, flags);
  358.  goto out;
  359. }

转载自我的csdn博客
http://blog.csdn.net/mrpre/article/details/33347221

阅读(4004) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~