最近有需要,看了一下。我觉得还是讲一下,但是不讲太细了。以下代码是以4.4为基准的,不是的会特别指出。

/proc/net/dev的读取问题

procfs的代码在fs/proc/proc_net.c,但是基本没干点啥事。真正的核心代码在net/core/net-procfs.c:dev_seq_show,我这里是105行。这个函数会打印个头,然后调用上面的dev_seq_printf_stats。dev_seq_printf_stats则是取设备最新信息,并打印。

这里要注意的一点是,整个过程并没有锁,只是在fs/seq_file.c:seq_read上面上了一把锁。这把锁的目地更像是排斥两个进程同时读取同一个文件,而不是保证计数器正确。因此,实际上每个设备的性能计数都是单独读取的,而且读取时计数器还在持续递增。

结论是什么?对于/proc/net/dev里的多个设备,做bonding加和是不可靠的。你不能假定设备1和2做了bonding,那么设备1的计数加上2的计数精确等于bonding的计数——虽然实际上就是这么算的。

bonding的数据获取

我们再看dev_get_stats,这个函数里面实际上是用了ops->ndo_get_stats来获得设备的数据。对于bonding来说,这个文件在drivers/net/bonding/bond_main.c:bond_get_stats。从下面的实现中,你能看到,实际上bonding网卡的计数就是用各个设备的加和。然而计算没有那么直接,用的是每个设备的当前值减去原来保存值,差值加到bonding的保存值上。换个说法就是,bonding的margin等于各个设备的margin。

这个特性是由这个补丁引入的:make global bonding stats more reliable,时间是2014年9月29日,版本是3.17.0-rc6。主要是解决bonding中去掉一块网卡的时候,计数器会掉下来的问题。在这个之前,还有两个补丁。

  1. Enable 64-bit net device statistics on 32-bit architectures的引入时间是2010年6月8日,版本是2.6.35-rc1。这个版本首次引入了64位计数器(因此,RH6里面默认配置的2.6.32内核应当是32位计数器),但是写出了bug。
  2. fix 64 bit counters on 32 bit arches的引入时间是2010年7月8日,版本同样是2.6.35-rc1。主要是解决补丁1在32位系统上非锁定读写64位计数器造成数据跨越总线宽度,导致多个CPU竞争读写时高低位被分别写入产生不一致的问题。

由这两个补丁,我们可以简单总结出bonding的问题历史。

  1. 在2010年,2.6.35之前,内核计数器只有32位,分分钟会出问题。实话说我完全不敢相信,有当时用过/proc/net/dev的朋友来讲讲么?
  2. 2010年的时候,有短暂的bug,只影响2.6.35。这是一个开发内核,我们可以忽略。
  3. 2014年,3.17.0之前。去掉一块网卡的时候,bonding计数器会掉下来。

然而,其实问题并没有结束。如果你仔细看代码的话,会发现,bond_get_stats根本没加锁。如果两个context同时调用,有可能发生这么一个过程。

  1. context1读取数据并计算,计算完成后,写入bond->bond_stats数据前被switch out。
  2. context2开始运算,完成计算并写入bond->bond_stats。很明显,由于context2的启动时间比1晚,context2的数据结果比1大。
  3. context1被switch in,写入bond->bond_stats。
  4. 在随后的时间里,计数器的增长比context2和context1的差值大。

满足上述条件的话,bonding计数器就有可能倒涨。当然,里面还有其他竟态情况,可能导致各种问题。由于这是3.17.0内核引入的,因此只会发生在这个版本之后。

当然,有朋友可能会说,/proc/net/dev里有锁啊。问题是,dev_get_stats可不是只有/proc/net/dev会用,rtnetlink也有可能哦。

这个问题是这个补丁修复的:fix bond_get_stats,时间是2016年3月18日,版本是4.5.0-rc7。在补丁的说明里,说到了这个问题。

因此,我们延伸一下故障列表:

  1. 在2010年,2.6.35之前,内核计数器只有32位,分分钟会出问题。实话说我完全不敢相信,有当时用过/proc/net/dev的朋友来讲讲么?
  2. 2010年的时候,有短暂的bug,只影响2.6.35。这是一个开发内核,我们可以忽略。
  3. 2014年,3.17.0之前。去掉一块网卡的时候,bonding计数器会掉下来。
  4. 2014-2016年,3.17.0到4.5.0之间。多个CPU同时读写时,会发生竞争出错。
  5. 2016年,4.5.0版本以上。未知。

另外注意。并不是说3.17.0之前没有竞争问题,而是由于没有写回状态,所以竞争问题并不产生明显影响。