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


TCP的发送缓存管理发生在两个层面上:单个Socket和整个TCP层。

上一篇blog讲述了单个Socket层面上的发送缓存管理,现在来看下整个TCP层面上的发送缓存管理。

从TCP层面判断发送缓存的申请是否合法

在申请发送缓存时,会调用sk_stream_memory_free()来判断sock发送队列的大小是否超过

了sock发送缓存的上限,如果超过了,就要进入睡眠来等待sock的发送缓存可写事件。

这是从单个socket层面来判断是否允许分配发送缓存。

在调用sk_stream_alloc_skb()申请完发送缓存后,还要从TCP层面来判断此次的申请是否合法。

如果不合法,就使用__kfree_skb()来释放申请好的skb。可见发送缓存的申请,需要经过两重关卡。

从TCP层面来判断发送缓存的申请是否合法,需要考虑整个TCP层面的内存使用量,以及此socket

的发送缓存使用量。sk->sk_forward_alloc为sock预分配缓存的大小,是sock事先分配好还未使用的内存。

当申请新的发送缓存后,如果发现sk->sk_forward_alloc < skb->truesize,即预分配缓存用光了,

才需要调用sk_wme_schedule()来从TCP层面判断合法性,否则不用再做检查。

[java] 
static inline bool sk_wmem_schedule(struct sock *sk, int size)
{ /* TCP层是有统计内存使用的,所以条件为假 */ if (! sk_has_account(sk)) return true; /* 如果本次使用的内存skb->truesize,少于sk预分配且未使用的缓存的大小,那么不用进行 * 进一步检查。否则需要从TCP层面判断此次发送缓存的申请是否合法。 */ return size <= sk->sk_forward_alloc || __sk_mem_schedule(sk, size, SK_MEM_SEND); } static inline bool sk_has_account(struct sock *sk) { /* return ture if protocol supports memory accounting */ return !! sk->sk_prot->memory_allocated; } /* return minimum truesize of one skb containing X bytes of data */ #define SKB_TRUESIZE(X) ((X) + \ SKB_DATA_ALIGN(sizeof(struct sk_buff)) + \ SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))

__sk_mem_schedule()用来从TCP层面判断此次发送缓存的申请是否合法,如果是合法的,

会更新预分配缓存sk->sk_forward_alloc和TCP层总的内存使用量tcp_memory_allocated,

后者的单位为页。

Q:哪些情况下此次发送缓存的申请是合法的呢?

1. TCP层的内存使用量低于最小值sysctl_tcp_mem[0]。

2. sock的发送缓存使用量低于最小值sysctl_tcp_wmem[0]。

3. TCP层不处于内存压力状态,即TCP层的内存使用量低于sysctl_tcp_wmem[1]。

4. TCP层处于内存压力状态,但当前socket使用的内存还不是太高。

5. TCP层的内存使用量超过最大值sysctl_tcp_wmem[2],降低发送缓存的上限后,发送队列的总大小超过

了发送缓存的上限了。因此之后会进入睡眠等待,所以也判为合法的。

可以看到,在绝大多数情况下发送缓存的申请都是合法的,除非TCP的内存使用量已经到极限了。

除了判断此次发送缓存申请的合法性,__sk_mem_schedule()还做了如下事情:

1. 如果TCP的内存使用量低于最小值sysctl_tcp_mem[0],就清零TCP的内存压力标志tcp_memory_pressure。

2. 如果TCP的内存使用量高于压力值sysclt_tcp_mem[1],把TCP的内存压力标志tcp_memory_pressure置为1。

3. 如果TCP的内存使用量高于最大值sysctl_tcp_mem[2],就减小sock发送缓存的上限sk->sk_sndbuf。

返回值为1时,表示发送缓存的申请是合法的;返回值为0时,表示不合法。

[java] 
/* increase sk_forward_alloc and memory_allocated
* @sk: socket * @size: memory size to allocate * @kind: allocation type * If kind is SK_MEM_SEND, it means wmem allocation. * Otherwise it means rmem allocation. This function assumes that * protocols which have memory pressure use sk_wmem_queued as * write buffer accounting. */ int __sk_mem_schedule(struct sock *sk, int size, int kind) { struct proto *prot = sk->sk_prot; /* 实例为tcp_prot */ int amt = sk_mem_pages(size); /* 把size转换为页数,向上取整 */ long allocated; int parent_status = UNDER_LIMIT; sk->sk_forward_alloc += amt * SK_MEM_QUANTUM; /* 更新预分配缓存的大小 */ /* 更新后的TCP内存使用量tcp_memory_allocated,单位为页 */ allocated = sk_memory_allocated_add(sk, amt, &parent_status); /* Under limit. 如果TCP的内存使用量低于最小值sysctl_tcp_mem[0] */ if (parent_status == UNDER_LIMIT && allocated <= sk_prot_mem_limits(sk, 0)) { sk_leave_memory_pressure(sk); /* 清零TCP层的内存压力标志tcp_memory_pressure */ return 1; } /* Under pressure. (we or our parents). * 如果TCP的内存使用量高于压力值sysclt_tcp_mem[1],把TCP层的内存压力标志 * tcp_memory_pressure置为1。 */ if ((parent_status > SOFT_LIMIT) || allocated > sk_prot_mem_limits(sk, 1)) sk_enter_memory_pressure(sk); /* Over hard limit (we or our parents). * 如果TCP层的内存使用量高于最大值sysctl_tcp_mem[2],就减小sock发送缓存的上限 * sk->sk_sndbuf。 */ if ((parent_status == OVER_LIMIT || (allocated > sk_prot_mem_limits(sk, 2))) goto suppress_allocation; /* guarantee minimum buffer size under pressure */ /* 不管是在发送还是接收时,都要保证sock至少有sysctl_tcp_{r,w}mem[0]的内存可用 */ if (kind == SK_MEM_RECV) { if (atomic_read(&sk->sk_rmem_alloc) < prot->sysctl_rmem[0]) return 1; } else { /* SK_MEM_SEND */ if (sk->sk_type == SOCK_STREAM) { if (sk->sk_wmem_queued < prot->sysctl_wmem[0]) return 1; } else if (atomic_read(&sk->sk_wmem_alloc) < prot->sysctl_wmem[0]) return 1; } if (sk_has_memory_pressure(sk)) { int alloc; /* 如果TCP不处于内存压力状态,直接返回 */ if (! sk_under_memory_pressure(sk)) return 1; alloc = sk_sockets_allocated_read_positive(sk); /* 当前使用TCP的socket个数 */ /* 如果当前socket使用的内存还不是太高时,返回真 */ if (sk_prot_mem_limits(sk, 2) > alloc * sk_mem_pages(sk->sk_wmem_queued + atomic_read(&sk->sk_rmem_alloc) + sk->sk_forward_alloc)) return 1; } suppress_allocation: if (kind == SK_MEM_SEND && sk->sk_type == SOCK_STREAM) { /* 减小sock发送缓冲区的上限,使得sndbuf不超过发送队列总大小的一半, * 不低于两个数据包的MIN_TRUESIZE。 */ sk_stream_moderate_sndbuf(sk); /* Fail only if socket is under its sndbuf. * In this case we cannot block, so that we have to fail. */ if (sk->sk_wmem_queued + size >= sk->sk_sndbuf) return 1; } trace_sock_exceed_buf_limit(sk, prot, allocated); /* 走到这里,判定此次发送缓存的申请为不合法的,撤销之前的内存计数更新 */ /* Alas. Undo changes. */ sk->sk_forward_alloc -= amt * SK_MEM_QUANTUM; sk_memory_allocated_sub(sk, amt); return 0; } /* 把字节数amt转换为页数,向上取整 */ static inline int sk_mem_pages(int amt) { return (amt + SK_MEM_QUANTUM - 1) >> SK_MEM_QUANTUM_SHIFT; } #define SK_MEM_QUANTUM ((int) PAGE_SIZE) /* 返回更新后的TCP内使用量tcp_memory_allocated,单位为页 */ static inline long sk_memory_allocated_add(struct sock *sk, int amt, int *parent_status) { struct proto *prot = sk->sk_prot; /* Cgroup相关,此处略过 */ if (mem_cgroup_sockets_enabled && sk->sk_cgrp) { ... } return atomic_long_add_return(amt, prot->memory_allocated); }




相关内容