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

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

相关文章

2019年篮球世界杯 中国男篮战胜科特迪瓦 迎“开门红”
365速度发国际大厅

2019年篮球世界杯 中国男篮战胜科特迪瓦 迎“开门红”

📅 07-29 👁️ 1794
Mac 键盘按键符号对照,Mac 新手必看
bt365在线

Mac 键盘按键符号对照,Mac 新手必看

📅 08-09 👁️ 4293
大量轩辕传奇小常识整理 总有你不知道的
bt365在线

大量轩辕传奇小常识整理 总有你不知道的

📅 09-23 👁️ 4647