TCP的发送系列 — 发送缓存的管理(一).docx_第1页
TCP的发送系列 — 发送缓存的管理(一).docx_第2页
TCP的发送系列 — 发送缓存的管理(一).docx_第3页
TCP的发送系列 — 发送缓存的管理(一).docx_第4页
TCP的发送系列 — 发送缓存的管理(一).docx_第5页
已阅读5页,还剩20页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

TCP的发送系列 发送缓存的管理(一)数据结构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_mem1后设置,低于tcp_mem0后清除 */.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_memtcp_mem是整个TCP层的内存消耗,单位为页。long sysctl_tcp_mem3 _read_mostly;tcp_mem - vector of 3 INTEGERs: min, pressure, maxmin: 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, whichis 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_mem3数组。tcp_mem0是最小值,为3/32的系统内存。tcp_mem1是压力值,为1/8的系统内存,也是最小值的4/3。tcp_mem2是最大值,为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_mem0 = limit / 4 * 3; /* 最小值设为3/32的系统内存 */sysctl_tcp_mem1 = limit; /* 压力值设为1/8的系统内存 */sysctl_tcp_mem2 = sysctl_tcp_mem0 * 2; /* 最大值设为3/16的系统内存 */(2) tcp_wmemtcp_wmem是每个sock的写缓存,单位为字节。int sysctl_tcp_wmem3 _read_mostly;tcp_wmem - vector of 3 INTEGERs: min, default, maxmin: 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 pagedefault: 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: 16Kmax: Maximal amount of memory allowed for automatically tuned send buffersfor TCP sockets. This value does not override net.core.wmem_max.Calling setsockopt() with SO_SNDBUF disables automatic tuning of thatsockets send buffer size, in which case this value is ignored.Default: between 64K and 4MB, depending on RAM size.tcp_wmem0是最小值,为4KB。tcp_wmem1是默认值,为16KB。tcp_wmem2是最大值,为4MB。tcp_rmem0是最小值,为4KB。tcp_rmem1是默认值,为87380字节。tcp_wmem2是最大值,为6MB(之前的内核为4MB)。javavoid _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() sk_sndbufsock发送缓冲区的上限sk-sk_sndbuf在tcp_init_sock()中初始化,初始值为tcp_wmem1,一般为16K。java void tcp_init_sock(struct sock *sk).sk-sk_sndbuf = sysctl_tcp_wmem1; /* 16K */sk-sk_rcvbuf = sysctl_tcp_rmem1; /* 85K */.(4) wmem_default和wmem_max/proc/sys/net/core/wmem_max和/proc/sys/net/core/wmem_default,默认值为256个的负荷为256字节的数据段的总内存消耗。对于TCP而言,wmem_default会被tcp_wmem1给覆盖掉,而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_rmem1。*/if (! (sk-sk_userlocks & SOCK_RCVBUF_LOCK)tcp_fixup_rcvbuf(sk);/* 如果用户没有使用SO_SNDBUF选项,就调整发送缓冲区的上限。* 调整之后,一般sk-sk_sndbuf会大于初始值tcp_wmem1。*/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 sk_rcvbuf sk_rcvbuf = min(rcvmem, syscl_tcp_rmem2);初始的接收窗口大小,一般为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 - BOOLEANIf set, TCP performs receive buffer auto-tuning, attempting to automaticallysize the buffer (no greater than tcp_rmem2) to match the size required bythe 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_wmem1小于本次计算的值sndmem,* 那么更新sk-sk_sndbuf。由于默认值为16K,所以肯定会更新的:)*/if (sk-sk_sndbuf sk_sndbuf = min(sndmem, sysctl_tcp_wmem2);(2) 建立连接以后当接收到ACK后,会检查是否需要调整发送缓存的上限sk-sk_sndbuf。tcp_rcv_established / tcp_rcv_state_processtcp_data_snd_checktcp_check_spacetcp_new_spacejava 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_mem0。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_mem0,就不扩大发送缓存的上限了 */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 sk_allocation);if (! skb)goto wait_for_memory;.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 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_mem1后设置,低于tcp_mem0后清除。javavoid 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。javastatic 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,wmem_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; /* 值为发送队列中的最后一个字节

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论