重传队列实际上就是发送队列(sk->sk_write_queue),保存着发送且未确认的数据段。

当有新的数据段被确认时,需要把这些段从重传队列中删除,同时更新一些变量,包括

packets_out、sacked_out、lost_out、retrans_out等。

对于非重复的ACK,会进行RTT采样,用于更新srtt和rto等时延信息。

本文主要内容:tcp_clean_rtx_queue()的实现。

内核版本:3.2.12

Author:zhangskd @ csdn

函数实现

Q:什么是重传队列?

A:重传队列实际上就是发送队列(sk->sk_write_queue),保存着发送且未确认的数据段。

The retransmit queue is implemented using a linked list to hold all the packets currently in

flight to the receiver.

Q:tcp_clean_rtx_queue()是干什么的?

A:tcp_clean_rtx_queue() is called to remove and free the acknowledged packets from the

retransmit queue, and packets_out is decremented by the number of freed packets.

/* Remove acknowledged frames from the retransmission queue.
* If our packet is before the ack sequence we can discard it as it's confirmed to
* have arrived at the other end.
*/ static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets, u32 prior_snd_una)
{
struct tcp_sock *tp = tcp_sk(sk);
const struct inet_connection_sock *icsk = inet_csk(sk);
struct sk_buff *skb;
u32 now = tcp_time_stamp; /* 当前时间,用于计算RTT */
int fully_acked = 1; /* 表示数据段是否完全被确认 */
int flag = 0;
u32 pkts_acked = 0;
u32 reord = tp->packets_out;
u32 prior_sacked = tp->sacked_out;
s32 seq_rtt = -1;
s32 ca_seq_rtt = -1;
ktime_t last_ackt = net_invalid_timestamp(); /* 把last_ackt置为0*/ /* 遍历发送队列sk_write_queue
* 注意:遍历到snd_una即停止,也就是说如果snd_una没更新,那么这个循环马上就退出!
*/
while ((skb = tcp_write_queue_head(sk)) && skb != tcp_send_head(sk)) {
struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
u32 acked_pcount;
u32 sacked = scb->sacked; /* 记分板scoreboard */ /* Determine how many packets and what bytes were acked, tso and else.
* tp->snd_una已经是更新过的了,所以从发送队列头到snd_una就是此ACK确认的数据量。
*/
if (after(scb->end_seq, tp->snd_una)) {
/* 如果没有使用TSO,或seq >= snd_una,那么就退出遍历*/
if (tcp_skb_pcount(skb) == 1 || ! after(tp->snd_una, scb->seq))
break; /* 如果只确认了TSO段中的一部分,则截掉确认的部分,并统计确认了多少段*/
acked_pcount = tcp_tso_acked(sk, skb); if (! acked_pcount) /* 处理出错 */
break; fully_acked = 0; /* 表示没有确认完TSO段*/
} else {
acked_pcount = tcp_skb_pcount(skb); /* 统计确认段的个数*/
} /* 如果此段被重传过*/
if (sacked & TCPCB_RETRANS) { if (sacked & TCPCB_SACKED_RETRANS) /* 之前重传了还没有恢复*/
tp->retrans_out -= acked_pcount; /* 更新网络中重传且未确认段的数量*/ flag |= FLAG_RETRANS_DATA_ACKED;
ca_seq_rtt = -1;
seq_rtt = -1; if ((flag & FLAG_DATA_ACKED) || (acked_pcount > 1))
flag |= FLAG_NONHEAD_RETRANS_ACKED; } else { /* 如果此段没有被重传过*/
ca_seq_rtt = now - scb->when; /* 通过此ACK计算skb的RTT采样值*/
last_ackt = skb->tstamp; /* 获取此skb的发送时间,可以精确到纳秒!*/
if (seq_rtt < 0) {
seq_rtt = ca_seq_rtt;
} /* 如果SACK块中有空洞,那么保存其中序号最小号的 */
if (! (sacked & TCPCB_SACKED_ACKED))
reord = min(pkts_acked, reord);
} /* 如果skb之前是带有SACK标志 */
if (sacked & TCPCB_SACKED_ACKED)
tp->sacked_out -= acked_pcount; /* 更新sacked_out */ /* 如果skb之前是带有LOST标志 */
if (sacked & TCPCB_LOST)
tp->lost_out -= acked_pcount; /* 更新lost_out */ tp->packets_out -= acked_pcount; /* 更新packets_out */
pkts_acked += acked_pcount; /* 累加此ACK确认的数据量*/ /* Initial outgoing SYN's get put onto the write_queue just like anything else
* we transmit. It is not true data, and if we misinform our callers that this ACK
* acks real data, we will erroneously exit connection startup slow start one packet
* too quickly. This is severely frowned upon behavior.
*/
if (! (scb->flags & TCPHDR_SYN)) {
flag |= FLAG_DATA_ACKED; /* 确认了新的数据 */ } else {
flag |= FLAG_SYN_ACKED; /* 确认了SYN段 */
tp->retrans_stamp = 0; /* Clear the stamp of the first SYN */
} if (! fully_acked) /* 如果TSO段没被完全确认,则到此为止*/
break; tcp_unlink_write_queue(skb, sk); /* 从发送队列上移除skb */
sk_wmem_free_skb(sk, skb); /* 删除skb的内存对象*/
tp->scoreboard_skb_hint = NULL;
if (skb == tp->retransmit_skb_hint)
tp->retransmit_skb_hint = NULL;
if (skb == tp->lost_skb_hint)
tp->lost_skb_hint = NULL;
} /* 退出循环了这里*/ if (likely(between(tp->snd_up, prior_snd_una, tp->snd_una)))
tp->snd_up = tp->snd_una; /* 更新Urgent pointer */ if (skb && (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))
flag |= FLAG_SACK_RENEGING; /* 虚假的SACK */ /* 如果此ACK确认了新数据,使snd_una前进了*/
if (flag & FLAG_ACKED) {
const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops; /* 如果路径MTU的探测段被确认了*/
if (unlikely(icsk->icsk_mtup.probe_size &&
! after(tp->mtu_probe.probe_seq_end, tp->snd_una))) {
tcp_mtup_probe_success(sk);
} /* 更新srtt、RTO等RTT相关变量*/
tcp_ack_update_rtt(sk, flag, seq_rtt);
tcp_rearm_rto(sk); /* 重置超时重传定时器*/ if (tcp_is_reno(tp)) {
/* Reno模拟SACK处理,更新tp->sacked_out。
* 如果检测到乱序,更新tp->reordering。
*/
tcp_remove_reno_sacks(sk, pkts_acked); } else {
int delta;
/* Non-retransmitted hole got filled? That's reordering。
* 如果之前没有SACK,prior_fackets为0,不会更新。
*/
if (reord < prior_fackets)
tcp_update_reordering(sk, tp->fackets_out - reord, 0); /* 更新乱序队列大小*/ delta = tcp_is_fack(tp) ? pkts_acked : prior_sacked - tp->sacked_out;
tp->lost_cnt_hint = -= min(tp->lost_cnt_hint, delta);
} tp->fackets_out -= min(pkts_acked, tp->fackets_out); /* 更新fackets_out */ /* 如果定义了pkts_acked()钩子*/
if (ca_ops->pkts_acked) {
s32 rtt_us = -1;
/* Is the ACK triggering packet unambiguous?,确认了非重传的数据段 */
if (! (flag & FLAG_RETRANS_DATA_ACKED)) {
/* High resolution needed and available?
* 高精确度的RTT测量,可以精确到微秒!
*/
if (ca_ops->flags & TCP_CONG_RTT_STAMP &&
! ktime_equal(last_ackt, net_invalid_timestamp()))
rtt_us = ktime_us_delta(ktime_get_real(), last_ackt); else if (ca_seq_rtt >=0) /* 普通测量,精确到毫秒,再转为微秒*/
rtt_us = jiffies_to_usecs(ca_seq_rtt);
} ca_ops->pkts_acked(sk, pkts_acked, rtt_us); /* 我们可以自定义的 */
}
} #if FASTRETRANS_DEBUG > 0
WARN_ON((int) tp->sacked_out < 0);
WARN_ON((int) tp->lost_out < 0);
WARN_ON((int) tp->retrans_out < 0); if (! tp->packets_out && tcp_is_sack(tp)) {
icsk = inet_csk(sk);
if (tp->lost_out) {
printk(KERN_DEBUG "Leak l=%u %d\n", tp->lost_out, icsk->icsk_ca_state);
tp->lost_out = 0;
} if (tp->sacked_out) {
printk(KERN_DEBUG "Leak s=%u %d\n", tp->sacked_out, icsk->icsk_ca_state);
tp->sacked_out = 0;
} if (tp->retrans_out) {
printk(KERN_DEBUG "Leak r=%u %d\n", tp->retrans_out, icsk->icsk_ca_state);
tp->retrans_out = 0;
}
} #endif
return flag;
}

ktime_t

/*
* ktime_t
* On 64-bit CPUs a single 64-bit variable is used to store the hrtimes internal
* representation of time values in scalar nanoseconds. The design plays out
* best on 64-bit CPUs, where most conversions are NOPs and most arithmetic
* ktime_t operations are plain arithmetic operations.
*
* On 32-bit CPUs an optimized representation of the timespec structure is used to
* avoid expensive conversions from and to timespecs. The endian-aware order of
* the tv struct members is chosen to allow mathematical operations on the tv64
* member of the union too, which for certain operations produces better code.
*
* For architectures with efficient support for 64/32-bit conversions the plain scalar
* nanosecond based representation can be selected by the config switch
* CONFIG_KTIME_SCALAR.
*/ union ktime {
s64 tv64; #if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
struct {
#ifdef __BIG_ENDIAN
s32 sec, nsec;
#else
s32 nsec, sec;
#endif
} tv;
#endif
}; typedef union ktime ktime_t; /* 返回值为0的ktime_t*/
static inline ktime_t net_invalid_timestamp(void)
{
return ktime_set(0, 0);
}

TSO

当TSO段不是整个被确认,而是被确认一部分时,那么就分割TSO段,返回确认的段数。

/* If we get here, the whole TSO packet has not been acked. */
static u32 tcp_tso_acked(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
u32 packets_acked; BUG_ON(! after(TCP_SKB_CB(skb)->end_seq, tp->snd_una)); packets_acked = tcp_skb_pcount(skb); /* tso段总共包括多少个段*/ if (tcp_trim_head(sk, skb, tp->snd_una - TCP_SKB_CB(skb)->seq))
return 0; packets_acked -= tcp_skb_pcount(skb); /* 减去未确认的段*/ if (packets_acked) {
BUG_ON(tcp_skb_pcount(skb) == 0);
BUG_ON(! before(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq));
} return packets_acked; /* 返回确认的段数 */
}

TCP的核心系列 — 重传队列的更新和时延的采样(一)的更多相关文章

  1. TCP的核心系列 — 重传队列的更新和时延的采样(二)

    在tcp_clean_rtx_queue()中,并非对每个ACK都进行时延采样.是否进行时延采样,跟这个ACK是否为 重复的ACK.这个ACK是否确认了重传包,以及是否使用时间戳选项都有关系. 本文主 ...

  2. TCP的核心系列 — ACK的处理(一)

    TCP发送数据包后,会收到对端的ACK.通过处理ACK,TCP可以进行拥塞控制和流控制,所以 ACK的处理是TCP的一个重要内容.tcp_ack()用于处理接收到的ACK. 本文主要内容:TCP接收A ...

  3. TCP的核心系列 — SACK和DSACK的实现(五)

    18版本对于每个SACK块,都是从重传队列头开始遍历.37版本则可以选择性的遍历重传队列的某一部分,忽略 SACK块间的间隙.或者已经cache过的部分.这主要是通过tcp_sacktag_skip( ...

  4. TCP的核心系列 — SACK和DSACK的实现(一)

    TCP的实现中,SACK和DSACK是比较重要的一部分. SACK和DSACK的处理部分由Ilpo Järvinen (ilpo.jarvinen@helsinki.fi) 维护. tcp_ack() ...

  5. TCP的核心系列 — SACK和DSACK的实现(二)

    和18版本相比,37版本的SACK和DSACK的实现做了很多改进,最明显的就是需要遍历的次数少了, 减少了CPU的消耗.37版的性能提升了,代码有大幅度的改动,逻辑也更加复杂了. 本文主要内容:37版 ...

  6. TCP的核心系列 — SACK和DSACK的实现(七)

    我们发送重传包时,重传包也可能丢失,如果没有检查重传包是否丢失的机制,那么只能依靠超时来恢复了. 37版本把检查重传包是否丢失的部分独立出来,这就是tcp_mark_lost_retrans(). 在 ...

  7. TCP的核心系列 — SACK和DSACK的实现(六)

    上篇文章中我们主要说明如何skip到一个SACK块对应的开始段,如何walk这个SACK块包含的段,而没有涉及到 如何标志一个段的记分牌.37版本把给一个段打标志的内容独立出来,这就是tcp_sack ...

  8. TCP的核心系列 — SACK和DSACK的实现(四)

    和18版本不同,37版本把DSACK的检测部分独立出来,可读性更好. 37版本在DSACK的处理中也做了一些优化,对DSACK的两种情况分别进行处理. 本文主要内容:DSACK的检测.DSACK的处理 ...

  9. TCP的核心系列 — ACK的处理(二)

    本文主要内容:tcp_ack()中的一些细节,如发送窗口的更新.持续定时器等. 内核版本:3.2.12 Author:zhangskd @ csdn 发送窗口的更新 什么时候需要更新发送窗口呢? (1 ...

随机推荐

  1. Python 制作Android开发 所需的适配不同分辨率的套图

    使用Python做起工具来还真是爽,简单,方便,快捷.今天忙活了一下,制作出一个比较实用的小工具. 自动化套图制作,适配不同屏幕 尤其是对于android开发来说,要适配不同屏幕就需要多套切图,那么. ...

  2. 理解性能的奥秘——应用程序中慢,SSMS中快(2)——SQL Server如何编译存储过程

    本文属于<理解性能的奥秘--应用程序中慢,SSMS中快>系列 接上文:理解性能的奥秘--应用程序中慢,SSMS中快(1)--简介 本文介绍SQL Server如何编译存储过程并使用计划缓存 ...

  3. 向VS中添加个PATH怎么样?

    属性中,有个调试的目录,向"环境"中添加: PATH="your path";$(PATH) 可以调试用一下.

  4. SQLite Where 子句(http://www.w3cschool.cc/sqlite/sqlite-where-clause.html)

    SQLite Where 子句 SQLite的 WHERE 子句用于指定从一个表或多个表中获取数据的条件. 如果满足给定的条件,即为真(true)时,则从表中返回特定的值.您可以使用 WHERE 子句 ...

  5. Python中使用rrdtool结合Django进行带宽监控

    我们有个网关需要做下带宽监控,能获取这个数据的唯一方法就是登录到管理界面查看.然后咱就写了个模拟登录的爬虫,定时抓取数据用rrdtool存储,最后通过Django来展示.这里就涉及了python的rr ...

  6. JDBC-数据库的更新操作编程(三)

    首先建立一个静态方法,代码如下: public static Statement getStatement(){ Statement st = null; try { Class.forName(&q ...

  7. android ndk之hello world

    前言:Android NDK r7及以上的版本已经集成了Cygwin编译环境,也就是说,我们完全可以抛弃庞大的Cygwin了. r6及以下版本,也可以抛弃几个G的完整版,使用精简过的Mini-Cygw ...

  8. 【问题汇总】ScrollView嵌套ListView的问题

    因产品的需求,需要在ScrollView中嵌套ListView来达到效果.众所周知,ScrollVIew和ListView都是可滑动的容器,嵌套使用一定会出现一些问题. [html] view pla ...

  9. linux2.6内核链表

    一.        链表数据结构简介      链表是一种常用的组织有序数据的数据结构,它通过指针将一系列数据节点连接成一条数据链,是线性表的一种重要实现方式.相对于数组,链表具有更好的动态性,建立链 ...

  10. 在Android中使用AlarmManager

    AlarmManager是Android中的一种系统级别的提醒服务,它会为我们在特定的时刻广播一个指定的Intent.而使用Intent的时候,我们还需要它执行一个动作,如startActivity, ...