精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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

2012-02-16 11:04:32

2024-09-29 10:46:01

2023-11-10 16:28:02

TCP窗口

2022-08-28 16:31:11

緩存雪崩

2010-07-20 11:03:45

Telnet會話

2021-03-15 22:42:25

NameNodeDataNode分布式

2022-09-06 15:30:20

緩存一致性

2015-10-13 15:09:31

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

2018-11-14 09:53:48

2010-05-06 09:52:11

Oracle發送郵件

2021-07-08 07:16:24

RocketMQ數據結構Message

2009-12-02 16:31:54

PHP發送郵件

2009-12-09 15:23:36

PHP mail()函

2020-08-05 08:30:25

Spring BootJavaSE代碼
點贊
收藏

51CTO技術棧公眾號

国产熟女一区二区| 国产aaa免费视频| 国产日韩欧美一区二区东京热| 午夜久久美女| 国产网站欧美日韩免费精品在线观看 | 动漫av一区| 在线观看网站黄不卡| 992tv成人免费观看| 日漫免费在线观看网站| 老色鬼精品视频在线观看播放| 色综合视频网站| 国产一区二区三区四区五区六区| 国产区一区二| 色婷婷精品大在线视频| 隔壁人妻偷人bd中字| 成人高清免费在线播放| thepron国产精品| 国产在线视频91| 91精品国产综合久久久蜜臀九色| 午夜国产精品视频免费体验区| 亚洲欧美激情在线视频| 中文字幕18页| 日本一区二区中文字幕| 色中色一区二区| 国产传媒久久久| 日本免费中文字幕在线| 国产亚洲制服色| 久久av一区二区三区亚洲| 99国产精品久久久久久久成人| 日精品一区二区三区| 久久久亚洲国产天美传媒修理工| 亚洲 欧美 变态 另类 综合| 日韩精品久久| 一本久久综合亚洲鲁鲁| 亚洲第一成人网站| 性欧美lx╳lx╳| 亚洲精品一区二区三区影院| 午夜影院免费版| av在线国产精品| 欧美精品色综合| 伊人国产在线视频| 99re久久| 欧美日韩激情在线| 三级av免费观看| 九色成人搞黄网站| 日韩欧美国产视频| 99久久久无码国产精品6| 久久青草伊人| 激情成人在线视频| 97视频久久久| 国产伦理精品| 精品国产福利在线| 日韩欧美一区三区| 中文字幕色婷婷在线视频| 午夜精品福利一区二区蜜股av| a级黄色小视频| 欧美freesex黑人又粗又大| 天天综合天天做天天综合| 欧美亚洲另类色图| 樱花草涩涩www在线播放| 欧美视频免费在线| 999在线免费视频| 国模一区二区| 911精品国产一区二区在线| 国产精品v日韩精品v在线观看| 国外成人福利视频| 欧美一二三四在线| 91传媒理伦片在线观看| 日本在线中文字幕一区| 亚洲欧美一区二区精品久久久| 人妻少妇无码精品视频区| 精品视频亚洲| 久久精品视频在线| 免费无遮挡无码永久在线观看视频 | 久久久午夜电影| 亚洲开发第一视频在线播放| 黄色在线观看网站| 精品久久久久久电影| 狠狠爱免费视频| 国产综合色激情| 亚洲精品在线网站| av女人的天堂| 亚洲乱码电影| 26uuu另类亚洲欧美日本老年| 波多野结衣黄色网址| 黄页网站大全一区二区| 国产精品白丝jk白祙| 久久经典视频| 亚洲女子a中天字幕| 黄色www网站| 台湾天天综合人成在线| 97国产精品久久久| 国产乱码一区二区三区| 免费99视频| 超碰在线最新| 91久久人澡人人添人人爽欧美| 久久久久久久高清| 日韩精选在线| 欧美精品在线免费观看| 久久久黄色大片| 国产福利一区二区三区视频在线 | 高清美女视频一区| 一卡二卡三卡日韩欧美| 啊啊啊国产视频| 成人精品动漫一区二区三区| 日韩在线一区二区三区免费视频| 日本天堂网在线观看| 久久精品av麻豆的观看方式| 久久久久se| www.久久久久.com| 欧美性一二三区| 色婷婷精品久久二区二区密| 欧美电影免费| 国产福利精品av综合导导航| 丁香六月色婷婷| 日韩毛片高清在线播放| 日韩欧美xxxx| 老汉色老汉首页av亚洲| 欧美国产激情18| 国产精品久久久久久久免费看| 久久久久久99久久久精品网站| 男人的天堂avav| 又色又爽又黄18网站| 麻豆视频网站在线观看| 日韩欧美一区二区三区久久| 国产精品亚洲一区二区无码| 天天操综合网| 国产精品视频资源| 国产一区电影| 色悠久久久久综合欧美99| 伊人久久一区二区三区| 国产一区亚洲| 亚洲自拍小视频免费观看| 老司机在线永久免费观看| 欧美伊人精品成人久久综合97| 国产精品毛片一区二区| 亚洲免费一区二区| 狠狠色噜噜狠狠狠狠色吗综合| 国产后进白嫩翘臀在线观看视频| 欧美一区二区三区四区五区 | 国产精品久久久久久久久久99| 天堂资源中文在线| 欧美日韩色婷婷| www.超碰97| 亚洲国产高清一区| 国产欧美一区二区在线播放| 亚洲丝袜精品| 精品久久久久久久一区二区蜜臀| 国产va在线播放| 国产福利一区在线| 欧美成人高潮一二区在线看| 6080成人| 2024亚洲男人天堂| 可以在线观看的av网站| 91久久精品国产91性色tv| 欧美黄色一级生活片| 日韩一区精品视频| 亚洲欧洲精品一区二区| 亚洲成人1区| 欧美老女人性视频| 欧美一级一区二区三区| 午夜精品福利在线| 欧美偷拍一区二区三区| 久久久人人人| 亚洲一区二区不卡视频| 亚洲不卡在线| 97视频色精品| 国产人成在线视频| 91精品中文字幕一区二区三区| 少妇影院在线观看| 99精品视频一区二区| 妞干网在线免费视频| 91日韩免费| 国产成人精品免费视频大全最热| 美女高潮在线观看| 中文字幕日韩欧美在线视频| 999av视频| 激情av一区二区| 91免费在线看片| 国产成人在线影院| 精品人妻一区二区三区四区在线 | 这里只有精品免费视频| 中文字幕亚洲区| 成人做爰69片免费| 日韩精品一级中文字幕精品视频免费观看| 亚洲日本欧美在线| 成人在线视频你懂的| 国产91精品在线播放| 成年视频在线观看| 亚洲欧美在线磁力| 国产人妖一区二区三区| 一本一道波多野结衣一区二区| 开心激情五月网| 91欧美一区二区| 香蕉视频色在线观看| 日韩制服丝袜先锋影音| 中文字幕色呦呦| 日韩av专区| 精品久久一区二区三区蜜桃| 四虎影视国产精品| 日韩av电影免费观看高清| av黄色在线| 中日韩美女免费视频网址在线观看| 亚洲精品视频91| 欧美日韩成人激情| 久久精品视频7| 亚洲综合男人的天堂| 最新日韩免费视频| 久久蜜桃av一区精品变态类天堂| 黑人无套内谢中国美女| 美女网站色91| 欧美牲交a欧美牲交aⅴ免费真 | 中文字幕乱码人妻综合二区三区| 欧美日韩影院| 亚洲一区二区三区四区中文| 自拍偷拍一区| 国产伦精品一区二区三区四区视频| 小说区图片区亚洲| 国产精品国产三级国产aⅴ9色 | 欧美精品一卡| 一本色道久久综合亚洲二区三区| 国产精品手机在线播放 | 色呦呦中文字幕| 日韩三区在线观看| 国产绳艺sm调教室论坛| 欧美日韩免费观看一区三区| 老熟妇一区二区三区| 精品久久久视频| 日本少妇吞精囗交| 亚洲综合999| 日韩黄色免费观看| 亚洲精品乱码久久久久久黑人| 日韩一区二区三区四区视频| 国产视频一区在线播放| 在线观看日本中文字幕| 国产不卡av一区二区| 欧美三级中文字幕在线观看| 成人毛片18女人毛片| 亚洲午夜羞羞片| 久久久久免费看| 玉足女爽爽91| 青青草成人免费| 亚洲影视在线播放| 久草视频免费在线| 亚洲一区二区在线播放相泽| 国产在线拍揄自揄拍| 一二三四区精品视频| 久草免费在线视频观看| 亚洲国产精品久久不卡毛片| 久久精品久久精品久久| 亚洲成人在线网站| www.国产色| 色菇凉天天综合网| 性高潮视频在线观看| 欧美色综合网站| 7777久久亚洲中文字幕| 欧美放荡的少妇| 亚洲国产精品久久人人爱潘金莲| 欧美成人精品1314www| 欧美熟妇交换久久久久久分类| 亚洲国语精品自产拍在线观看| 手机福利在线| 伊人久久大香线蕉av一区二区| caoporn国产精品免费视频| 中文字幕免费国产精品| 久操视频在线观看| 欧美激情伊人电影 | 偷拍与自拍一区| 日韩手机在线视频| 欧美日韩美少妇| 亚洲AV无码乱码国产精品牛牛| 亚洲福利视频久久| 国产视频网站在线| 成人97在线观看视频| 国产免费拔擦拔擦8x高清在线人| 日韩美女免费观看| а天堂中文最新一区二区三区| 成人在线看片| 激情综合网五月| 成人在线观看毛片| 蜜桃av综合| 亚洲自拍第三页| 久久蜜桃av一区精品变态类天堂| 一级性生活免费视频| 五月天中文字幕一区二区| 免费黄色片视频| 日韩三级视频在线看| 欧美日韩在线中文字幕| 久久精品99久久久香蕉| 1234区中文字幕在线观看| 国产精品免费一区二区三区都可以| 日韩区一区二| 日韩亚洲视频| 激情丁香综合| 爱情岛论坛成人| 北条麻妃国产九九精品视频| 亚洲一区 欧美| 精品福利免费观看| 国产精品人人妻人人爽| 亚洲男人天堂久| 欧美性爽视频| 国产在线视频欧美| 中文字幕亚洲影视| 国产精品久久久久久久久电影网| 日韩精品电影在线观看| 国产污在线观看| 日韩美女视频19| 亚洲成熟少妇视频在线观看| 精品国产一区二区亚洲人成毛片| 欧美高清视频| 国产成人精品免费视频| 国产图片一区| 中国 免费 av| 免费人成精品欧美精品| 黄色在线观看av| 亚洲第一主播视频| 国产熟女精品视频| 最好看的2019的中文字幕视频| 美女露胸视频在线观看| 99在线视频免费观看| 欧美电影一区| 99热一区二区| 国产蜜臀av在线一区二区三区| 免费观看一区二区三区毛片| 精品美女一区二区| h片在线观看网站| 国产综合在线观看视频| 成人精品影视| 日韩精品一区二区三区久久| 北条麻妃国产九九精品视频| 久久免费小视频| 精品乱人伦一区二区三区| 成人在线播放| 亚洲va电影大全| 欧美在线视屏| 日批免费观看视频| 亚洲午夜在线电影| 男人天堂手机在线观看| 久久噜噜噜精品国产亚洲综合| 亚洲一区 二区| 国产中文字幕乱人伦在线观看| 国产91丝袜在线播放0| 精品无码av在线| 亚洲精品美女久久久| 日韩av一卡| 欧美人xxxxx| 日韩一区精品视频| 国产精品18在线| 91精品久久久久久久久99蜜臂| 久久99精品久久久久久野外| 91视频国产精品| 欧美女人交a| 一级黄色片毛片| 色综合av在线| 欧洲美女少妇精品| 亚洲一区二区三区视频播放| 女生裸体视频一区二区三区| 色欲欲www成人网站| 亚洲.国产.中文慕字在线| 亚洲人妻一区二区| 国产精品成人一区| 日韩毛片视频| 99国产精品免费视频| 亚洲国产精品久久久男人的天堂| 熟妇高潮一区二区三区| 日韩免费在线视频| 99久久国产综合精品成人影院| 欧美日韩一区二区区| 亚洲成人av资源| 男男激情在线| 国产男女猛烈无遮挡91| 欧美不卡视频| avtt香蕉久久| 欧美日韩一区二区三区不卡| 50度灰在线| 欧美高清性xxxxhd| 韩国午夜理伦三级不卡影院| 久久成人在线观看| 亚洲天堂久久av| 九九九九九九精品任你躁 | 免费成人av在线| 久久久久久久蜜桃| 亚洲欧美国产高清va在线播| 色综合一区二区日本韩国亚洲| 国产天堂视频在线观看| 国产亚洲欧洲997久久综合| 99国产精品一区二区三区 | 亚洲精品久久久久久久蜜桃臀| 久久婷婷综合激情| 国产女人高潮的av毛片| 欧美一乱一性一交一视频| 国产精品国产三级国产在线观看 | 国产999视频| 亚洲欧美网站在线观看| 成人精品999| 日韩免费电影网站| 日本黄色一区| 日本丰满少妇xxxx| 中文字幕一区二区日韩精品绯色| 天天射天天色天天干|