TCP的核心系列 — SACK和DSACK的实现(五)
18版本对于每个SACK块,都是从重传队列头开始遍历。37版本则可以选择性的遍历重传队列的某一部分,忽略
SACK块间的间隙、或者已经cache过的部分。这主要是通过tcp_sacktag_skip()和tcp_sacktag_walk()完成的。
tcp_sacktag_skip()可以直接找到包含某个序号的skb,通常用于定位SACK块的开头。
tcp_sacktag_walk()则遍历两个序号之间的skb,通常用于遍历一个SACK块。
本文主要内容:SACK的遍历函数tcp_sacktag_skip()和tcp_sacktag_walk()。
Author:zhangskd
tcp_sacktag_skip
从当前skb开始遍历,查找skip_to_seq序号对应的skb,同时统计fackets_out。
这样可以从当前包,直接遍历到某个块的start_seq,而不用从头开始遍历,也可以跳过块间的间隙。
/* Avoid all extra work that is being done by sacktag while walking in a normal way */
static struct sk_buff *tcp_sacktag_skip(struct sk_buff *skb, struct sock *sk,
struct tcp_sacktag_state *state, u32 skip_to_seq)
{
tcp_for_write_queue_from(skb, sk) {
if (skb == tcp_send_head(sk)) /* 到了发送队列头,即下一个将要发送的数据包 */
break; if (after(TCP_SKB_CB(skb)->end_seq, skip_to_seq)) /* 找到包含skip_to_seq序号的数据包了 */
break; state->fack_count += tcp_skb_pcount(skb); /* 统计fackets_out个数 */
} return skb; /* 返回包含skip_to_seq的skb */
}
tcp_sacktag_walk
遍历一个SACK块,如果SACK块包含了多个连续的skb,那么先尝试合并这些段。
为什么要合并呢?因为下次遍历的时候,要遍历的包个数就减少了,能提高效率。
如果skb完全包含在块中,则调用tcp_sacktag_one更新该段的记分牌。
static struct sk_buff *tcp_sacktag_walk(struct sk_buff *skb, struct sock *sk,
struct tcp_sack_block *next_dup,
struct tcp_sacktag_state *state,
u32 start_seq, u32 end_seq,
int dup_sack_in)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *tmp; tcp_for_write_queue_from(skb, sk) {
int in_sack = 0;
int dup_sack = dup_sack_in; if (skb == tcp_send_head(sk)) /* 遍历到发送队列头了 */
break; if (! before(TCP_SKB_CB(skb)->seq, end_seq)) /* skb序号超过SACK块了 */
break; /* 如果下一个块是DSACK,且此skb可能包含在其中 */
if ((next_dup != NULL) &&
before(TCP_SKB_CB(skb)->seq, next_dup->end_seq)) { /* 此skb是否完全包含在DSACK块中 */
in_sack = tcp_match_skb_to_sack(sk, skb, next_dup->start_seq, next_dup->end_seq); if (in_sack > 0)
dup_sack = 1; /* 表示这个skb被DSACK */
} if (in_sack <= 0) {
/* 一个SACK块可能包括多个skb,尝试把这些连续的skb合并 */
tmp = tcp_shift_skb_data(sk, skb, state, start_seq, end_seq, dup_sack); if (tmp != NULL) { /* 合并成功 */
if (tmp != skb) { /* tmp和当前段地址不同,则跳到合并后的段处理 */
skb = tmp;
continue;
}
in_sack = 0; } else { /* 合并不成功,单独处理这个段 */
in_sack = tcp_match_skb_to_sack(sk, skb, start_seq, end_seq); /* 段是否完全包含在块中 */
}
} if (unlikely(in_sack < 0))
break; /* 如果这个段完全包含在块中,进行处理 */
if (in_sack) { /* 就是在这里:标志这个段的记分牌!*/
TCP_SKB_CB(skb)->sacked = tcp_sacktag_one(skb, sk, state, dup_sack, tcp_skb_pcount(skb)); /* 如果当前skb的开始序列号大于被SACK的包的最高初始序列号 */
if (! before(TCP_SKB_CB(skb)->seq, tcp_highest_sack_seq(tp)))
tcp_advance_highest_sack(sk, skb); /*把highest_sack设为skb->next */
} state->fack_count += tcp_skb_pcount(skb); /* 更新fackets_out */
} return skb; /* 遍历到此skb退出 */
}
tcp_match_skb_to_sack()用于检查一个数据段是否完全包含在一个SACK块中,主要考虑到GSO分段。
/* Check if skb is fully within the SACK block.
* In presence of GSO skbs, the incoming SACK may not exactly match but we can find smaller MSS
* aligned portion of it that matches. Therefore we might need to fragment which may fail and creates
* some hassle (caller must handle error case returns).
* FIXME: this could be merged to shift decision code
*/
static int tcp_match_skb_to_sack(struct sock *sk, struct sk_buff *skb, u32 start_seq, u32 end_seq)
{
int in_sack, err;
unsigned int pkt_len;
unsigned int mss; /* 如果start_seq <= skb->seq < skb->end_seq <= end_seq,说明skb完全包含在SACK块中 */
in_sack = ! after(start_seq, TCP_SKB_CB(skb)->seq) &&
! before(end_seq, TCP_SKB_CB(skb)->end_seq); /* 如果有GSO分段,skb可能部分包含在块中 */
if (tcp_skb_pcount(skb) > 1 && ! in_sack &&
after(TCP_SKB_CB(skb)->end_seq, start_seq)) { mss = tcp_skb_mss(skb);
in_sack = ! after(start_seq, TCP_SKB_CB(skb)->seq); /* 前半部在块中 */ /* 这里根据skb->seq和start_seq的大小,分情况处理 */
if (! in_sack) { /* 后半部在块中 */
pkt_len = start_seq - TCP_SKB_CB(skb)->seq; /* skb在块之前的部分 */
if (pkt_len < mss)
pkt_len = mss; } else {
pkt_len = end_seq - TCP_SKB_CB(skb)->seq; /* skb在块内的部分 */
if (pkt_len < mss)
return -EINVAL;
} /* Round if necessary so that SACKs cover only full MSSes and/or the remaining
* small portion (if present)
*/
if (pkt_len > mss) {
unsigned int new_len = (pkt_len / mss) * mss;
if (! in_sack && new_len < pkt_len) {
new_len += mss;
if (new_len > skb->len)
return 0;
}
pkt_len = new_len;
} err = tcp_fragment(sk, skb, pkt_len, mss); /* 把skb分为两个包,SACK块内的和SACK块外的 */
} return in_sack;
}
tcp_shift_skb_data()尝试把SACK块内的多个包合成一个,可以提升遍历效率。
一个SACK块可能包括多个skb,尝试把这些连续的skb合成一个。
/* Try to collapsing SACK blocks spanning across multiple skbs to a single skb. */
static struct sk_buff *tcp_shift_skb_data(struct sock *sk, struct sk_buff *skb,
struct tcp_sacktag_state *state,
u32 start_seq, u32 end_seq, int dup_sack)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *prev;
int mss;
int pcount = 0;
int len;
int in_sack; if (! sk_can_gso(sk))
goto fallback; ... fallback:
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SACKSHIFTFALLBACK);
return NULL;
}
TCP的核心系列 — SACK和DSACK的实现(五)的更多相关文章
- TCP的核心系列 — SACK和DSACK的实现(一)
TCP的实现中,SACK和DSACK是比较重要的一部分. SACK和DSACK的处理部分由Ilpo Järvinen (ilpo.jarvinen@helsinki.fi) 维护. tcp_ack() ...
- TCP的核心系列 — SACK和DSACK的实现(二)
和18版本相比,37版本的SACK和DSACK的实现做了很多改进,最明显的就是需要遍历的次数少了, 减少了CPU的消耗.37版的性能提升了,代码有大幅度的改动,逻辑也更加复杂了. 本文主要内容:37版 ...
- TCP的核心系列 — SACK和DSACK的实现(七)
我们发送重传包时,重传包也可能丢失,如果没有检查重传包是否丢失的机制,那么只能依靠超时来恢复了. 37版本把检查重传包是否丢失的部分独立出来,这就是tcp_mark_lost_retrans(). 在 ...
- TCP的核心系列 — SACK和DSACK的实现(三)
不论是18版,还是37版,一开始都会从TCP的控制块中取出SACK选项的起始地址. SACK选项的起始地址是保存在tcp_skb_cb结构的sacked项中的,那么这是在什么时候做的呢? SACK块并 ...
- TCP的核心系列 — SACK和DSACK的实现(六)
上篇文章中我们主要说明如何skip到一个SACK块对应的开始段,如何walk这个SACK块包含的段,而没有涉及到 如何标志一个段的记分牌.37版本把给一个段打标志的内容独立出来,这就是tcp_sack ...
- TCP的核心系列 — SACK和DSACK的实现(四)
和18版本不同,37版本把DSACK的检测部分独立出来,可读性更好. 37版本在DSACK的处理中也做了一些优化,对DSACK的两种情况分别进行处理. 本文主要内容:DSACK的检测.DSACK的处理 ...
- TCP的核心系列 — ACK的处理(二)
本文主要内容:tcp_ack()中的一些细节,如发送窗口的更新.持续定时器等. 内核版本:3.2.12 Author:zhangskd @ csdn 发送窗口的更新 什么时候需要更新发送窗口呢? (1 ...
- TCP的核心系列 — ACK的处理(一)
TCP发送数据包后,会收到对端的ACK.通过处理ACK,TCP可以进行拥塞控制和流控制,所以 ACK的处理是TCP的一个重要内容.tcp_ack()用于处理接收到的ACK. 本文主要内容:TCP接收A ...
- TCP的核心系列 — 重传队列的更新和时延的采样(二)
在tcp_clean_rtx_queue()中,并非对每个ACK都进行时延采样.是否进行时延采样,跟这个ACK是否为 重复的ACK.这个ACK是否确认了重传包,以及是否使用时间戳选项都有关系. 本文主 ...
随机推荐
- Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑
转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52835829 前言:上篇中,&l ...
- OpenCV +Python 制作画板
效果图 画图工具实现 代码 运行结果 程序分析 窗体自由度 如何退出程序 滚动条相关 支持的事件 首先声明一下,本例思路不是博主原创,博主在前人的代码上进行了个性化的修改,制作了一个简单的画图工具.下 ...
- Ubuntu Intel显卡驱动安装 (Ubuntu 14.04--Ubuntu 16.10 + Intel® Graphics Update Tool)
最近使用在使用Ubuntu时,发现大部分情况下,不安装显卡驱动,使用默认驱动,都是没有问题的,但对于一些比较奇特配置的电脑,如下所示,如果使用默认驱动,会时常莫名其妙死机crash,尤其是在使用Ope ...
- 用premake5创建lua532工程
用premake5创建lua532工程 (金庆的专栏) lua-5.3.2只有Makefile,根据readme.html中"Building Lua on other systems&qu ...
- 理解性能的奥秘——应用程序中慢,SSMS中快(3)——不总是参数嗅探的错
本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(2)--SQL Server如何编译存储过程 在我们开始深入研究如何处理 ...
- Linux动态频率调节系统CPUFreq之三:governor
在上一篇文章中,介绍了cpufreq的core层,core提供了cpufreq系统的初始化,公共数据结构的建立以及对cpufreq中其它子部件提供注册功能.core的最核心功能是对policy的管理, ...
- Dynamics CRM 权限整理二
接上篇http://blog.csdn.net/vic0228/article/details/50510605,继续列举CRM相关权限 prvReadBusinessUnit privilege(I ...
- SpriteKit:检测当新场景显示以后
Detecting When a New Scene Is Presented Sprite Kit在SKScene类中提供2个可以重载的方法用来检测当一个场景过渡出去或过渡进来的时候. 第一个方法是 ...
- 有奖试读—Windows PowerShell实战指南(第2版)
为什么要学PowerShell? Windows用户都已习惯于使用图形化界面去完成工作,因为GUI总能轻易地实现很多功能,并且不需要记住很多命令.使得短时间学会一种工具成为可能. 但是不幸的是,GUI ...
- OpenCV实时美颜摄像并生成H264视频流
为什么美颜摄像这么简单的功能,OpenCV这个开源项目网上很少有代码呢?对于在windows平台下,生成h264视频流也比价麻烦,没有现成的api可以使用,需要借助MinGw编译libx264,或者f ...