自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

TCP的發(fā)送系列 — 發(fā)送緩存的管理(一)

網(wǎng)絡(luò) 網(wǎng)絡(luò)管理
TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,由IETF的RFC 793定義。

數(shù)據(jù)結(jié)構(gòu)

TCP對發(fā)送緩存的管理是在兩個層面上進(jìn)行的,一個層面是單個socket的發(fā)送緩存管理,

另一個層面是整個TCP層的內(nèi)存管理。

單個socket的發(fā)送緩存所涉及的變量。

[java] 
struct sock {
...
/* 預(yù)分配緩存大小,是已經(jīng)分配但尚未使用的部分 */
int sk_forward_alloc;
...
/* 提交給IP層的發(fā)送數(shù)據(jù)大小(累加skb->truesize) */
atomic_t sk_wmem_alloc;
...
int sk_sndbuf; /* 發(fā)送緩沖區(qū)大小的上限 */
struct sk_buff_head sk_write_queue; /* 發(fā)送隊列 */
...
/* 發(fā)送隊列的總大小,包含發(fā)送隊列中skb負(fù)荷大小,
* 以及sk_buff、sk_shared_info結(jié)構(gòu)體、協(xié)議頭的額外開銷。
*/
int sk_wmem_queued;
...
};

整個TCP層的內(nèi)存相關(guān)變量。

[java] 
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
...
/* 設(shè)置TCP的內(nèi)存壓力標(biāo)志,把tcp_memory_pressure置為1 */
.enter_memory_pressure = tcp_enter_memory_pressure,
/* 檢查sock是否有剩余的發(fā)送緩存(sk_wmem_queued < sk_sndbuf)。
* 值得注意的是,用戶可以使用TCP_NOTSENT_LOWAT選項來避免占用過多的發(fā)送緩存。
*/
.stream_memory_free = tcp_stream_memory_free,
...
/* TCP目前已經(jīng)分配的內(nèi)存 */
.memory_allocated = &tcp_memory_allocated,
/* TCP內(nèi)存壓力標(biāo)志,超過tcp_mem[1]后設(shè)置,低于tcp_mem[0]后清除 */
.memory_pressure = &tcp_memory_pressure,
/* TCP內(nèi)存使用的最小值、壓力值、最大值,單位為頁 */
.sysctl_mem = sysctl_tcp_mem,
/* 每個sock寫緩存的最小值、默認(rèn)值、最大值,單位為字節(jié) */
.sysctl_wmem = sysctl_tcp_wmem,
/* 每個sock讀緩存的最小值、默認(rèn)值、最大值,單位為字節(jié) */
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER, /* 協(xié)議頭的最大長度 */
...
};
atomic_long_t tcp_memory_allocated; /* Current allocated memory. */
int tcp_memory_pressure __read_mostly;

初始化

(1) tcp_mem

tcp_mem是整個TCP層的內(nèi)存消耗,單位為頁。

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()中,調(diào)用tcp_init_mem()來初始化sysctl_tcp_mem[3]數(shù)組。

tcp_mem[0]是最小值,為3/32的系統(tǒng)內(nèi)存。

tcp_mem[1]是壓力值,為1/8的系統(tǒng)內(nèi)存,也是最小值的4/3。

tcp_mem[2]是最大值,為3/16的系統(tǒng)內(nèi)存,也是最小值的2倍。

[java] 
static void tcp_init_mem(void)
{
/* nr_free_buffer_pages()計算ZONE_DMA和ZONE_NORMAL的頁數(shù),
* 對于64位系統(tǒng)來說,其實就是所有內(nèi)存了。
*/
unsigned long limit = nr_free_buffer_pages() / 8;
limit = max(limit, 128UL); /* 不能低于128頁 */
sysctl_tcp_mem[0] = limit / 4 * 3; /* 最小值設(shè)為3/32的系統(tǒng)內(nèi)存 */
sysctl_tcp_mem[1] = limit; /* 壓力值設(shè)為1/8的系統(tǒng)內(nèi)存 */
sysctl_tcp_mem[2] = sysctl_tcp_mem[0] * 2; /* 最大值設(shè)為3/16的系統(tǒng)內(nèi)存 */
}

(2) tcp_wmem

tcp_wmem是每個sock的寫緩存,單位為字節(jié)。

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]是默認(rèn)值,為16KB。

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

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

tcp_rmem[1]是默認(rèn)值,為87380字節(jié)。

tcp_wmem[2]是最大值,為6MB(之前的內(nèi)核為4MB)。

[java]
void __init tcp_init(void)
{
...
/* 初始化sysctl_tcp_mem數(shù)組 */
tcp_init_mem();
/* Set per-socket limits to no more than 1/128 the pressure threshold */
/* 系統(tǒng)內(nèi)存的1/128,單位為字節(jié) */
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; /* 默認(rèn)值為16KB */
/* 取系統(tǒng)內(nèi)存的1/128、4MB中的小者,并且不能低于64KB。
* 也就是說如果系統(tǒng)內(nèi)存超過512MB,那么最大值為4MB。
*/
sysctl_tcp_wmem[2] = max(64 * 1024, max_wshare);
sysctl_tcp_rmem[0] = SK_MEM_QUANTUM; /* 最小值為一頁,4KB */
sysctl_tcp_rmem[1] = 87380; /* 默認(rèn)值為差不多85KB */
/* 去系統(tǒng)內(nèi)存的1/128、6MB中的小者,且不能低于87380。
* 也就是說如果系統(tǒng)內(nèi)存超過768MB,那么最大值為6MB。
* 在較低內(nèi)核版本中,是如果系統(tǒng)內(nèi)存超過512MB,最大值為4MB。
*/
sysctl_tcp_rmem[2] = max(87380, max_rshare);
...
}

#p#

(3) 發(fā)送緩存區(qū)上限sk->sk_sndbuf

sock發(fā)送緩沖區(qū)的上限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,

默認(rèn)值為256個的負(fù)荷為256字節(jié)的數(shù)據(jù)段的總內(nèi)存消耗。

對于TCP而言,wmem_default會被tcp_wmem[1]給覆蓋掉,而wmem_max作為一個上限,

限制著用戶使用SO_SNDBUF時可設(shè)置的發(fā)送緩存的大小。

[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:
/* 設(shè)置的值不能高于wmem_max */
val = min_t(u32, val, sysctl_wmem_max);
set_sndbuf:
/* 用戶使用SO_SNDBUF的標(biāo)志 */
sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
/* 發(fā)送緩存的上限,其實是兩倍的用戶設(shè)置值!*/
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); /*有發(fā)送緩存可寫事件 */
...
}
...
}

sock發(fā)送緩存上限的動態(tài)調(diào)整

sk->sk_sndbuf為socket發(fā)送緩存的上限,發(fā)送隊列的總大小不能超過這個值。

(1) 連接建立成功時

調(diào)用tcp_init_buffer_space()來調(diào)整發(fā)送緩存和接收緩存的大小。

[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選項,就調(diào)整接收緩沖區(qū)的上限。
* 調(diào)整之后,一般sk->sk_rcvbuf會大于初始值tcp_rmem[1]。
*/
if (! (sk->sk_userlocks & SOCK_RCVBUF_LOCK))
tcp_fixup_rcvbuf(sk);
/* 如果用戶沒有使用SO_SNDBUF選項,就調(diào)整發(fā)送緩沖區(qū)的上限。
* 調(diào)整之后,一般sk->sk_sndbuf會大于初始值tcp_wmem[1]。
*/
if (! (sk->sk_userlocks & SOCK_SNDBUF_LOCK))
tcp_sndbuf_expand(sk);
tp->rcvq_space.space = tp->rcv_wnd; /* 當(dāng)前接收緩存的大小,只包括數(shù)據(jù) */
tp->rcvq_space.time = tcp_time_stamp;
tp->rcvq_space.seq = tp->copied_seq; /* 下次復(fù)制從這里開始 */
maxwin = tcp_full_space(sk); /* 接收緩存上限的3/4 */
if (tp->window_clamp >= maxwin) {
tp->window_clamp = maxwin;
/* 最大的通告窗口,變?yōu)榻邮站彺嫔舷薜?/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. 至少要預(yù)留一個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. 調(diào)整接收緩沖區(qū)的上限sk->sk_rcvbuf

調(diào)整之后的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段耗費的總內(nèi)存大小,包括協(xié)議頭、sk_buff和
* skb_shared_info結(jié)構(gòu)體。
*/
rcvmem = 2 * SKB_TRUESIZE(mss + MAX_TCP_HEADER) *
tcp_default_init_rwnd(mss);
/* 如果讓系統(tǒng)自動調(diào)節(jié)接收緩存的大小(默認(rèn)是的) */
if (sysctl_tcp_moderate_rcvbuf)
rcvmem <<= 2; /* 增加一倍 */
/* 如果rcvmem比tcp_rmem[1]大,那么更新接收緩沖區(qū)的上限。
* 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; /* 設(shè)為初始擁塞窗口的2倍 */
if (mss > 1460)
init_rwnd = max((1460 * init_rwnd) / mss, 2U);
return init_rwnd;
}

tcp_moderate_rcvbuf讓系統(tǒng)自動調(diào)節(jié)接收緩存的大小,默認(rèn)使用。

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. 調(diào)整發(fā)送緩沖區(qū)的上限sk->sk_sndbuf

調(diào)整之后的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.
*/
/* 當(dāng)不使用GSO/TSO時,一個TCP負(fù)荷為MSS的段所消耗的總內(nèi)存 */
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));
/* 數(shù)據(jù)段的個數(shù),取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倍 */
/* 如果默認(rèn)的發(fā)送緩沖區(qū)上限tcp_wmem[1]小于本次計算的值sndmem,
* 那么更新sk->sk_sndbuf。由于默認(rèn)值為16K,所以肯定會更新的:)
*/
if (sk->sk_sndbuf < sndmem)
sk->sk_sndbuf = min(sndmem, sysctl_tcp_wmem[2]);
}

#p#

(2) 建立連接以后

當(dāng)接收到ACK后,會檢查是否需要調(diào)整發(fā)送緩存的上限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); /* 發(fā)送數(shù)據(jù)段 */
tcp_check_space(sk); /* 更新發(fā)送緩存 */
}

如果發(fā)送隊列中有skb被釋放了,且設(shè)置了同步發(fā)送時發(fā)送緩存不足的標(biāo)志,

就檢查是否要更新發(fā)送緩存的上限、是否要觸發(fā)有發(fā)送緩存可寫的事件。

[java] 
static void tcp_check_space(struct sock *sk)
{
/* 如果發(fā)送隊列中有skb被釋放了 */
if (sock_flag(sk, SOCK_QUEUE_SHRUNK)) {
sock_reset_flag(sk, SOCK_QUEUE_SHRUNK);
/* 如果設(shè)置了同步發(fā)送時,發(fā)送緩存不足的標(biāo)志 */
if (sk->sk_socket && test_bit(SOCK_NOSPACE, &sk->sk_socket->flags))
tcp_new_space(sk); /* 更新發(fā)送緩存 */
}
}
[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);
/* 檢查能否擴(kuò)大發(fā)送緩存的上限 */
if (tcp_should_expand_sndbuf(sk)) {
tcp_sndbuf_expand(sk); /* 擴(kuò)大發(fā)送緩存的上限 */
tp->snd_cwnd_stamp = tcp_time_stamp;
}
/* 檢查是否需要觸發(fā)有緩存可寫事件 */
sk->sk_write_space(sk);
}

在什么情況下允許擴(kuò)大發(fā)送緩存的上限呢?

必須同時滿足以下條件:

1. sock有發(fā)送緩存不足的標(biāo)志(上層函數(shù)作判斷)。

2. 用戶沒有使用SO_SNDBUF選項。

3. TCP層沒有設(shè)置內(nèi)存壓力標(biāo)志。

4. TCP層使用的內(nèi)存小于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選項,就不自動調(diào)整了。
*/
if (sk->sk_userlocks & SOCK_SNDBUF_LOCK)
return false;
/* If we are under global TCP memory pressure, do not expand.
* 如果TCP設(shè)置了內(nèi)存壓力標(biāo)志,就不擴(kuò)大發(fā)送緩存的上限了。
*/
if (sk_under_memory_pressure(sk))
return false;
/* If we are under soft global TCP memory pressure, do not expand. */
/* 如果目前TCP層使用的內(nèi)存超過tcp_mem[0],就不擴(kuò)大發(fā)送緩存的上限了 */
if (sk_memory_allocated(sk) >= sk_prot_mem_limits(sk, 0))
return false;
/* If we filled the congestion window, do not expand.
* 如果把擁塞控制窗口給用滿了,說明擁塞窗口才是限制因素,就不擴(kuò)大發(fā)送緩存的上限了。
*/
if (tp->packets_out >= tp->snd_cwnd)
return false;
return true;
}

發(fā)送緩存的申請

在tcp_sendmsg()中,如果發(fā)送隊列的最后一個skb不能追加數(shù)據(jù)了,就要申請一個新的skb來裝載數(shù)據(jù)。

[java] 
int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t size)
{
...
if (copy <= 0) { /* 需要使用新的skb來裝數(shù)據(jù) */
new_segment:
/* Allocate new segment. If the interface is SG,
* allocate skb fitting to single page.
*/
/* 如果發(fā)送隊列的總大小sk_wmem_queued大于等于發(fā)送緩存的上限sk_sndbuf,
* 或者發(fā)送緩存中尚未發(fā)送的數(shù)據(jù)量超過了用戶的設(shè)置值,就進(jìn)入等待。
*/
if (! sk_stream_memory_free(sk))
goto wait_for_sndbuf;
/* 申請一個skb,其線性數(shù)據(jù)區(qū)的大小為:
* 通過select_size()得到的線性數(shù)據(jù)區(qū)中TCP負(fù)荷的大小 + 最大的協(xié)議頭長度。
* 如果申請skb失敗了,或者雖然申請skb成功,但是從系統(tǒng)層面判斷此次申請不合法,
* 那么就進(jìn)入睡眠,等待內(nèi)存。
*/
skb = sk_stream_alloc_skb(sk, select_size(sk, sg), sk->sk_allocation);
if (! skb)
goto wait_for_memory;
...
}

#p#

skb的線性數(shù)據(jù)區(qū)中,TCP payload的大小是如何選取的呢?

1. 如果網(wǎng)卡不支持scatter-gather,那么TCP負(fù)荷的大小為一個MSS,不用管分段和分頁。

2. 如果網(wǎng)卡支持分散聚合。

2.1 如果網(wǎng)卡支持GSO,那么TCP負(fù)荷的大小為2048 - MAX_TCP_HEADER - sizeof(struct skb_shared_info),

多出來的數(shù)據(jù)會在skb的分頁中。

2.2 如果網(wǎng)卡不支持GSO。

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

且不超過分散聚合所支持的最大長度64k,那么TCP負(fù)荷的大小為

PAGE_SIZE - MAX_TCP_HEADER - sizeof(struct skb_shared_skb),剩余的數(shù)據(jù)放在分頁區(qū)中。

2.2.2 否則TCP負(fù)荷的大小為一個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;
/* 如果網(wǎng)卡支持分散聚合 */
if (sg) {
/* 如果網(wǎng)卡支持GSO */
if (sk_can_gso(sk)) {
/* Small frames wont use a full page:
* Payload will immediately follow tcp header.
*/
/* 線性數(shù)據(jù)區(qū)中TCP負(fù)荷的大小 = 2048 - MAX_TCP_HEADER - sizeof(struct skb_shared_info).
* 較早的版本是把tmp直接置為0,把數(shù)據(jù)都放在分頁中,這會浪費內(nèi)存。
*/
tmp = SKB_WITH_OVERHEAD(2048 - MAX_TCP_HEADER);
} else {
/* 值為PAGE_SIZE - MAX_TCP_HEADER,也就是一頁中除去協(xié)議頭的剩余部分 */
int pgbreak = SKB_MAX_HEAD(MAX_TCP_HEADER);
/* 如果MSS大于一頁中的剩余部分,且不超過分散聚合所支持的最大長度64k,
* 那么線性數(shù)據(jù)區(qū)中TCP負(fù)荷的大小為一頁中出去協(xié)議頭的部分,剩余的數(shù)據(jù)會放在分頁區(qū)中。
*/
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,其線性數(shù)據(jù)區(qū)的長度為size(不包括協(xié)議頭)。

[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,線性數(shù)據(jù)區(qū)的大小 =
* 通過select_size()得到的線性數(shù)據(jù)區(qū)中TCP負(fù)荷的大小 + 最大的協(xié)議頭長度。
*/
skb = alloc_skb_fclone(size + sk->sk_prot->max_header, gfp);
if (skb) {
/* skb->truesize包括TCP負(fù)荷大小,sk_buff、skb_shared_info結(jié)構(gòu)大小,以及協(xié)議頭的大小。
* 調(diào)用sk_wmem_schedule()來從整個TCP的層面判斷此次發(fā)送緩存的申請是否合法。
*/
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的申請失敗了 */
/* 設(shè)置TCP層的內(nèi)存壓力標(biāo)志 */
sk->sk_prot->enter_memory_pressure(sk);
/* 減小sock發(fā)送緩沖區(qū)的上限,使得sndbuf不超過發(fā)送隊列總大小的一半,
* 不低于兩個數(shù)據(jù)包的MIN_TRUESIZE。
*/
sk_stream_moderate_sndbuf(sk);
}
}

TCP層內(nèi)存壓力志,超過tcp_mem[1]后設(shè)置,低于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層的內(nèi)存壓力標(biāo)志 */
}
}

減小sock發(fā)送緩沖區(qū)的上限,使得sndbuf不超過發(fā)送隊列總大小的一半,

不低于兩個數(shù)據(jù)包的MIN_TRUESIZE。

[java]
static inline void sk_stream_moderate_sndbuf(struct sock *sk)
{
/* 如果用戶沒有使用SO_SNDBUF選項 */
if (! (sk->sk_userlocks & SOCK_SNDBUF_LOCK)) {
/* 取當(dāng)前sndbuf、發(fā)送隊列總大小1/2的小者 */
sk->sk_sndbuf = min(sk->sk_sndbuf, sk->sk_wmem_queued >> 1);
/* 取當(dāng)前sndbuf、兩個數(shù)據(jù)包的總大小的大者 */
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發(fā)送隊列的尾部,增加發(fā)送隊列的大小,減小預(yù)分配緩存的大小。

[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; /* 值為發(fā)送隊列中的最后一個字節(jié)序號加一 */
tcb->tcp_flags = TCPHDR_ACK;
tcb->sacked = 0;
skb_header_release(skb); /* 增加skb負(fù)荷部分的引用計數(shù) */
tcp_add_write_queue_tail(sk, skb); /* 把skb添加到發(fā)送隊列的尾部 */
sk->sk_wmem_queued += skb->truesize; /* 增加發(fā)送隊列的總大小 */
sk_mem_charge(sk, skb->truesize); /* 減小預(yù)分配緩存的大小 */
if (tp->nonagle & TCP_NAGLE_PUSH) /* 取消禁止nagle的標(biāo)志 */
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添加到發(fā)送隊列的尾部 */
/* Queue it, remembering where we must start sending. */
if (sk->sk_send_head == NULL) {
sk->sk_send_head = skb; /* 更新下一個要發(fā)送的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;
}

發(fā)送緩存的釋放

sk_wmem_free_skb()用來釋放skb,同時更新發(fā)送緩存的大小。

[java]
static inline void sk_wmem_free_skb(struct sock *sk, struct sk_buff *skb)
{
sock_set_flag(sk, SOCK_QUEUE_SHRUNK); /* 發(fā)送隊列中有skb被釋放了 */
sk->sk_wmem_queued -= skb->truesize; /* 更新發(fā)送隊列的總大小 */
sk_mem_uncharge(sk, skb->truesize); /* 更新剩余的預(yù)分配內(nèi)存 */
__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;
}
責(zé)任編輯:何妍 來源: CSDN博客
相關(guān)推薦

2015-09-10 09:16:45

TCP緩存

2019-09-30 09:28:26

LinuxTCPIP

2023-11-10 16:28:02

TCP窗口

2024-09-29 10:46:01

2012-02-16 11:04:32

2010-07-20 11:03:45

Telnet會話

2022-08-28 16:31:11

緩存雪崩

2021-03-15 22:42:25

NameNodeDataNode分布式

2022-09-06 15:30:20

緩存一致性

2015-10-13 15:09:31

2020-07-14 09:58:01

Python開發(fā)工具

2023-10-16 18:39:22

2009-08-07 09:35:40

Oracle發(fā)送Ema

2009-09-03 17:40:25

C#發(fā)送短信

2018-11-14 09:53:48

2020-08-05 08:30:25

Spring BootJavaSE代碼

2015-12-08 09:16:32

JavaMail郵件發(fā)送總結(jié)

2009-08-21 09:44:44

C#發(fā)送Email郵件

2023-11-08 07:51:11

RabbitMQ接收消息

2010-05-06 09:52:11

Oracle發(fā)送郵件
點贊
收藏

51CTO技術(shù)棧公眾號