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

网络 网络管理
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。

数据结构

TCP对发送缓存的管理是在两个层面上进行的,一个层面是单个socket的发送缓存管理,

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

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

[java] 
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层的内存相关变量。

[java] 
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;

初始化

(1) tcp_mem

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

long sysctl_tcp_mem[3] __read_mostly;

tcp_mem - vector of 3 INTEGERs: min, pressure, max

min: below this number of pages TCP is not bothered about its memory appetite.

pressure: when amount of memory allocated by TCP exceeds this number of pages,

TCP moderates it memory consumption and enters memory pressure mode, which

is exited when memory consumption falls under min.

max: number of pages allowed for queueing by all TCP sockets.

Defaults are calculated at boot time from amount of available memory.

在tcp_init()中,调用tcp_init_mem()来初始化sysctl_tcp_mem[3]数组。

tcp_mem[0]是最小值,为3/32的系统内存。

tcp_mem[1]是压力值,为1/8的系统内存,也是最小值的4/3。

tcp_mem[2]是最大值,为3/16的系统内存,也是最小值的2倍。

[java] 
static void tcp_init_mem(void)
{
/* nr_free_buffer_pages()计算ZONE_DMA和ZONE_NORMAL的页数,
* 对于64位系统来说,其实就是所有内存了。
*/
unsigned long limit = nr_free_buffer_pages() / 8;
limit = max(limit, 128UL); /* 不能低于128页 */
sysctl_tcp_mem[0] = limit / 4 * 3; /* 最小值设为3/32的系统内存 */
sysctl_tcp_mem[1] = limit; /* 压力值设为1/8的系统内存 */
sysctl_tcp_mem[2] = sysctl_tcp_mem[0] * 2; /* 最大值设为3/16的系统内存 */
}

(2) tcp_wmem

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

int sysctl_tcp_wmem[3] __read_mostly;

tcp_wmem - vector of 3 INTEGERs: min, default, max

min: Amount of memory reserved for send buffers for TCP sockets.

Each TCP socket has rights to use it due to fact of its birth.

Default: 1 page

default: initial size of send buffer used by TCP sockets.

This value overrides net.core.wmem_default used by other protocols.

It is usually lower than net.core.wmem_default.

Default: 16K

max: Maximal amount of memory allowed for automatically tuned send buffers

for TCP sockets. This value does not override net.core.wmem_max.

Calling setsockopt() with SO_SNDBUF disables automatic tuning of that

socket's send buffer size, in which case this value is ignored.

Default: between 64K and 4MB, depending on RAM size.

tcp_wmem[0]是最小值,为4KB。

tcp_wmem[1]是默认值,为16KB。

tcp_wmem[2]是最大值,为4MB。

tcp_rmem[0]是最小值,为4KB。

tcp_rmem[1]是默认值,为87380字节。

tcp_wmem[2]是最大值,为6MB(之前的内核为4MB)。

[java]
void __init tcp_init(void)
{
...
/* 初始化sysctl_tcp_mem数组 */
tcp_init_mem();
/* Set per-socket limits to no more than 1/128 the pressure threshold */
/* 系统内存的1/128,单位为字节 */
limit = nr_free_buffers_pages() << (PAGE_SHIFT - 7);
max_wshare = min(4UL * 1024 * 1024, limit); /* 不能低于4MB */
max_rshare = min(6UL * 1024 * 1024, limit); /* 不能低于6MB */
sysctl_tcp_wmem[0] = SK_MEM_QUANTUM; /* 最小值为一页,4KB */
sysctl_tcp_wmem[1] = 16 * 1024; /* 默认值为16KB */
/* 取系统内存的1/128、4MB中的小者,并且不能低于64KB。
* 也就是说如果系统内存超过512MB,那么最大值为4MB。
*/
sysctl_tcp_wmem[2] = max(64 * 1024, max_wshare);
sysctl_tcp_rmem[0] = SK_MEM_QUANTUM; /* 最小值为一页,4KB */
sysctl_tcp_rmem[1] = 87380; /* 默认值为差不多85KB */
/* 去系统内存的1/128、6MB中的小者,且不能低于87380。
* 也就是说如果系统内存超过768MB,那么最大值为6MB。
* 在较低内核版本中,是如果系统内存超过512MB,最大值为4MB。
*/
sysctl_tcp_rmem[2] = max(87380, max_rshare);
...
}

#p#

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

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

一般为16K。

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

(4) wmem_default和wmem_max

/proc/sys/net/core/wmem_max和/proc/sys/net/core/wmem_default,

默认值为256个的负荷为256字节的数据段的总内存消耗。

对于TCP而言,wmem_default会被tcp_wmem[1]给覆盖掉,而wmem_max作为一个上限,

限制着用户使用SO_SNDBUF时可设置的发送缓存的大小。

[java] 
#define _SK_MEM_PACKETS 256
#define _SK_MEM_OVERHEAD SKB_TRUESIZE(256)
#define SK_WMEM_MAX (_SK_MEM_OVERHEAD * _SK_MEM_PACKETS)
__u32 sysctl_wmem_max __read_mostly = SK_WMEM_MAX;
__u32 sysctl_wmem_default __read_mostly = SK_WMEM_MAX:
int sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval,
unsigned int optlen)
{
...
switch (optname) {
...
case SO_SNDBUF:
/* 设置的值不能高于wmem_max */
val = min_t(u32, val, sysctl_wmem_max);
set_sndbuf:
/* 用户使用SO_SNDBUF的标志 */
sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
/* 发送缓存的上限,其实是两倍的用户设置值!*/
sk->sk_sndbuf = max_t(u32, val * 2, SOCK_MIN_SNDBUF);
/* Wake up sending tasks if we upped the value. */
sk->sk_write_space(sk); /*有发送缓存可写事件 */
...
}
...
}

sock发送缓存上限的动态调整

sk->sk_sndbuf为socket发送缓存的上限,发送队列的总大小不能超过这个值。

(1) 连接建立成功时

调用tcp_init_buffer_space()来调整发送缓存和接收缓存的大小。

[java] 
/* Try to fixup all. It is made immediately after connection enters
* established state.
*/
void tcp_init_buffer_space(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
int maxwin;
/* 如果用户没有使用SO_RCVBUF选项,就调整接收缓冲区的上限。
* 调整之后,一般sk->sk_rcvbuf会大于初始值tcp_rmem[1]。
*/
if (! (sk->sk_userlocks & SOCK_RCVBUF_LOCK))
tcp_fixup_rcvbuf(sk);
/* 如果用户没有使用SO_SNDBUF选项,就调整发送缓冲区的上限。
* 调整之后,一般sk->sk_sndbuf会大于初始值tcp_wmem[1]。
*/
if (! (sk->sk_userlocks & SOCK_SNDBUF_LOCK))
tcp_sndbuf_expand(sk);
tp->rcvq_space.space = tp->rcv_wnd; /* 当前接收缓存的大小,只包括数据 */
tp->rcvq_space.time = tcp_time_stamp;
tp->rcvq_space.seq = tp->copied_seq; /* 下次复制从这里开始 */
maxwin = tcp_full_space(sk); /* 接收缓存上限的3/4 */
if (tp->window_clamp >= maxwin) {
tp->window_clamp = maxwin;
/* 最大的通告窗口,变为接收缓存上限的3/4的3/4 */
if (sysctl_tcp_app_win && maxwin > 4 * tp->advmss)
tp->window_clamp = max(maxwin - (maxwin >> sysctl_tcp_app_win),
4 * tp->advmss);
}
/* Force reservation of one segment. 至少要预留一个MSS的空间 */
if (sysctl_tcp_app_win && tp->window_clamp > 2 * tp->advmss &&
tp->window_clamp + tp->advmss > maxwin)
tp->window_clamp = max(2 * tp->advmss, maxwin - tp->advmss);
tp->rcv_ssthresh = min(tp->rcv_ssthresh, tp->window_clamp);
tp->snd_cwnd_stamp = tcp_time_stamp;
}

a. 调整接收缓冲区的上限sk->sk_rcvbuf

调整之后的sk->sk_rcvbuf,一般为8倍的初始拥塞控制窗口(TCP_INIT_CWND)。

[java] 
/* Tuning rcvbuf, when connection enters established state. */
static void tcp_fixup_rcvbuf(struct sock *sk)
{
u32 mss = tcp_sk(sk)->advmss;
int rcvmem;
/* 初始的rwnd一般为2倍的初始拥塞控制窗口,即20个MSS。
* 所以rcvmem是40个MSS段耗费的总内存大小,包括协议头、sk_buff和
* skb_shared_info结构体。
*/
rcvmem = 2 * SKB_TRUESIZE(mss + MAX_TCP_HEADER) *
tcp_default_init_rwnd(mss);
/* 如果让系统自动调节接收缓存的大小(默认是的) */
if (sysctl_tcp_moderate_rcvbuf)
rcvmem <<= 2; /* 增加一倍 */
/* 如果rcvmem比tcp_rmem[1]大,那么更新接收缓冲区的上限。
* rcvmem一般会比tcp_rmem[1]大。
*/
if (sk->sk_rcvbuf < rcvmem)
sk->sk_rcvbuf = min(rcvmem, syscl_tcp_rmem[2]);
}

初始的接收窗口大小,一般为2倍的初始拥塞窗口大小,即20个MSS。

[java] 
u32 tcp_default_init_rwnd(u32 mss)
{
/* Initial receive window should be twice of TCP_INIT_CWND to enable
* proper sending of new unsent data during fast recovery (RFC 3517,
* Section 4, NextSeg() rule (2)). Further place a limit when mss is larger
* than 1460.
*/
u32 init_rwnd = TCP_INIT_CWND * 2; /* 设为初始拥塞窗口的2倍 */
if (mss > 1460)
init_rwnd = max((1460 * init_rwnd) / mss, 2U);
return init_rwnd;
}

tcp_moderate_rcvbuf让系统自动调节接收缓存的大小,默认使用。

tcp_moderate_rcvbuf - BOOLEAN

If set, TCP performs receive buffer auto-tuning, attempting to automatically

size the buffer (no greater than tcp_rmem[2]) to match the size required by

the path for full throughput. Enabled by default.

b. 调整发送缓冲区的上限sk->sk_sndbuf

调整之后的sk->sk_sndbuf不少于2倍的拥塞控制窗口(tp->snd_cwnd)。

[java] 
/* Buffer size and advertised window tuning.
* Tuning sk->sk_sndbuf, when connection enters established state.
*/
static void tcp_sndbuf_expand(struct sock *sk)
{
const struct tcp_sock *tp = tcp_sk(sk);
int sndmem, per_mss;
u32 nr_segs;
/* Worst case is non GSO/TSO: each frame consumes one skb and
* skb->head is kmalloced using power of two area of memory.
*/
/* 当不使用GSO/TSO时,一个TCP负荷为MSS的段所消耗的总内存 */
per_mss = max_t(u32, tp->rx_opt.mss_clamp, tp->mss_cache) +
MAX_TCP_HEADER + SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
per_mss = roundup_pow_of_two(per_mss) +
SKB_DATA_ALIGN(sizeof(struct sk_buff));
/* 数据段的个数,取TCP_INIT_CWND、tp->snd_cwnd和
* tp->reordering + 1中的最大者。
*/
nr_segs = max_t(u32, TCP_INIT_CWND, tp->snd_cwnd);
nr_segs = max_t(u32, nr_segs, tp->reordering + 1);
/* Fast Recovery (RFC 5681 3.2):
* Cubic needs 1.7 factor, rounded to 2 to include extra cushion
* (application might react slowly to POLLOUT)
*/
sndmem = 2 * nr_segs * per_mss; /* 2倍 */
/* 如果默认的发送缓冲区上限tcp_wmem[1]小于本次计算的值sndmem,
* 那么更新sk->sk_sndbuf。由于默认值为16K,所以肯定会更新的:)
*/
if (sk->sk_sndbuf < sndmem)
sk->sk_sndbuf = min(sndmem, sysctl_tcp_wmem[2]);
}

#p#

(2) 建立连接以后

当接收到ACK后,会检查是否需要调整发送缓存的上限sk->sk_sndbuf。

tcp_rcv_established / tcp_rcv_state_process

tcp_data_snd_check

tcp_check_space

tcp_new_space

[java]
static inline void tcp_data_snd_check(struct sock *sk)
{
tcp_push_pending_frames(sk); /* 发送数据段 */
tcp_check_space(sk); /* 更新发送缓存 */
}

如果发送队列中有skb被释放了,且设置了同步发送时发送缓存不足的标志,

就检查是否要更新发送缓存的上限、是否要触发有发送缓存可写的事件。

[java] 
static void tcp_check_space(struct sock *sk)
{
/* 如果发送队列中有skb被释放了 */
if (sock_flag(sk, SOCK_QUEUE_SHRUNK)) {
sock_reset_flag(sk, SOCK_QUEUE_SHRUNK);
/* 如果设置了同步发送时,发送缓存不足的标志 */
if (sk->sk_socket && test_bit(SOCK_NOSPACE, &sk->sk_socket->flags))
tcp_new_space(sk); /* 更新发送缓存 */
}
}
[java] 
/* When incoming ACK allowed to free some skb from write_queue,
* we remember this event in flag SOCK_QUEUE_SHRUNK and wake up socket
* on the exit from tcp input handler.
*/
static void tcp_new_space(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
/* 检查能否扩大发送缓存的上限 */
if (tcp_should_expand_sndbuf(sk)) {
tcp_sndbuf_expand(sk); /* 扩大发送缓存的上限 */
tp->snd_cwnd_stamp = tcp_time_stamp;
}
/* 检查是否需要触发有缓存可写事件 */
sk->sk_write_space(sk);
}

在什么情况下允许扩大发送缓存的上限呢?

必须同时满足以下条件:

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

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

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

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

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

[java] 
static bool tcp_should_expand_sndbuf(const struct sock *sk)
{
const struct tcp_sock *tp = tcp_sk(sk);
/* If the user specified a specific send buffer setting, do not modify it.
* 如果用户使用了SO_SNDBUF选项,就不自动调整了。
*/
if (sk->sk_userlocks & SOCK_SNDBUF_LOCK)
return false;
/* If we are under global TCP memory pressure, do not expand.
* 如果TCP设置了内存压力标志,就不扩大发送缓存的上限了。
*/
if (sk_under_memory_pressure(sk))
return false;
/* If we are under soft global TCP memory pressure, do not expand. */
/* 如果目前TCP层使用的内存超过tcp_mem[0],就不扩大发送缓存的上限了 */
if (sk_memory_allocated(sk) >= sk_prot_mem_limits(sk, 0))
return false;
/* If we filled the congestion window, do not expand.
* 如果把拥塞控制窗口给用满了,说明拥塞窗口才是限制因素,就不扩大发送缓存的上限了。
*/
if (tp->packets_out >= tp->snd_cwnd)
return false;
return true;
}

发送缓存的申请

在tcp_sendmsg()中,如果发送队列的最后一个skb不能追加数据了,就要申请一个新的skb来装载数据。

[java] 
int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t size)
{
...
if (copy <= 0) { /* 需要使用新的skb来装数据 */
new_segment:
/* Allocate new segment. If the interface is SG,
* allocate skb fitting to single page.
*/
/* 如果发送队列的总大小sk_wmem_queued大于等于发送缓存的上限sk_sndbuf,
* 或者发送缓存中尚未发送的数据量超过了用户的设置值,就进入等待。
*/
if (! sk_stream_memory_free(sk))
goto wait_for_sndbuf;
/* 申请一个skb,其线性数据区的大小为:
* 通过select_size()得到的线性数据区中TCP负荷的大小 + 最大的协议头长度。
* 如果申请skb失败了,或者虽然申请skb成功,但是从系统层面判断此次申请不合法,
* 那么就进入睡眠,等待内存。
*/
skb = sk_stream_alloc_skb(sk, select_size(sk, sg), sk->sk_allocation);
if (! skb)
goto wait_for_memory;
...
}

#p#

skb的线性数据区中,TCP payload的大小是如何选取的呢?

1. 如果网卡不支持scatter-gather,那么TCP负荷的大小为一个MSS,不用管分段和分页。

2. 如果网卡支持分散聚合。

2.1 如果网卡支持GSO,那么TCP负荷的大小为2048 - MAX_TCP_HEADER - sizeof(struct skb_shared_info),

多出来的数据会在skb的分页中。

2.2 如果网卡不支持GSO。

2.2.1 如果MSS大于PAGE_SIZE - MAX_TCP_HEADER - sizeof(struct skb_shared_info),

且不超过分散聚合所支持的最大长度64k,那么TCP负荷的大小为

PAGE_SIZE - MAX_TCP_HEADER - sizeof(struct skb_shared_skb),剩余的数据放在分页区中。

2.2.2 否则TCP负荷的大小为一个MSS。

[java] 
static inline int select_size(const struct sock *sk, bool sg)
{
const struct tcp_sock *tp = tcp_sk(sk);
int tmp = tp->mss_cache;
/* 如果网卡支持分散聚合 */
if (sg) {
/* 如果网卡支持GSO */
if (sk_can_gso(sk)) {
/* Small frames wont use a full page:
* Payload will immediately follow tcp header.
*/
/* 线性数据区中TCP负荷的大小 = 2048 - MAX_TCP_HEADER - sizeof(struct skb_shared_info).
* 较早的版本是把tmp直接置为0,把数据都放在分页中,这会浪费内存。
*/
tmp = SKB_WITH_OVERHEAD(2048 - MAX_TCP_HEADER);
} else {
/* 值为PAGE_SIZE - MAX_TCP_HEADER,也就是一页中除去协议头的剩余部分 */
int pgbreak = SKB_MAX_HEAD(MAX_TCP_HEADER);
/* 如果MSS大于一页中的剩余部分,且不超过分散聚合所支持的最大长度64k,
* 那么线性数据区中TCP负荷的大小为一页中出去协议头的部分,剩余的数据会放在分页区中。
*/
if (tmp >= pgbreak && tmp <= pgbreak + (MAX_SKB_FRAGS - 1) * PAGE_SIZE)
tmp = pgbreak;
}
}
return tmp;
}
#define SKB_WITH_OVERHEAD(X) \
((X) - SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
#define MAX_TCP_HEADER (128 + MAX_HEADER)
#define SKB_MAX_HEAD(X) (SKB_MAX_ORDER(X), 0))
#define SKB_MAX_ORDER(X, ORDER) \
SKB_WITH_OVERHEAD(PAGE_SIZE << (ORDER)) - (X))

sk_stream_alloc_skb()用于申请一个新的skb,其线性数据区的长度为size(不包括协议头)。

[java] 
struct sk_buff *sk_stream_alloc_skb(struct sock *sk, int size, gfp_t gfp)
{
struct sk_buff *skb;
/* The TCP header must be at least 32-bit aligned. */
size = ALIGN(size, 4);
/* 申请一个skb,线性数据区的大小 =
* 通过select_size()得到的线性数据区中TCP负荷的大小 + 最大的协议头长度。
*/
skb = alloc_skb_fclone(size + sk->sk_prot->max_header, gfp);
if (skb) {
/* skb->truesize包括TCP负荷大小,sk_buff、skb_shared_info结构大小,以及协议头的大小。
* 调用sk_wmem_schedule()来从整个TCP的层面判断此次发送缓存的申请是否合法。
*/
if (sk_wmem_schedule(sk, skb->truesize)) {
skb_reserve(skb, sk->sk_prot->max_header);
/* Make sure that we have exactly size bytes available to the caller,
* no more, no less. tailroom的大小。
*/
skb->reserved_tailroom = skb->end - skb->tail - size;
return skb;
}
__kfree_skb(skb); /* 如果不合法,就释放掉 */
} else { /* 如果skb的申请失败了 */
/* 设置TCP层的内存压力标志 */
sk->sk_prot->enter_memory_pressure(sk);
/* 减小sock发送缓冲区的上限,使得sndbuf不超过发送队列总大小的一半,
* 不低于两个数据包的MIN_TRUESIZE。
*/
sk_stream_moderate_sndbuf(sk);
}
}

TCP层内存压力志,超过tcp_mem[1]后设置,低于tcp_mem[0]后清除。

[java]
void tcp_enter_memory_pressure(struct sock *sk)
{
if (! tcp_memory_pressure) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMEMORYPRESSURES);
tcp_memory_pressure = 1; /* TCP层的内存压力标志 */
}
}

减小sock发送缓冲区的上限,使得sndbuf不超过发送队列总大小的一半,

不低于两个数据包的MIN_TRUESIZE。

[java]
static inline void sk_stream_moderate_sndbuf(struct sock *sk)
{
/* 如果用户没有使用SO_SNDBUF选项 */
if (! (sk->sk_userlocks & SOCK_SNDBUF_LOCK)) {
/* 取当前sndbuf、发送队列总大小1/2的小者 */
sk->sk_sndbuf = min(sk->sk_sndbuf, sk->sk_wmem_queued >> 1);
/* 取当前sndbuf、两个数据包的总大小的大者 */
sk->sk_sndbuf = max_t(u32, sk->sk_sndbuf, SOCK_MIN_SNDBUF);
}
}
#define SOCK_MIN_SNDBUF (TCP_SKB_MIN_TRUESIZE * 2)
/* Since sk_{r,w}mem_alloc sums skb->truesize, even a small frame might need
* sizeof(sk_buff) + MTU + padding, unless net driver perform copybreak.
* Note: for send buffers, TCP works better if we can build two skbs at minimum.
*/
#define TCP_SKB_MIN_TRUESIZE (2048 + SKB_DATA_ALIGN(sizeof(struct sk_buff)))

如果通过sk_stream_alloc_skb()成功申请了一个新的skb,那么更新skb的TCP控制块字段,

把skb加入到sock发送队列的尾部,增加发送队列的大小,减小预分配缓存的大小。

[java] 
static inline void skb_entail(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_skb_cb *tcb = TCP_SKB_CB(skb);
skb->csum = 0;
tcb->seq = tcb->end_seq = tp->write_seq; /* 值为发送队列中的最后一个字节序号加一 */
tcb->tcp_flags = TCPHDR_ACK;
tcb->sacked = 0;
skb_header_release(skb); /* 增加skb负荷部分的引用计数 */
tcp_add_write_queue_tail(sk, skb); /* 把skb添加到发送队列的尾部 */
sk->sk_wmem_queued += skb->truesize; /* 增加发送队列的总大小 */
sk_mem_charge(sk, skb->truesize); /* 减小预分配缓存的大小 */
if (tp->nonagle & TCP_NAGLE_PUSH) /* 取消禁止nagle的标志 */
tp->nonagle &= ~TCP_NAGLE_PUSH;
}
static inline void tcp_add_write_queue_tail(struct sock *sk, struct sk_buff *skb)
{
__tcp_add_write_queue_tail(sk, skb); /* 把skb添加到发送队列的尾部 */
/* Queue it, remembering where we must start sending. */
if (sk->sk_send_head == NULL) {
sk->sk_send_head = skb; /* 更新下一个要发送的skb */
if (tcp_sk(sk)->highest_sack == NULL)
tcp_sk(sk)->highest_sack = skb;
}
}
[java] 
static inline bool sk_has_account(struct sock *sk)
{
/* return ture if protocol supports memory accounting */
return !! sk->sk_prot->memory_allocated;
}
static inline void sk_mem_charge(struct sock *sk, int size)
{
if (! sk_has_account(sk))
return;
sk->sk_forward_alloc -= size;
}

发送缓存的释放

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

[java]
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 */
}
[java] 
static inline void sk_mem_uncharge(struct sock *sk, int size)
{
if (! sk_has_account(sk))
return;
sk->sk_forward_alloc += size;
}
责任编辑:何妍 来源: CSDN博客
相关推荐

2015-09-10 09:16:45

TCP缓存

2019-09-30 09:28:26

LinuxTCPIP

2023-11-10 16:28:02

TCP窗口

2012-02-16 11:04:32

2010-07-20 11:03:45

Telnet会话

2021-03-15 22:42:25

NameNodeDataNode分布式

2022-08-28 16:31:11

缓存雪崩

2009-08-07 09:35:40

Oracle发送Ema

2009-09-03 17:40:25

C#发送短信

2020-07-14 09:58:01

Python开发工具

2023-10-16 18:39:22

2022-09-06 15:30:20

缓存一致性

2018-11-14 09:53:48

2015-10-13 15:09:31

2010-05-06 09:52:11

Oracle发送邮件

2009-12-02 16:31:54

PHP发送邮件

2009-12-09 15:23:36

PHP mail()函

2021-07-08 07:16:24

RocketMQ数据结构Message

2020-08-05 08:30:25

Spring BootJavaSE代码

2009-08-21 09:44:44

C#发送Email邮件
点赞
收藏

51CTO技术栈公众号