ifconfig dropped packet

ifconfig dropped packet

ifconfig 发现 dropped 定时性地, 大概 1s 一次地增加.

在 这里 找到一些说明:

Beginning with kernel 2.6.37, it has been changed the meaning of dropped packet count. Before, dropped packets was most likely due to an error. Now, the rx_dropped counter shows statistics for dropped frames because of:

Softnet backlog full  -- (Measured from /proc/net/softnet_stat)
Bad / Unintended VLAN tags
Unknown / Unregistered protocols
IPv6 frames when the server is not configured for IPv6

If any frames meet those conditions, they are dropped before the protocol stack and the rx_dropped counter is incremented.

打开 tcpdump 之后, drop 包不再增加. 说明就是以上的这些原因.

机器使用了 bond0, 我把 bond0 下的网卡设置了 promisc 或 把 bond 设置混杂都没有效果.
但是 tcpdump 就可以. 文献上说 ifconfig 设置 prmisc 这种方法对于 bond 没有效果.

# ifconfig <interface> promisc

mlock

mlock

 mlock 用于锁定进程的内存到物理内存, 不允许其被交换到 swap. 对于要求性能与实时性的情况下比较有用.

但是物理内存是有限的, 所以系统有一些限制, 不是任意进程都可以把任意数量的内存, 设置成 mlock 的.

这个限制是通过 ulimit 实现的.

#ulimit -a
-t: cpu time (seconds)              unlimited
-f: file size (blocks)              unlimited
-d: data seg size (kbytes)          unlimited
-s: stack size (kbytes)             8192
-c: core file size (blocks)         0
-m: resident set size (kbytes)      unlimited
-u: processes                       11795
-n: file descriptors                1024
-l: locked-in-memory size (kbytes)  64
-v: address space (kbytes)          unlimited
-x: file locks                      unlimited
-i: pending signals                 11795
-q: bytes in POSIX msg queues       819200
-e: max nice                        0
-r: max rt priority                 0
-N 15:                              unlimited

可以看到一般情况下的, 限制是 64k. 就是说, 一般的情况下, 设定 mlock 的内存不能超过 64k.

-l: locked-in-memory size (kbytes)  64

以下是 rlimit 的 man 手册里的说明.

  RLIMIT_MEMLOCK
              The maximum number of bytes of memory that may be locked into
              RAM.  In effect this limit is rounded down to the nearest
              multiple of the system page size.  This limit affects mlock(2)
              and mlockall(2) and the mmap(2) MAP_LOCKED operation.  Since
              Linux 2.6.9 it also affects the shmctl(2) SHM_LOCK operation,
              where it sets a maximum on the total bytes in shared memory
              segments (see shmget(2)) that may be locked by the real user
              ID of the calling process.  The shmctl(2) SHM_LOCK locks are
              accounted for separately from the per-process memory locks
              established by mlock(2), mlockall(2), and mmap(2) MAP_LOCKED;
              a process can lock bytes up to this limit in each of these two
              categories.  In Linux kernels before 2.6.9, this limit
              controlled the amount of memory that could be locked by a
              privileged process.  Since Linux 2.6.9, no limits are placed
              on the amount of memory that a privileged process may lock,
              and this limit instead governs the amount of memory that an
              unprivileged process may lock.

注意到 2.6.9 之后的系统中对于有特权的进程, 就不再限制了. 那么哪些进程有特权呢?

Limits and permissions
       In Linux 2.6.8 and earlier, a process must be privileged
       (CAP_IPC_LOCK) in order to lock memory and the RLIMIT_MEMLOCK soft
       resource limit defines a limit on how much memory the process may
       lock.

       Since Linux 2.6.9, no limits are placed on the amount of memory that
       a privileged process can lock and the RLIMIT_MEMLOCK soft resource
       limit instead defines a limit on how much memory an unprivileged
       process may lock.

2.6.9 之后, 对于有 CAP_IPC_LOCK 权限的进程 rlimit 就不再进行限制了. rlimit 中的限制,
只针对于非特权进程了.

root 当然是特权进程了, CAP_IPC_LOCK 怎么设置呢?

#sudo setcap cap_ipc_lock=eip a.out

cross compile python

cross compile python

python 的 cross 编译最大的不同在于, python 会先编译出一个小程序 Parser/pgen, 后续的编译过程中要使用这个程序.
所以我们要有一个运行于 HOST 的 Parser/pgen.

所以我们创建了两个目录 build-pc, build-mips.
分别打开两个终端处理这两个目录下的编译, 防止出现干扰.

build-pc

没有什么特别的处理, 就如正常的编译一下样 ../configure. 之后直接 make Parser/pgen. 我们只要这个.

build-mips

# export PATH=/path/to/mipsel-gcc
#####使用的是 openwrt 生成的 工具链, 要申明一下 STAGING_DIR.
export STAGING_DIR=/path/to/openwrt/staging_dir/

export TOOLS=mipsel-openwrt-linux
export CC=$TOOLS-gcc
export CXX=$TOOLS-g++
export AR=$TOOLS-ar
export RANLIB=$TOOLS-ranlib
export ac_cv_file__dev_ptmx=no
export ac_cv_file__dev_ptc=no
export ac_cv_have_long_long_format=yes
../configure --host=mipsel-linux --build=mipsel --disable-ipv6

这样就可以 make 了, 但是要注意要指定一下 Parser/pgen.

make PGEN=../build-pc/Parser/pgen

有的时候, 这个 make 的过程中可能会把 ../build-pc/Parser/pgen 使用 mips 下的 文件重新生成一个 mips 的 Parser/pgen.
只要重新回去到 build-pc 下 删除 Parser/pgen 重新 make Parser/pgen 回来就可以了.
当然, 如果你感觉这样比较麻烦可以把当前的 Makefile 里生成 pgen 的代码删除就可以了:

$(PGEN):        $(PGENOBJS)
                $(CC) $(OPT) $(LDFLAGS) $(PGENOBJS) $(LIBS) -o $(PGEN)

install

安装的过程中, 会做一些检查, 但是这些检查的过程使用的 python 是系统里的会出错, 这一点比较讨厌.
可以使用参数 -i 跳过这些错误;

make install -i

Modules

python 的主程序没有什么依赖, 但是其功能的扩展使用了很多模块, 官方的安装包里有就很多模块比如 zlib.
这些模块的编译, 使用的是 setup.py. 并且默认的执行这个 setup.py 的还是系统环境里的 python.

好吧, 这一点并不是什么大问题. 但是虽然 setup.py 有对于 cross comiple 进行支持, 但是支持的结果并不好.
可能要手动修改一下才可以.

setup.py 会对于库文件进行检查, 比如 zlib, 会先查找 libz.so 还有头文件. 一般性的安装, 自然是到系统里
去找这些东西, cross comiple 的时候, 这个查找的路径, 就不一定可以正确的得到. 可以手动在函数
detect_modules 里增加一下:

   def detect_modules(self):
        # Ensure that /usr/local is always used
        if not cross_compiling:
            add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
            add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
        if cross_compiling:
            self.add_gcc_paths()
        self.add_multiarch_paths()

        l = "/path/openwrt-ramips/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/lib/"
        i = "/path/openwrt-ramips/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/include"
        self.compiler.include_dirs.append(i)
        self.compiler.library_dirs.append(l)

buildroot

使用 buildroot 会比较简单, gcc 的环境变量之类的东西, 已经设置好了. 只要把 buildroot/output/host/usr/bin
加入到 PATH 就可以了.

buildroot 的所有的输出在 output 下面.

output/host 用于保存运行于本机上的程序如工具链. 而 uclic 是放到 output/host/usr/ 下面的.

images 下面的 rootfs.tar 是其打包的版本.
host/target 下面保存的是个 rootfs.tar 的没有打包的版本, busybox 就是这下面.

另: 修改了一些配置之后, 比如打开了 wchar, 要生效要先把 output/build 下面的 uclibc 删除了.

openwrt

openwrt 感觉是对于 buildroot 的包装, 包含了很多东西. 最后的结果是生成一个可以写入到设备上的镜像.
我只要一个工具链的, 其实没有必要打开那些功能. 使用 buildroot

undefined symbol

undefined symbol

今天遇到一个情况, 一个程序在动态加载了一个 A.so 库的时候出现了
undefined symbol 的情况.

对应的 symbol 在另一个动态库 B.so 中. 但是 ldd A.so 的时候, 并没有 B.so.
有些奇怪. 为什么会这样呢?

先不管这个问题, 想想怎么解决这个问题呢? 我们试着先动态加载了 B.so 再加载 A.so
可以在 proc 下的 maps 文件里看到两个库都成功加载了, 但是问题还是存在的.
想来动态加载 B.so 的时候, 我并没有指定要调用的 symbol. 所以程序的 symbols 表里还是
没有对应的 symbol. 在试着, 加载指定的 symbol 的时候, 遇到了一些问题.

后来的解决方法是, 我们写了一个 C.so, 编译的时候指定了 A.so 还 B.so. 程序直接加 C.so, 可以解决问题.

那么问题是为什么这个 A.so 有 undefined symbol 却没有依赖的库呢?

没有依赖的库, 原因来自于在生成库的时候, 没有使用 -l 指定. 我们知道如果生成的是一个可执行程序,
没有使用 -l 是一定会报错的. 但是 使用-shared 的时候会怎么样的呢? 我们一直都是以为
如果有 undefined symbols 那么 gcc 应该会试着去 -l 指定的库中找到, 并把 -l 指定的库的 soname
写入到的 so 文件中去, 也就是说要保证所有的 undefined 的 symbol 可以在 so 文件依赖的库中找到,
从而实现一个完美的符号链表. 我一直是这样认为的是. 但是事实却不是这样的.

--allow-shlib-undefined
--no-allow-shlib-undefined
    Allows or disallows undefined symbols in shared libraries.
    This switch is similar to --no-undefined except that it determines
    the behaviour when the undefined symbols are in a shared library
    rather than a regular object file.  It does not affect how
    undefined symbols in regular object files are handled.

    The default behaviour is to report errors for any undefined symbols
    referenced in shared libraries if the linker is being used
    to create an executable, but to allow them if the linker is being
    used to create a shared library.

    The reasons for allowing undefined symbol references in shared
    libraries specified at link time are that:

    ·   A shared library specified at link time may not be the same as
        the one that is available at load time, so the symbol might
        actually be resolvable at load time.

    ·   There are some operating systems, eg BeOS and HPPA, where undefined
        symbols in shared libraries are normal.

        The BeOS kernel for example patches shared libraries at load time to
        select whichever function is most appropriate for the
        current architecture.  This is used, for example, to dynamically
        select an appropriate memset function.

ld 会通过这个参数开启是不否允许在 shared library 中存在 undefined symbols.
这是因为 ld 认为, 链接的时候生成的 shared 文件与在加载的时候的并不一定是同一个, 这中间还
可以有很多的操作与变化. 另一个原因在于其它的一些平台上, 这种情况是正常的. BeOS 会
依赖于运行的环境不同, 加载不同的符号.

如下的这些 ld 的参数都是用于控制是不否允许在 shared 文件里有 undefined symbols

  • –no-undefined
  • -z defs
  • –allow-shlib-undefined
  • –no-allow-shlib-undefined

补: 7-22-1

今天, 再回看这个问题, 发现 A.so 还有一个对应的 A.a 文件. 那么是不是可以使用这个 A.a 来生成一个可以使用的
A.so 呢?

在使用静态库的时候, 有一个问题, 对于静态库的链接只会提取引用到的符号. 我们这里想的是把整个 A.a 都放到
生成的动态库中去. 如下:

gcc -shared -o libD.so  -Wl,--whole-archive libA.a  -Wl,--no-whole-archive -l curl

使用 –whole-archive 把整个 libA.a 都加入到动态库中去生成一个 D.so 文件, 并补充增加一个 curl 库.

另一个问题, 其实就是为什么会要存在这种情况. 今天在和同事讨论的时候, 他说了一个情况就是 lua.
lua 的库就是这样编译. 对比 python, python 的解释器在运行的时候, 是引用的 libpython2.7.so 的,
那么我们一般编译 python 的库的时候, 也会使用-l 增加库的依赖库文件 libpython2.7.so.
但是为什么 lua 就要让库文件中保留这种 undefined symbols 的情况呢? 因为一般 lua 的解释器运行的时候
不会再引用一个 lua 的动态库文件, lua 一般是嵌入到整个程序中的. 这样的情况, 如果库文件增加一个 liblua.so 的
依赖库, 在运行起来的时候就会引用 liblua.so. 但是实际上 liblua.so 里的那些符号已经在解释器里静态存在了.
这样的结果就是可能会引发冲突.

补: 7-22-2

又研究出来一个方法, 我们其实可以考虑直接修改 A.so 给它增加一个依赖库的头.

patchelf --add-needed libcurl.so.4 A.so

 patchelf 是一个可以对于 ELF 文件进行修正的工具.

arp

arp

arp_ignore

定义了在响应收到的 ARP 请求时候的不同的工作
用于处理多个本地 IP 的情况

  • 0. (默认): 只要请求的 IP 已经配置在本机的任何一个网卡上, 都可以进行 ARP 响应.
  • 1. 只有当被请求的 IP 是被配置在请求进来的网卡上的时候才进行回复.
  • 2. 只有当被请求的 IP 是被配置在请求进来的网卡上, 并且请求的 IP 是处于进来的这个
    网卡的子段内.

  • 3. 不对被配置成了 host 域的 IP 进行 ARP 回复, 只对于 global/link 域 IP 进行回复.
  • 4-7. reserved
  • 8. 不对于请求任何本地 IP 的 ARP 进行响应.

arp_announce

定义不同的限制级别, 用于指定在网卡上发出 ARP 请求的时候指定在 IP 包中的本地的源 IP.

  • 0: 使用任何配置在任一网卡上的 IP
  • 1: 避开使用网卡不是目的 IP 同一个子网的 IP.

 http://blog.chinaunix.net/uid-13423994-id-5146098.html 

screen in linux

screen in linux

显示器是 linux 下可能比较麻烦的硬件, 因为显示器太重要的.

亮度

xorg-xbacklight 包中的 xbacklight 命令可以设置亮度

$xbacklight -set 50  # sets brightness to 50% of maximum
$xbacklight -inc 10  # increase brightness by 10%
$xbacklight -dec 10  # decrease brightness by 10%

但是可能也不能用, 因为这个东西其实比较麻烦, 可以看 这里 

xorg 结构

实际上现在的 xorg 是可以对于显示设置驱动什么的进行自动处理的, xorg 的配置文件
已经不处长写的了, 但是还是有一些场景下是有用的.

先理解一下 xorg 下对于显示设置的处理.

首先是显卡也就是 Device. 显卡下面可以接显示器也就是 Monitor. 而一个 screen 可以由多个 Monitor 组合而
成.

比如:

/etc/X11/xorg.conf.d/10-monitor.conf
=======================================================
Section "Monitor"
    Identifier             "Monitor0"
EndSection

Section "Device"
    Identifier             "Device0"
    Driver                 "vesa" #Choose the driver used for this monitor
EndSection

Section "Screen"
    Identifier             "Screen0"  #Collapse Monitor and Device section to Screen section
    Device                 "Device0"
    Monitor                "Monitor0"
    DefaultDepth           16 #Choose the depth (16||24)
    SubSection             "Display"
        Depth              16
        Modes              "1024x768" #Choose the resolution
    EndSubSection
EndSection

但是这样写, 可能是有问题的, 如果我有多个 Device 怎么办呢?

Device 可以通过 ‘BusID’ 定位, 其值来自于 lspci:

# lspci |grep VGA
0b:00.0 VGA compatible controller: ASPEED Technology, Inc. ASPEED Graphics Family (rev 21)

所以写成:

Section "Device"
    Identifier             "Device0"
    Driver                 "vesa"
    BusID                  "PCI:0b:00:0"
EndSection

而 Monitor 是在 Device 下面的, 通过如下的方式来指定:

Section "Monitor"
    Identifier             "Monitor0"
    Option                 "PreferredMode" "1208x1024"
EndSection

Section "Device"
    Identifier             "Device0"
    Driver                 "vesa"
    BusID                  "PCI:0b:00:0"
    Option          "Monitor-DP1"  "Monitor0"
EndSection

使用 “Monitor-*” 的方式来指定某个接口对应的显示器. 这些接口的名字通过 xrandr 这个命令可以得到.
对于 Monitor 使用的 mode 使用选项 PreferredMode 来指定.

通过以上的方式我们可以把 Screen, Device, Monitor 三者关联起来.

xrandr

xrandr 命令是一个很方便的命令用于控制显示的输出. xrandr 可以列出来, 当前的所有的显示器.
使用 –output 命令, 可以实现各种输出, 并控制各种细节.

参考

 https://wiki.archlinux.org/index.php/xrandr 

EFI

EFI

EFI

这次想在 mac 下安装 linux. 又搞了一些东西. linux 想用于工作中还是比较困难的.
但是又感受了很多技术. 简单纪录一下.

在 mac 下安装 linux 第一个问题就是引导的问题, 磁盘的划分还在其次.
mac 下使用的 EFI 引导的. 其实我之前就知道这个东西, 但是由于比较麻烦一直
也没有去好好研究一下. 这一次算是被逼的了.

引用一下 EFI 的介绍吧:

可扩展固件接口(EFI)最初是由英特尔开发,于2002年12月英特尔释出其订定的版本——1.1版
之后英特尔不再有其他关于EFI的规范格式发布。
有关EFI的规范,英特尔已于2005年将此规范格式交由UEFI论坛来推广与发展,
后来并更改名称为Unified EFI(UEFI)。UEFI论坛于2007年1月7日释出并发放2.1版本的规格,
其中较1.1版本增加与改进了加密编码(cryptography)、网络认证(network authentication)
与用户接口架构(User Interface Architecture)。

说点实际的, 一般 EFI 会有一个 100M 的 fat 的分区(EFI System Partition)
用于保存一些数据. 这些东西可以启动运行, 一定
程序上这些是一个底层的小操作系统. 这些东西运行起来之后, 再引导系统的启动.

默认情况下, EFI 就是支持多个启动选项的存在的. 可以在 linux 通过 efibootmgr 这样的程序来修改.
这个修改应该是修改的引导区的信息. EFI 分区里, EFI 目录下可能有多个目录用于保存不同的引导.
MAC 下的 bless 应该也是一个相似的工具.

rEFInd

这是一个 UEFI 的启动管理器. 是 rEFIt 的一个分支. 有针对对于 MAC 进行一些优化. 但是平台无关的.
这个东西为什么要存在呢? 我们之前说过, EFI 是可以修改引导的选项的. 但是这个修改可能并不方便,
efibootmgr 要在 linux 下运行. 我们要的是一个可以在 EFI 进入一个引导项的时候进行选项的工具.
rEFInd 就是这样一个东西. 它作为默认的引导项, 开机启动之后, 让你选择进入其它的哪一个. 比如 APPLE, linux,
windows. 这就是它的存在的意义.

当要安装 mac pro 下的双系统的时候, 一般是要安装这个东西的. 但是我这里出现了一些问题, 没有办法安装.
因为 SIP (System Integrity Protection) 的限制, 没有办法安装 rEFInd. 同时我的 recovery 分区好像也没有了.
我也不知道是怎么没有的. 但是没有了. 试了好几个方法都没有办法.

但是我突然想到 rEFInd 其实是与系统没有关系的吧. 那我可以在使用 linux 启动 U 盘安装系统的时候, 再安装 rEFInd
的啊. 嗯, 没错是可以的.

grub

安装了系统, 这个时候还是要为系统设置 grub 的, 不然没有办法启动的, 现在又到了 EFI 这个问题上.
现在如果不考虑双系统的问题, 如果只有一个系统, 使用 EFI 进行安装 grub.

grub-install --target=x86_64-efi --efi-directory=$esp --bootloader-id=grub --recheck

  • –bootloader-id: 引导项的名字, 就比如 APPLE, WINDOWS. 之类的自己起,
  • –efi-directory: ESP 分区的挂载点.

这个过程中比较重要的一点就是把 grub 的 EFI 程序 grubx64.efi 放到 $esp/EFI/grub 下面.
比如, 我把 ESP 挂载到了 /boot/efi. 当然 grub 的引导器和配置文件还是在 /boot/grub 下面.

之后可能你还要使用 grub-mkconfig 生成一下配置文件.

# grub-mkconfig -o $esp/EFI/grub/grub.cfg

这个过程中没有使用 device_path 这个东西, 因为 UEFI 不会使用启动块.

UEFI 变量

不用使用 device_path, 那 UEFI 是如何知道使用 $esp/EFI 下面的哪一个的呢, 这下面可能有很多啊.
哪个是默认的, 顺序是如何的呢? 这些是写入到主板的硬件里的. 所以使用 EFI, 系统也要支持. 因为有可能要
配置主板硬件. 对于 ESP 下的文件的加载与执行是由硬件的固件在实现的. 对于默认启动项的选择也是
保存到固件里的.

而 linux 使用的是就是 /sys/firmware/efi/efivars 来处理硬件上定义的变量. efibootmgr 也就是通过这个来
处理 EFI 的一些特性. 这个修改是和 ESP 没有关系的.

参考

 https://wiki.archlinux.org/index.php/GRUB 

matplotlib 手册

matplotlib 手册

输出格式化:

def size_fmt(s, pos = 0):
    for f in 'BKMG':
        if s < 1024:
            break
        s = s / 1024
    else:
        f = 'T'

    return '%.1f%s' % (s, f)

  • 设置 X 轴的显示: plt.gca().xaxis.set_major_formatter(ticker.FuncFormatter(size_fmt))
  • 设置 X 轴的步长: plt.gca().xaxis.set_major_locator(ticker.MultipleLocator(1024 * 64))

  • 双 Y 轴
    fig, left_axis=plt.subplots()
    right_axis = left_axis.twinx()
    
  • 设置轴 label:
    right_axis.set_ylabel('label')
    

  • 设置 title: fig.suptitle(‘title’)
  • 设置图片大小: fig.set_size_inches(40, 10.5)

TCP Tail Loss Probe(TLP)

TCP Tail Loss Probe(TLP)

目的

Early Retransmit机制解决了dupack较少,无法触发快速重传的问题。
但是如果发生了尾丢包,由于尾包后面没有更多的数据包,也就没有办法触发任何的dupack。

具体的场景:

a. Drop tail at the end of transactions.
b. Mid-transaction loss of an entire window of data or ACKs.
c. Insufficient number of duplicate ACKs to trigger fast recovery at sender.
    -- 基本被Eearly Retransmit机制解决了
d. An unexpectedly long round-trip time(RTT), such that the ACKs arrive after
   the RTO timer expires.
    -- F-RTO机制通过检测spurious retransmission,能够尽量的undo RTO造成的影响

算法

FlightSize: the amount of data that has been sent but not yet *cumulatively* acknowledged.
    -- 这个与内核中的packet_in_flight计数器要区分开,这里要强调累计确认

PTO: Probe timeout is a timer event indicating that an ACK is overdue.

Open state: the sender has so far received in-sequence ACKs with no SACK
            blocks, and no other indications (such as retransmission timeout) that
            a loss may have occurred.
    -- 换成中文:TCP的正常状态,哈哈

Consecutive PTOs: back-to-back PTOs all scheduled for the same tail packets in a flight.

1. 在Open state发送新数据后,设置一个PTO计时器
    if (FlightSize > 1)     PTO = max(2*SRTT, 10ms)
    if (FlightSize == 0)    PTO = max(2*SRTT, 1.5*SRTT + WCDelAckT)
    if (RTO is earlier)     PTO = min(RTO, PTO)
        其中WCDelAckT表示worst case delayed ACK timer,默认值是200ms

2. 启用PTO timer的条件:
    a. connection is in open state
        -- 如果不在open state,说明有其他信息帮助判断丢包。而无需启用TLP
    b. connection is either cwnd limited or application limited
        -- TLP必须满足tail这个条件
    c. number of consecutive PTOs <= 2
        -- TLP 不要尝试太多次
    d. connection is SACK enable
        -- TLP依赖于SACK选项来提供是否触发FR的决策

3. 当PTO超时后:
    if (能发新数据)         发送一个新数据包,FlightSize += SMSS, cwnd不改变
    if (没有新数据可发)     发送一个序号最大的数据包
    增加loss probe的计数器
    如果步骤2中的条件满足,则再次设置PTO;否则设置RTO超时计时器'now+RTO'

4. 在处理收到的ACK包时
    取消PTO timer
    如果步骤2条件满足,则设置PTO timer

算法步骤:

1. 初始化
    当TCP流进入ESTABLISHED状态,或者RTO超时后,或者进入Fast Recovery后,对上面两个变量进行初始化
    TLPRtxOut = 0;
    TLPHighRxt = 0;
2. 当发送一个TLP探测包后
    if (TLPRtxOut == 0)
        TLPHighRxt = SND.NXT
    TLPRtxOut++;
3. 在收到一个ACK包后
    当满足所有以下条件时,认为这个ACK是由TLP包触发的,而且这个TLP是完全多余的
    a. TLPRtxOut > 0                        /* 首先当然得发送过TLP包 */
    b. SEG.ACK == TLPHighRxt                /* ACK包确认了SND.NXT序号 */
    c. ACK不包含序号超过TLPHighRxt的SACK段  /* 意味着这个ACK就是TLP包序号触发的,而不是TLPHighRxt序号之后某个包触发的 */
    d. ACK没有移动SND.UNA                   /* 说明这是一个纯粹的dupack,并且ACK号是SND.NXT证明这个ACK包对应的TLP是完全多余的 */
    e. ACK包不含数据                        /* 就是要证明这个ACK是一个完全多余的TLP包触发的 */
    f. ACK包不是一个窗口更新包              /* 理由同e */
    以上条件都满足时,TLPRtxOut--

    如果ACK.SEQ > TLPHighRxt,则说明TLP阶段应该结束了。最后来判断是否发现了丢包
    isLoss = (TLPRtxOut > 0) &&     /* 不为0说明有一个TLP包不是多余的,也就是说有丢包发生 */
             (ACK不携带任何TLP重传相关的DSACK信息)      /* 如果包含DSACK信息,也能证明TLP是多余的。所以要排除这种情况 */
    TLPRtxOut = 0
    if (isLoss)
        EnterRecovery()
4. TLP探测包的发送条件,除了满足TLP原始算法中步骤2中的条件外,还要满足
    (TLPRxtOut == 0) || (SND.NXT == TLPHighRxt)
    -- The sender maintains this invariant so that there is at most
       one TLP retransmission "espisode" happening at a time.

源码分析

调用逻辑:

1. 正常数据的发送流程中,增加调度安装PTO超时计时器的逻辑
   即TLP算法逻辑的第一步。

__tcp_push_pending_frame()
    ==> tcp_write_xmit() with push_one=0
        ==> tcp_schedule_loss_probe()   /* 尝试安装PTO超时计时器的安装 */

2. 处理ack时,增加调度安装PTO超时计时器和结束TLP状态的逻辑
   即TLP算法的最后一步

tcp_ack()
    ==> if (tp->tlp_high_seq) tcp_process_tlp_ack();    /* 判断是否需要结束TLP状态 */
    ==> tcp_schedule_loss_probe()

/* 返回false代表未设置timer, 返回true代表设置了PTO timer */
bool tcp_schedule_loss_probe(struct sock *sk)
{
    ...
    u32 rtt = tp->srtt >> 3;    /* tp->srtt存的实际是RFC中SRTT的8倍 */
    ...
    /* TLP is only scheduled when next timer event is RTO. */
    if (icsk->icsk_pending != ICSK_TIME_RETRANS)
        return false;

    /* Schedule a loss probe in 2*RTT for SACK capable connections
     * in Open state, that are either limited by cwnd or application.
     */
    if (sysctl_tcp_early_retrans < 3 ||     /* 没开TLP */
        !rtt ||                             /* 没有RTTsample可用,没法设置PTO */
        !tp->packets_out ||                 /* 网络中没有未被确认的数据包,没必要设置PTO */
        !tcp_is_sack(tp) ||                 /* 不支持SACK选项 */
        !inet_csk(sk)->icsk_ca_state != TCP_CA_Open)    /* 只有在open状态才设置PTO */
        return false;

    /* Probe timeout is at lease 1.5*rtt + TCP_DELACK_MAX to account
     * for delayed ack when there's one outstanding packet.
     */
    /* 这段代码完全符合TLP算法逻辑,不解释了 */
    timeout = rtt << 1;
    if (tp->packets_out == 1)
        timeout = max_t(u32, timeout, (rtt + (rtt >> 1) + TCP_DELACK_MAX));
    timeout = max_t(u32, timeout, msecs_to_jiffies(10));

    /* If RTO is shorter, just schedule TLP in its place. */
    /* PTO = min(PTO, RTO) */
    tlp_time_stamp = tcp_time_stamp + timeout;
    rto_time_stamp = (u32)inet_csk(sk)->icsk_timeout;
    if ((s32)(tlp_time_stamp - rto_time_stamp) > 0) {
        s32 delta = rto_time_stamp - tcp_time_stamp;
        if (delta > 0)
            timeout = delta;
    }

    inet_csk_reset_xmit_timer(sk, ICSK_TIME_LOSS_PROBE, timeout, TCP_RTO_MAX);
    return true;
}

// 在tcp_write_timer_handler中会根据event的类型,来做相应的处理
// 如果是PTO超时,则调用tcp_send_loss_probe(sk)来发送TLP探测包
/* When probe timeout (PTO) fires, send a new segment if one exists, else
 * retransmit the last segment.
 */
void tcp_send_loss_probe(struct sock *sk)
{
    ...
    /*
     * 如果有新数据可以发送,则发新数据作为探测包
     * TLP用了一个push_one=2的trick来区分是正常的发送包,还是loss probe包
     */
    if (tcp_send_head(sk) != NULL) {
        err = tcp_write_xmit(sk, mss, TCP_NAGLE_OFF, 2, GFP_ATOMIC);
        goto rearm_timer;
    }

    /* At most one outstanding TLP retransmission */
    if (tp->tlp_high_seq)
        goto rearm_timer;

    /* Retransmit last segment */
    skb = tcp_write_queue_tail(sk);
    if (WARN_ON(!skb))
        goto rearm_timer;

    /* 省略一些判断tcp fragment的代码 */

    /* Probe with zero data doesn't trigger fast recovery */
    if (skb->len > 0)
        err = __tcp_retransmit_skb(sk, skb);

    /* Record snd_nxt for loss detection */
    if (!likely(!err))
        tp->tlp_high_seq = tp->snd_nxt;

rearm_timer:
    /* 重新安装RTO超时计时器 */
    inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, inet_csk(sk)->icsk_rto, TCP_RTO_MAX);

    if (likely(!err))   /* 增加计数器的值,可以在/proc/net/snmp中看到,netstat -s也可以 */
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPLOSSPROBES);

    return;
}

参考

原文在这里, 本文一定程度上是对于此文的学习记录.
 http://perthcharles.github.io/2015/10/31/wiki-network-tcp-tlp/