TCP发送源码学习(2)--tcp_write_xmit
一、tcp_write_xmit()将发送队列上的SBK发送出去,返回值为0表示发送成功。函数执行过程如下:
1、检测拥塞窗口的大小。
2、检测当前报文是否完全处在发送窗口内。
3、检测报文是否使用nagle算法进行发送。
4、通过以上检测后将该SKB发送出去。
5、循环检测发送队列上所有未发送的SKB。
static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
int push_one, gfp_t gfp)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
unsigned int tso_segs, sent_pkts;
int cwnd_quota;
int result; /*sent_pkts用来统计函数中已发送报文总数。*/
sent_pkts = 0; if (!push_one) {
/* Do MTU probing. */
result = tcp_mtu_probe(sk);
if (!result) {
return 0;
} else if (result > 0) {
sent_pkts = 1;
}
}
/*13~21首先初始化为0,接着发送一个路径MTU探测报文,如果成功则发送报文数加1。*/ /*如果发送队列不为空,则准备开始发送报文*/
while ((skb = tcp_send_head(sk))) {
unsigned int limit; /*设置有关TSO的信息,包括GSO类型,GSO分段的大小等等。这些信息是准备给软件TSO分段使用的。
如果网络设备不支持TSO,但又使用了TSO功能,则报文在提交给网络设备之前,需进行软分段,即由代码实现TSO分段。*/
tso_segs = tcp_init_tso_segs(sk, skb, mss_now);
BUG_ON(!tso_segs); /*检测拥塞窗口的大小,如果为0,则说明拥塞窗口已满,目前不能发送。
拿拥塞窗口和正在网络上传输的包数目相比,如果拥塞窗口还大,则返回拥塞窗口减掉正在网络上传输的包数目剩下的大小。
该函数目的是判断正在网络上传输的包数目是否超过拥塞窗口,如果超过了,则不发送。
tcp_cwnd_test()源代码见段二*/
cwnd_quota = tcp_cwnd_test(tp, skb);
if (!cwnd_quota)
break; /*检测当前报文是否完全处于发送窗口内,如果是则可以发送,否则不能发送*/
if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))
break; /*tso_segs=1表示无需tso分段*/
if (tso_segs == 1) {
/*根据nagle算法,计算是否需要发送数据*/
if (unlikely(!tcp_nagle_test(tp, skb, mss_now,
(tcp_skb_is_last(sk, skb) ?
nonagle : TCP_NAGLE_PUSH))))
break;
} else {
/*如果需要TSO分段,则检测该报文是否应该延时发送。tcp_tso_should_defer()用来检测GSO段是否需要延时发送。
在段中有FIN标志,或者不处于open拥塞状态,或者TSO段延时超过2个时钟滴答,或者拥塞窗口和发送窗口的最小值大于64K或三倍的当前有效MSS,
在这些情况下会立即发送,而其他情况下会延时发送,这样主要是为了减少软GSO分段的次数,以提高性能。*/
if (!push_one && tcp_tso_should_defer(sk, skb))
break;
} /*limit为再次分段的段长,初始化为当前MSS*/
limit = mss_now;
/*在TSO分片大于1并且不是URG模式下,通过mss_now计算limit的值
以发送窗口和拥塞窗口的最小值作为分段段长*/
if (tso_segs > 1 && !tcp_urg_mode(tp))
limit = tcp_mss_split_point(sk, skb, mss_now,
cwnd_quota); /*如果SKB中的数据长度大于分段段长,则调用tso_fragment()根据该段长进行分段,如果分段失败则暂不发送*/
if (skb->len > limit &&
unlikely(tso_fragment(sk, skb, limit, mss_now, gfp)))
break;
/*line61~71:根据条件,可能需要对SKB中的报文进行分段处理,分段的报文包括两种:一种是普通的用MSS分段的报文,另一种则是TSO分段的报文。
能否发送报文主要取决于两个条件:一是报文需完全在发送窗口中,而是拥塞窗口未满。第一种报文,应该不会再分段了,因为在tcp_sendmsg()中创建报文的SKB时已经根据MSS处理了,
而第二种报文,则一般情况下都会大于MSS,因为通过TSO分段的段有可能大于拥塞窗口的剩余空间,如果是这样,就需要以发送窗口和拥塞窗口的最小值作为段长对报文再次分段。*/ /*更新TCP时间戳,记录此报文发送的时间,
#define tcp_time_stamp ((__u32)(jiffies))*/
TCP_SKB_CB(skb)->when = tcp_time_stamp; /*调用tcp_transmit_skb()发送TCP段,其中第三个参数1表示是否需要克隆被发送的报文,详见后续对此函数的分析*/
if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
break; /* Advance the send_head. This one is sent out.
* This call will increment packets_out.
*/
/*调用tcp_event_new_data_sent()-->tcp_advance_send_head()更新sk_send_head,即取发送队列中的下一个SKB。
同时更新snd_nxt,即等待发送的下一个TCP段的序号,然后统计发出但未得到确认的数据报个数。
最后如果发送该报文前没有需要确认的报文,则复位重传定时器,对本次发送的报文做重传超时计时。*/
tcp_event_new_data_sent(sk, skb); /*更新struct tcp_sock中的snd_sml字段。snd_sml表示最近发送的小包(小于MSS的段)的最后一个字节序号,
在发送成功后,如果报文小于MSS,即更新该字段,主要用来判断是否启动nagle算法*/
tcp_minshall_update(tp, mss_now, skb);
sent_pkts++;//更新已发送报文总数 if (push_one)
break;
} /*如果本次有数据发送,则对TCP拥塞窗口进行检查确认。*/
if (likely(sent_pkts)) {
tcp_cwnd_validate(sk);
return 0;
} /*如果本次没有数据发送,则根据已发送但未确认的报文数packets_out和sk_send_head返回,packets_out不为零或sk_send_head为空都视为有数据发出,因此返回成功。*/
return !tp->packets_out && tcp_send_head(sk);
}
二、tcp_init_tso_segs()函数
该函数根据当前mss的值重新设置数据包中的struct skb_shared_info内的关于GSO的内容项。
static int tcp_init_tso_segs(struct sock *sk, struct sk_buff *skb,
unsigned int mss_now)
{
int tso_segs = tcp_skb_pcount(skb); if (!tso_segs || (tso_segs > 1 && tcp_skb_mss(skb) != mss_now)) {
tcp_set_skb_tso_segs(sk, skb, mss_now);
tso_segs = tcp_skb_pcount(skb);
}
return tso_segs;
}
三、tcp_cwnd_test()函数
static inline unsigned int tcp_cwnd_test(struct tcp_sock *tp,
struct sk_buff *skb)
{
u32 in_flight, cwnd; /* Don't be strict about the congestion window for the final FIN. */
/*对FIN包不检测,让他通过*/
if ((TCP_SKB_CB(skb)->flags & TCPHDR_FIN) && tcp_skb_pcount(skb) == 1)
return 1; /*计算正在网络上传输的包数目*/
in_flight = tcp_packets_in_flight(tp);
/*获取当前拥塞窗口的大小,snd_cwnd表示当前拥塞窗口的大小*/
cwnd = tp->snd_cwnd;
if (in_flight < cwnd)
return (cwnd - in_flight); return 0;
}
static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
{
return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
}
static inline unsigned int tcp_left_out(const struct tcp_sock *tp)
{
return tp->sacked_out + tp->lost_out;
}
这是通过使用tcp的sock中的几个计数器运算得出的。
packets_out:从发送队列发出而未得到确认的TCP段的数目,该值是动态的,当有新的段发出或有新的确认收到都会增加或减少该值。
retrans_out:重传还未得到确认的TCP段数目。
tcp_left_out:已离开主机在网络中且未确认的TCP段数,包含两种情况,一是通过SACK确认的段,即sacked_out,而是已丢失的段,即lost_out。
所以left_out = sacked_out + lost_out。
left_out需要与packets_out进行区分,packets_out只是离开发送队列(不一定已离开主机),而left_out则必定离开了主机。所以packets_out必定大于或等于left_out。
四、tcp_snd_wnd_test()函数
static inline int tcp_snd_wnd_test(struct tcp_sock *tp, struct sk_buff *skb,
unsigned int cur_mss)
{
u32 end_seq = TCP_SKB_CB(skb)->end_seq; if (skb->len > cur_mss)
end_seq = TCP_SKB_CB(skb)->seq + cur_mss; return !after(end_seq, tcp_wnd_end(tp));
}
#define TCP_SKB_CB(__skb) ((struct tcp_skb_cb *)&((__skb)->cb[0]))
TCP层在SKB区块有个私有信息控制块,即skb_buff结构的cb成员,TCP利用这个字段存储了一个tcp_skb_cb结构。在TCP层,用宏TCP_SKB_CB实现访问该信息控制块,以增强代码的可读性。
struct tcp_skb_cb {
union {
struct inet_skb_parm h4;
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
struct inet6_skb_parm h6;
#endif
} header; /* For incoming frames */
__u32 seq; /* Starting sequence number */
__u32 end_seq; /* SEQ + FIN + SYN + datalen */
__u32 when; /* used to compute rtt's */
__u8 flags; /* TCP header flags. */
__u8 sacked; /* State flags for SACK/FACK. */
#define TCPCB_SACKED_ACKED 0x01 /* SKB ACK'd by a SACK block */
#define TCPCB_SACKED_RETRANS 0x02 /* SKB retransmitted */
#define TCPCB_LOST 0x04 /* SKB is lost */
#define TCPCB_TAGBITS 0x07 /* All tag bits */
#define TCPCB_EVER_RETRANS 0x80 /* Ever retransmitted frame */
#define TCPCB_RETRANS (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)
__u32 ack_seq; /* Sequence number ACK'd */
}
union {...} header:
在TCP处理接收到的TCP段之前,下层协议(IPv4或IPv6)会先处理该段,且会利用SKB中的控制块来记录每一个包中的信息,例如IPv4会记录从IP首部中解析出来的IP首部选项。
为了不破坏三层协议层私有数据,在SKB中TCP控制块的前部定义了这个结构,这包括IPv4和IPv6。
__u32 seq
__u32 end_seq
seq为当前段开始序号,而end_seq为当前段开始序号加上当前段数据长度,如果标志域中存在SYN或FIN标志,则还需加1,因为SYN和FIN标志都会消耗一个序号,利用end_seq、seq和标志,很容易得到数据长度。
__u32 when
段发送时间及段发送时记录的当前jffies值。必要时,此值也用来计算RTT。
__u8 flags
记录原始TCP首部标志。发送过程中,tcp_transmit_skb()在发送TCP段之前会根据此标志来填充发送段的TCP首部的标志域;接收过程中,会提取接收段的TCP首部标志到该字段中。
__u8 sacked
主要用来描述段的重传状态,同时标识包是否包含紧急数据。检查接收到的SACK,根据需要更新TCPCB_TAGBITS,重传引擎会根据该标志位来确定是否需要重传。一旦重传超时发生,所有的SACK状态标志将被清除,因为无须再关心其状态。无论通过哪种方式重传了包,重传超时或快速重传,都会设置TCPCB_EVER_RETRANS标志位。tcp_restransmit_skb()中设置TCPCB_EVER_RETRANS和TCPCB_SACKED_RETRANS标志位,tcp_enter_loss()中则清除TCPCB_SACKED_RETRANS标志位。
sacked的取值如下:
TCPCB_SACKED_ACKED:该段通过SACK被确认。
TCPCB_SACKED_RETRANS:该段已经重传。
TCPCB_LOST:该段在传输过程中已丢失。
__u32 ack_seq
接收到的TCP段首部中的确认序号。
from:http://sunjiangang.blog.chinaunix.net/uid-9543173-id-3543419.html
TCP发送源码学习(2)--tcp_write_xmit的更多相关文章
- TCP发送源码学习(1)--tcp_sendmsg
一.tcp_sendmsg()函数分析: int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t ...
- TCP发送源码学习(3)--tcp_transmit_skb
一.tcp_transmit_skb static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, g ...
- RocketMQ 源码学习笔记————Producer 是怎么将消息发送至 Broker 的?
目录 RocketMQ 源码学习笔记----Producer 是怎么将消息发送至 Broker 的? 前言 项目结构 rocketmq-client 模块 DefaultMQProducerTest ...
- RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?
目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目结构 rocketmq-client 模块 DefaultMQProducerTest Roc ...
- Netty 源码学习——EventLoop
Netty 源码学习--EventLoop 在前面 Netty 源码学习--客户端流程分析中我们已经知道了一个 EventLoop 大概的流程,这一章我们来详细的看一看. NioEventLoopGr ...
- MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...
- igmpproxy源码学习——igmpProxyInit()
igmpproxy源码学习--igmpProxyInit()函数具体解释.igmpproxy初始化 在执行igmpproxy的主程序igmpproxyRun()之前须要对igmpproxy进行一些配置 ...
- 源码学习之ASP.NET MVC Application Using Entity Framework
源码学习的重要性,再一次让人信服. ASP.NET MVC Application Using Entity Framework Code First 做MVC已经有段时间了,但看了一些CodePle ...
- 【Spark2.0源码学习】-1.概述
Spark作为当前主流的分布式计算框架,其高效性.通用性.易用性使其得到广泛的关注,本系列博客不会介绍其原理.安装与使用相关知识,将会从源码角度进行深度分析,理解其背后的设计精髓,以便后续 ...
随机推荐
- Git Bash
Git Bash是Git的命令行工具,可以执行Git的所有命令,但是当我们想把一个URL粘贴到Git Blash时,Ctrl+V或者右键粘贴不起作用了 方法1-使用快捷键"Insert&qu ...
- win7安装JDK6
注:虽然9已经出来了,但是今天刚好业务需要要装JDK6,所以以JDK 6作为演示,同样适用于JDK 7.8的安装. 安装 基本上一直点下一步就可以. 此处可修改安装路径. 我将JDK的安装路径设置成了 ...
- Django REST framework+Vue 打造生鲜超市(十一)
十二.支付宝沙箱环境配置 12.1.创建应用 进入蚂蚁金服开放平台(https://open.alipay.com/platform/home.htm),登录后进入管理中心-->>应用列表 ...
- C++ 前期准备
在线编译网站: http://www.dooccn.com/cpp/ 刷题: https://leetcode.com/ https://leetcode-cn.com/
- JavaScript 和 TypeScript 中的 class
对于一个前端开发者来说,很少用到 class ,因为在 JavaScript 中更多的是 函数式 编程,抬手就是一个 function,几乎不见 class 或 new 的踪影.所以 设计模式 也是大 ...
- gradlew在Travis CI没可执行权限 permission denied
问题的来源 我给一个gradlew项目添加Travis CI的时候遇到一个问题,gradlew没有可执行权限.具体错误如下: /home/travis/build.sh: line 45: ./gra ...
- [SDOI 2010]魔法猪学院
Description 题库链接 给出一张 \(n\) 个点 \(m\) 条边有向图,询问最多有多少条不同的路径从 \(1\) 到 \(n\) 并且路径长度和 \(\leq E\) . \(2\leq ...
- [JSOI 2011]分特产
Description JYY 带队参加了若干场ACM/ICPC 比赛,带回了许多土特产,要分给实验室的同学们. JYY 想知道,把这些特产分给N 个同学,一共有多少种不同的分法?当然,JYY 不希望 ...
- [NOIp 2012]国王游戏
Description 恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这 n 位大臣排成一排,国 ...
- 【BZOJ1087】【SCOI2005】互不侵犯King
Description 在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案.国王能攻击到它上下左右,以及左上 左下右上右下八个方向上附近的各一个格子,共8个格子. Input 只有一行, ...