TCP的发送系列 — 发送缓存的管理(一)

这里写图片描述

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

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

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

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

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

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

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


 

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

long sysctl_tcp_mem[3] __read_mostly;
int sysctl_tcp_wmem[3] __read_mostly;
int sysctl_tcp_rmem[3] __read_mostly;


struct proto tcp_prot = {
    .name = "TCP",
    .owner = THIS_MODULE,
     ... 
    /* 设置TCP的内存压力标志,把tcp_memory_pressure置为1 */
    .enter_memory_pressure = tcp_enter_memory_pressure,
 
    /* 检查sock是否有剩余的发送缓存(sk_wmem_queued < sk_sndbuf)。
     * 值得注意的是,用户可以使用TCP_NOTSENT_LOWAT选项来避免占用过多的发送缓存。
     */
    .stream_memory_free = tcp_stream_memory_free, 
    ...
    /* TCP目前已经分配的内存 */
    .memory_allocated = &tcp_memory_allocated,
 
    /* TCP内存压力标志,超过tcp_mem[1]后设置,低于tcp_mem[0]后清除 */
    .memory_pressure = &tcp_memory_pressure,
 
    /* TCP内存使用的最小值、压力值、最大值,单位为页 */
    .sysctl_mem = sysctl_tcp_mem,
 
    /* 每个sock写缓存的最小值、默认值、最大值,单位为字节 */
    .sysctl_wmem = sysctl_tcp_wmem,
 
    /* 每个sock读缓存的最小值、默认值、最大值,单位为字节 */
    .sysctl_rmem = sysctl_tcp_rmem,
 
    .max_header = MAX_TCP_HEADER, /* 协议头的最大长度 */
    ...
};
atomic_long_t tcp_memory_allocated; /* Current allocated memory. */
int tcp_memory_pressure __read_mostly; 

static inline bool tcp_stream_memory_free(const struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);
    u32 notsent_bytes = tp->write_seq - tp->snd_nxt; /* 尚未发送的数据大小 */
    /* 当尚未发送的数据,少于配置的值时,才返回真。
    * 这是为了避免发送缓存占用过多的内存。
    */
    return notsent_bytes < tcp_notsent_lowat(tp);
}

(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]。

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

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

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

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

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

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

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

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

什么时候申请?

什么时候释放?

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

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


 


 

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博客_发送缓冲区和接收缓冲区

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值