当前位置:   article > 正文

TCP的发送系列 — 发送缓存的管理(一)_tcp发送缓存区创建过程,设置发送缓存

tcp发送缓存区创建过程,设置发送缓存

这里写图片描述

由图可知,发送缓存暂时存放:
1. 应用程序传给发送TCP准备发送的数据。
2. TCP已发送出但尚未收到ack的数据。

发送窗口只是发送缓存的一部分。已发送但未被确认数据大小<=发送窗口的大小。已被确认的数据会从发送缓存中删除。发送缓存和发送窗口后沿(这里的左边)是重合的,因为一被确认就会删除。删除后发送缓存和发送窗口的后沿同时向左移。发送应用程序必须控制写入缓存的速率,不能太快,否则发送缓存会没有存放数据的空间。
 

TCP对发送缓存的管理是在两个层面上进行的:

一个层面是单个socket的发送缓存管理,

另一个层面是整个TCP层的内存管理

单个socket的发送缓存所涉及的变量。

  1. struct sock {
  2.     ...
  3.     /* 预分配缓存大小,是已经分配但尚未使用的部分 */
  4.     int sk_forward_alloc;
  5.     ...
  6.     /* 提交给IP层的发送数据大小(累加skb->truesize) */
  7.     atomic_t sk_wmem_alloc;
  8.     ...
  9.     int sk_sndbuf; /* 发送缓冲区大小的上限 */
  10.     struct sk_buff_head sk_write_queue; /* 发送队列 */
  11.     ...
  12.     /* 发送队列的总大小,包含发送队列中skb负荷大小,
  13.      * 以及sk_buff、sk_shared_info结构体、协议头的额外开销。
  14.      */
  15.     int sk_wmem_queued; 
  16.     ...
  17. };


 

整个TCP层的内存相关变量。

  1. long sysctl_tcp_mem[3] __read_mostly;
  2. int sysctl_tcp_wmem[3] __read_mostly;
  3. int sysctl_tcp_rmem[3] __read_mostly;
  4. struct proto tcp_prot = {
  5.     .name = "TCP",
  6.     .owner = THIS_MODULE,
  7.      ... 
  8.     /* 设置TCP的内存压力标志,把tcp_memory_pressure置为1 */
  9.     .enter_memory_pressure = tcp_enter_memory_pressure,
  10.  
  11.     /* 检查sock是否有剩余的发送缓存(sk_wmem_queued < sk_sndbuf)。
  12.      * 值得注意的是,用户可以使用TCP_NOTSENT_LOWAT选项来避免占用过多的发送缓存。
  13.      */
  14.     .stream_memory_free = tcp_stream_memory_free, 
  15.     ...
  16.     /* TCP目前已经分配的内存 */
  17.     .memory_allocated = &tcp_memory_allocated,
  18.  
  19.     /* TCP内存压力标志,超过tcp_mem[1]后设置,低于tcp_mem[0]后清除 */
  20.     .memory_pressure = &tcp_memory_pressure,
  21.  
  22.     /* TCP内存使用的最小值、压力值、最大值,单位为页 */
  23.     .sysctl_mem = sysctl_tcp_mem,
  24.  
  25.     /* 每个sock写缓存的最小值、默认值、最大值,单位为字节 */
  26.     .sysctl_wmem = sysctl_tcp_wmem,
  27.  
  28.     /* 每个sock读缓存的最小值、默认值、最大值,单位为字节 */
  29.     .sysctl_rmem = sysctl_tcp_rmem,
  30.  
  31.     .max_header = MAX_TCP_HEADER, /* 协议头的最大长度 */
  32.     ...
  33. };
  34. atomic_long_t tcp_memory_allocated; /* Current allocated memory. */
  35. int tcp_memory_pressure __read_mostly; 
  1. static inline bool tcp_stream_memory_free(const struct sock *sk)
  2. {
  3. const struct tcp_sock *tp = tcp_sk(sk);
  4. u32 notsent_bytes = tp->write_seq - tp->snd_nxt; /* 尚未发送的数据大小 */
  5. /* 当尚未发送的数据,少于配置的值时,才返回真。
  6. * 这是为了避免发送缓存占用过多的内存。
  7. */
  8. return notsent_bytes < tcp_notsent_lowat(tp);
  9. }

(1) sysctl_tcp_mem[3]

tcp_mem是整个TCP层的内存消耗,单位为页。

(2) sysctl_tcp_wmem[3]

tcp_wmem是每个sock的写缓存,单位为字节。

tcp_wmem[0]是最小值,tcp_wmem[1]是默认值,tcp_wmem[2]是最大值,

(3) sysctl_tcp_rmem[3]

tcp_rmem是每个sock的读缓存,单位为字节。

(4) 发送缓存区上限sk->sk_sndbuf

 sock发送缓冲区的上限sk->sk_sndbuf在tcp_init_sock()中初始化,初始值为tcp_wmem[1]。

发送队列的总大小不能超过这个值。

  1. void tcp_init_sock(struct sock *sk)
  2. {
  3. ...
  4. sk->sk_sndbuf = sysctl_tcp_wmem[1]; /* 16K */
  5. sk->sk_rcvbuf = sysctl_tcp_rmem[1]; /* 85K */
  6. ...
  7. }

发送缓存区的上限是可以动态调整的,但必须同时满足以下条件:

1. sock有发送缓存不足的标志(上层函数作判断)。

2. 用户没有使用SO_SNDBUF选项。

3. TCP层没有设置内存压力标志。

4. TCP层使用的内存小于tcp_mem[0]。

5. 目前的拥塞控制窗口没有被完全使用掉。

什么时候申请?

什么时候释放?

sk_wmem_free_skb()用来释放skb,同时更新发送缓存的大小。

  1. static inline void sk_wmem_free_skb(struct sock *sk, struct sk_buff *skb)
  2. {
  3.     sock_set_flag(sk, SOCK_QUEUE_SHRUNK); /* 发送队列中有skb被释放了 */
  4.     sk->sk_wmem_queued -= skb->truesize; /* 更新发送队列的总大小 */
  5.     sk_mem_uncharge(sk, skb->truesize); /* 更新剩余的预分配内存 */
  6.     __kfree_skb(skb); /* 释放skb */
  7. }
  8. static inline void sk_mem_uncharge(struct sock *sk, int size)
  9. {
  10.     if (! sk_has_account(sk))
  11.         return;
  12.     sk->sk_forward_alloc += size;
  13. }


 


 

ref:

Queueing in the Linux Network Stack | Dan Siemon

linux - What is the difference between sock->sk_wmem_alloc and sock->sk_wmem_queued - Unix & Linux Stack Exchange

TCP的发送系列 — 发送缓存的管理(一)_zhangskd的博客-CSDN博客_tcp_sndbuf

Socket发送缓冲区接收缓冲区快问快答_恐龙弟旺仔的博客-CSDN博客_发送缓冲区和接收缓冲区

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

闽ICP备14008679号