当前位置:   article > 正文

TCP发送缓存控制tcp_notsent_lowat

notsent

PROC文件tcp_notsent_lowat控制发送缓存队列中的未发送数据量。低于此值可发送,超出此值停止发送,。


初始化

内核在TCP初始化函数tcp_sk_init中,将sysctl_tcp_notsent_lowat设置为无符号整数的最大值,此值为TCP套接口全局的tcp_notsent_lowat控制值。如果用户层针对特定套接口使用setsockopt的设置选项TCP_NOTSENT_LOWAT设定了新的notsent_lowat值,其优先级大于全局的sysctl_tcp_notsent_lowat值。

  1. static int __net_init tcp_sk_init(struct net *net)
  2. {
  3. net->ipv4.sysctl_tcp_notsent_lowat = UINT_MAX;
  4. }
  5. $ cat /proc/sys/net/ipv4/tcp_notsent_lowat
  6. -4294967295
  7. $
  8. static int do_tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval, unsigned int optlen)
  9. {
  10. switch (optname) {
  11. case TCP_NOTSENT_LOWAT:
  12. tp->notsent_lowat = val;
  13. sk->sk_write_space(sk);
  14. break;
  15. }
  16. }


发送数据空判定


如函数tcp_stream_memory_free所示,当未发送的数据长度小于设定的notsent_lowat时,内核即认为TCP的发送缓存为空,而不必真正等到全部数据发送完成。

  1. static inline u32 tcp_notsent_lowat(const struct tcp_sock *tp)
  2. {
  3. struct net *net = sock_net((struct sock *)tp);
  4. return tp->notsent_lowat ?: net->ipv4.sysctl_tcp_notsent_lowat;
  5. }
  6. static inline bool tcp_stream_memory_free(const struct sock *sk)
  7. {
  8. u32 notsent_bytes = tp->write_seq - tp->snd_nxt;
  9. return notsent_bytes < tcp_notsent_lowat(tp);
  10. }

套接口层函数sk_stream_memory_free封装了TCP函数tcp_stream_memory_free,套接口层对缓存的判断,首先比较发送队列的空间与设定的限值sk_sndbuf,小于限值缓存可用,否则,调用TCP的判定函数,即未发送数据notsent_bytes大于等于设定的notsent_lowat值,认为未发送的数据量过大,发送缓存暂时不可用。

  1. static inline bool sk_stream_memory_free(const struct sock *sk)
  2. {
  3. if (sk->sk_wmem_queued >= sk->sk_sndbuf)
  4. return false;
  5. return sk->sk_prot->stream_memory_free ? sk->sk_prot->stream_memory_free(sk) : true;
  6. }

相反的,在判断发送缓存何时变得可用时,内核使用sk_stream_is_writeable函数,首要条件是发送队列缓存空间的余量(sk_stream_wspace)大于等于当前发送队列占用空间的一半,即还有1/3以上的空余空间。其次,是未发送的数据量低于notsent_lowat的值。

  1. static inline bool sk_stream_is_writeable(const struct sock *sk)
  2. {
  3. return sk_stream_wspace(sk) >= sk_stream_min_wspace(sk) && sk_stream_memory_free(sk);
  4. }
  5. static inline int sk_stream_wspace(const struct sock *sk)
  6. {
  7. return sk->sk_sndbuf - sk->sk_wmem_queued;
  8. }
  9. static inline int sk_stream_min_wspace(const struct sock *sk)
  10. {
  11. return sk->sk_wmem_queued >> 1;
  12. }


发送路径缓存判断

发送路径函数do_tcp_sendpages和tcp_sendmsg_locked函数都要对notsent_lowat进行判断,以后者为例,看一下其中的逻辑。首先在分配发送skb之前,判断发送缓存是否可用,不可用的话,跳转到wait_for_sndbuf标签,虽然字面上是等待缓存变得可用,但是如果用户设置了MSG_DONTWAIT标志,不执行等待直接返回。详细信息将函数sk_stream_wait_memory。此处设置了套接口的SOCK_NOSPACE标志。

  1. int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
  2. {
  3. while (msg_data_left(msg)) {
  4. if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
  5. new_segment:
  6. if (!sk_stream_memory_free(sk))
  7. goto wait_for_sndbuf;
  8. skb = sk_stream_alloc_skb(sk, select_size(sk, sg, first_skb), sk->sk_allocation, first_skb);
  9. }
  10. continue;
  11. wait_for_sndbuf:
  12. set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
  13. wait_for_memory:
  14. if (copied)
  15. tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH, size_goal);
  16. err = sk_stream_wait_memory(sk, &timeo);
  17. if (err != 0)
  18. goto do_error;
  19. }

    
接下来是do_error标签的处理,如果在发送缓存不可用之前,有数据拷贝发生,接收了部分应用层数据,返回拷贝的数据长度。反之,如果没有数据拷贝发送,返回错误码。

  1. out:
  2. if (copied) {
  3. tcp_tx_timestamp(sk, sockc.tsflags);
  4. tcp_push(sk, flags, mss_now, tp->nonagle, size_goal);
  5. }
  6. out_nopush:
  7. sock_zerocopy_put(uarg);
  8. return copied + copied_syn;
  9. do_error:
  10. if (copied + copied_syn)
  11. goto out;
  12. out_err:
  13. sock_zerocopy_put_abort(uarg);
  14. err = sk_stream_error(sk, flags, err);
  15. /* make sure we wake any epoll edge trigger waiter */
  16. if (unlikely(skb_queue_len(&sk->sk_write_queue) == 0 &&err == -EAGAIN)) {
  17. sk->sk_write_space(sk);
  18. }
  19. return err;
  20. }

等待缓存可用函数sk_stream_wait_memory如下,如果timeo_p所定义的超时时间为0,并且用户设定了MSG_DONTWAIT标志,立即返回。否则,使用sk_wait_event函数等待缓存可用事件发生,或者超时。

  1. int sk_stream_wait_memory(struct sock *sk, long *timeo_p)
  2. {
  3. long current_timeo = *timeo_p;
  4. bool noblock = (*timeo_p ? false : true);
  5. DEFINE_WAIT_FUNC(wait, woken_wake_function);
  6. if (sk_stream_memory_free(sk))
  7. current_timeo = vm_wait = (prandom_u32() % (HZ / 5)) + 2;
  8. add_wait_queue(sk_sleep(sk), &wait);
  9. while (1) {
  10. sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
  11. if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
  12. goto do_error;
  13. if (!*timeo_p) {
  14. if (noblock)
  15. set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
  16. goto do_nonblock;
  17. }
  18. if (signal_pending(current))
  19. goto do_interrupted;
  20. sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);
  21. if (sk_stream_memory_free(sk) && !vm_wait)
  22. break;
  23. set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
  24. sk->sk_write_pending++;
  25. sk_wait_event(sk, &current_timeo, sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN) || (sk_stream_memory_free(sk) &&!vm_wait), &wait);
  26. sk->sk_write_pending--;
  27. if (vm_wait) {
  28. vm_wait -= current_timeo;
  29. current_timeo = *timeo_p;
  30. if (current_timeo != MAX_SCHEDULE_TIMEOUT && (current_timeo -= vm_wait) < 0)
  31. current_timeo = 0;
  32. vm_wait = 0;
  33. }
  34. *timeo_p = current_timeo;
  35. }
  36. out:
  37. remove_wait_queue(sk_sleep(sk), &wait);
  38. return err;
  39. }

如果发送缓存可写,清除套接口socket的SOCK_NOSPACE标志,唤醒等待的进程。

  1. void sk_stream_write_space(struct sock *sk)
  2. {
  3. struct socket *sock = sk->sk_socket;
  4. struct socket_wq *wq;
  5. if (sk_stream_is_writeable(sk) && sock) {
  6. clear_bit(SOCK_NOSPACE, &sock->flags);
  7. wq = rcu_dereference(sk->sk_wq);
  8. if (skwq_has_sleeper(wq))
  9. wake_up_interruptible_poll(&wq->wait, POLLOUT | POLLWRNORM | POLLWRBAND);
  10. }
  11. }

POLL事件


如下内核函数tcp_poll,通过函数sk_stream_is_writeable的结果判定,可通知用户层发送数据的时机。当发送缓存队列的数据流小于notsent_lowat值的时候,由POLLOUT通知应用层可写,无需等待;反之,应用层需要等待。

  1. unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
  2. {
  3. if (state != TCP_SYN_SENT && (state != TCP_SYN_RECV || tp->fastopen_rsk)) {
  4. if (!(sk->sk_shutdown & SEND_SHUTDOWN)) {
  5. if (sk_stream_is_writeable(sk)) {
  6. mask |= POLLOUT | POLLWRNORM;
  7. } else {
  8. /* Race breaker. If space is freed after wspace test but before the flags are set, IO signal will be lost. Memory barrier pairs with the input side. */
  9. smp_mb__after_atomic();
  10. if (sk_stream_is_writeable(sk))
  11. mask |= POLLOUT | POLLWRNORM;
  12. }
  13. }
  14. }
  15. }

 

内核版本 4.15

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/892466
推荐阅读
相关标签
  

闽ICP备14008679号