由图可知,发送缓存暂时存放:
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