成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

2024-09-29 10:46:01

2012-02-16 11:04:32

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-09-03 17:40:25

C#發送短信

2009-08-07 09:35:40

Oracle發送Ema

2020-07-14 09:58:01

Python開發工具

2023-10-16 18:39:22

2018-11-14 09:53:48

2009-12-09 15:23:36

PHP mail()函

2010-05-06 09:52:11

Oracle發送郵件

2021-07-08 07:16:24

RocketMQ數據結構Message

2009-12-02 16:31:54

PHP發送郵件

2020-08-05 08:30:25

Spring BootJavaSE代碼
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩看片 | 亚洲视频一区 | 国产原创视频 | 99爱在线观看 | 欧美日韩不卡合集视频 | 正在播放国产精品 | 久久夜夜| 亚洲国产成人一区二区 | 男女在线免费观看 | 国产三级精品三级在线观看四季网 | 韩国av一区二区 | 成人福利网| 亚洲二区精品 | 久久精彩视频 | 国产一区二区三区在线 | 日本特黄特色aaa大片免费 | 黄视频免费在线 | 成人高潮片免费视频欧美 | 久色视频在线观看 | 亚洲精品在线免费观看视频 | 久色 | 天天摸天天看 | www.天天操| 毛片一级片 | 亚洲视频免费观看 | 国产一级成人 | 欧美高清一级片 | 久久午夜精品福利一区二区 | jizz18国产| 成年人网站免费视频 | 日韩av成人| 日韩影院在线观看 | 国产精品一区2区 | 99精品国产一区二区三区 | 亚洲一区二区三区四区五区中文 | 亚洲成人激情在线观看 | 久久精品日产第一区二区三区 | 国产精品视频免费观看 | 欧美日韩黄色一级片 | 欧美精品综合 | 国产精品久久久久久久久图文区 |