本文是2017年4月3日Netconf会议报告。主要的议题是:移除ndo_select_queue()函数,对于refcount_t类型引入开关,TC重定向导致内核陷入循环等。
ndo_select_queue()函数是大家首先讨论的,它被用来决定往网络接口发送数据包的时间和方式。主要的争议是把它放在驱动里面是否合适,Alexander Duyck说Intel在考虑用ndo_select_queue()来说收/发包队列的matching,不过目前也没真正在用。
无线方面倒是重度使用该函数,不过是用来做traffic分流,比如让语音数据避开一个best-effort服务的拥挤队列。不过该函数也不是做这个事情的唯一选择,在无线协议栈里面基于fq_codel的流控机制也很有效。
接下来的问题就是怎么把它移除了,很快我们就不会看到一个通用的ndo_select_queue()接口了。以后这个某个具体协议栈或者某个具体驱动操心的事情了。
refcount_t类型是kernel内部用来防止对象引用计数向上或者向下溢出的数据类型。大家的观点是这样的数据类型用于调试是可行的,但是不能默认enable,大家非常确信这样的数据类型对网络性能会有非常严重的影响,不过目前没有数据来说明。
第二天关于KASan内存问题检测的讨论中,Eric也表达了类似refcount_t类型的观点。这种检测方法对一大批问题无能为力,还可能导致协议栈突破10%内存使用限制,因此有人建议检测方法的开启关闭应该用可选择参数控制。用于debug特定问题是OK的,但是一直开启就影响到网络性能。
关于可选择参数大家通常都会默认关闭,以至于这种检测方法实际不会发挥作用因为不会测试到选择参数控制的路径。Eric解释了一通Google的测试,最后也承认某些路径确实无法完全顾及到。
最后总结一下的话,就是谨慎的小伙伴会更喜欢这种提高内核可靠性的方法,不过搞网络的朋友因为性能方面的考虑不能完全接受。
Berg提出有时候用户需要高性能获取关心数据的方法,特别是无线应用里面。虽然已经有相关的机制但是开销实在太大。Miller认为eBPF干这个很合适。
Hannes Frederic Sowa在会上提了个无伤大雅的问题,即内核如何处理VLAN 0?理论上,VLAN 0表示没有VLAN。但是内核现在处理VLAN 0的方式取决于VLAN模块是否加载,以及VLAN 0接口是否创建。因此,有时数据包中的VLAN 0标记被直接丢弃,有时又没有,出现语义不一致。追溯源头,VLAN 0语义的破坏是由一个patch意外导致,Sowa计划将恢复VLAN 0的原始语义。
内核TC (traffic-control)子系统的维护者Jamal Hadi Salim指出,利用tc REDIRECT特性能够使内核陷入无限循环。只要将REDIRECT的目的接口配置成源接口,就能很快重现这个问题。当然啦,只要简单的丢弃源接口和目的接口相同的数据包就可避免这种情况。更严重的是,当一个数据包从eth0发送到eth1, eth1又迅速的将这个包重定向到eth0。很明显,只有root用户才有权限去刻意制造这种问题场景,管理员的错,那又怪得了谁呢?
然而,当考虑到物理机上部署了容器的情况,事情就变得不那么简单了。一个非授信用户可能在容器内部拥有root权限,并刻意构造这种问题场景,将会对运行在该服务器上的其它容器构成DoS攻击。在特定条件下,还会导致内核死锁。
Salim 发现,之所以会出现这个问题,是因为skb struct中用于标记数据包的两个bits被砍掉了。这两个bit是一种简单的TTL机制,在每次循环中+1,当达到允许的最大限制时数据包被丢弃。正是这小小的TTL避免了死循环的出现。
当前的大趋势是要精简skb struct,至于是将这两个bit重新召回,还是继续忍受DoS威胁,期待后续发展。
在一些场景下,用户需要从内核中导出大量数据。Salim提供了一个真实的tc读取6M entries的案例。但是,当前的基于netlink机制的API一次只能读取20 entries,效率超级低。Salim写了个patch,提高一次读数据的最大值到 8 x NLMSG_GOOD_SIZE.使性能提高一个数量级。显然,这只是一个临时方案,社区还在寻找一个更通用完美的方案:从内核读取大量统计信息的机制,允许在读取过程中,数据仍然在不断变化。
本文是2017年4月4日Netconf会议报告的第二部分。主要的议题是VRF上套接字的绑定,eBPF程序的标识,IPv4/IPv6协议间的区别,数据中心硬件的变迁等等。
David Ahern带来的第一个问题是关于如何将套接字绑定到一个指定的接口上。当前有如下四种方法可以完成这项工作:
这里存在的问题是有太多方式可以完成绑定套接字到指定接口这一工作,而对这些方法的修改势必会破坏ABI兼容。同时这些方法之间也存在着冲突。比如一个用户使用了某种方法设置了一个标记,但是这一方式会破坏其他用户已经完成的工作,同时这一问题内核并不会向用户态进行汇报。参加会议的开发者同意需要添加一个冲突通知机制来通知用户这一问题。
接下来Ahern提出了另一个问题,如何将套接字绑定到指定的VRF上。比如一个UDP组播套接字如何绑定到VRF上。
Tom Herbert说此前曾经讨论过通过扩展bind(2)系统调用的方式来解决这一问题。即通过bind(2)来将一个UDP套接字绑定到一组离散的IP地址或者子网上。他认为这是一个很宽泛的问题,即如何提供更通用的接口。
Ahern解释了当前将套接字绑定到一个VRF从设备上的方法。同时他提出了一个问题:内核接收到报文后如何选择套接字。当前针对UDP套接字的做法是进行打分。但这一方式并不通用。
David Miller指出不同的功能域有不同的方法来完成上述工作。比如VRF层和netns层。此前Miller不希望网络代码中充斥着各种netns关键字,因为在获得灵活性的同时会带来很大的性能开销。他认为不要添加新的key而是要复用已有的架构。Herbert则认为应该明确说明这一原因。最后大家的一致意见是使用IP_UNICAST_IF标记。当前该标记只针对UDP和RAW套接字,随后将会被扩展到TCP。
Ahern随后讨论的问题是关于BPF的。他举了个例子,一个经典的BPF程序:对ARP报文进行检查。如果从过滤器读取程序代码,用户会得到一系列难以解析的二进制数据。尽管内核可以输出成类C风格,但是依然难以解析。
Ahern希望对于一个经典BPF程序是否可以恢复到原始的普通文本输出。比如将0x806替换成常量。即便对于eBPF程序的输出也可以进行很多的改进。Alexei Starovoitov是BPF的维护者,他认为合理的做法是给eBPF程序返回一些映射关系的信息。更加复杂的数据则留待后续解决。
当前最重要的是debug工具的开发工作,即完成一个逆编译器将指令重构为可以阅读的代码。随后是如何将数据返回到用户态。Ahern解释现在使用bpf(2)系统调用仅仅是将数据拷贝到不同的文件描述符。Starovoitov指出当前这样的行为是比较合理的。
一个类似的问题是如何区分XDP程序(XDP也是通过BPF来编写的)。Miller解释了用户希望有一种方法来获得系统中安装了哪些XDP程序。当前仅仅只有一个SHA-1标识符来标识这些XDP程序,但是仅仅是内部使用的,并没有暴露给用户态。Starovoitov提到当前有一个布尔值来标识程序是否加载。
在上述问题的基础上,如何获取已经部署的BPF程序的代码也成为了讨论的问题。但是目前并没有特别的结论。
主要针对内核中 IPv4/v6 的处理差异提出讨论,看是否有某些层面合并的可能,比如:
主要是是否可以在驱动加载之前,为设备传递某些选项,比如:有些设备的 Firmware 加载之前就需要预先根据某些配置来完成早期的初始化。 提到了 devlink , 但是实际上 PCI BDF 未必能和设备一一对应,包括是否让驱动在某种状态等待,或者一些特殊的脚本加载过程,总之 hack 的程度比较高。
译:这里面可能还要涉及到 BIOS 和早期 option ROM 加载的问题,总之,目前并没有统一的方法,我认为这个应该 HW design 和驱动编写的时候应该充分考虑到这一点,给自己留好 fallback 的路线,这种事情还是别指望内核了。
主要是接口向后兼容性问题经常被打破的问题,这个需要有全局观,D.M. 应该会更加严格的控制这些 patch 的合并,包括是否实际验证。总之这件事是被强调了,后面会严格控制。
译:这里并非技术本身,流程,还是流程。
主要是同一个网卡被多个主机共享,FB 已经有在自己的数据中心应用了。
会上提出了很多 concern, 主要包括安全性和稳定性,所谓访问范围扩大化,当然给了黑客更多的机会,另外是否会引起故障扩大化。
另外,Mellanox 和 Broadcom 也都有类似的产品,还可以研究下 Yosemite platform
译:这些 host 网络可以通过 PCI-e 的通道互联之后,某些网络延迟可以做到更小,一些特殊的计算模型会更快,带来的肯定就是稳定性和安全的问题,还是设计平衡。还有很多很多讨论了,大家有兴趣去 Netconf 现场听听吧。
2017LSFMM(Linux Storage Filesystem, and Memory-Management Summit)第二个全体会议日,Fred Knight向与会人员讲解了过去一年中,存储标准方面的进展。过去一年中,传输(如:光纤通信、以太网)和SCSI协议都没有太大变化,NVMe标准则有一些更新。
在传输标准方面,32Gb/128Gb的光纤通信已经开始支持,64Gb/256Gb的支持也在开发中。T级别的光纤通信也在规划中,Knight说:如果带宽可以达到T数量级,那就比较有趣了。同样以太网也增加了新的带宽支持(从2.5Gb到Tb级),同样也有新的市场斩获(比如:汽车)。NVMe有了新的命令集,同时支持多种连接方式可选(如:PCEe、基于以太的RDMA、InfiniBand)。
对于SCSI, 一个简化的捆绑状态命令(TEST BIND)被添加。WRITE ATOMIC命令也被添加进来,这条命令允许写操作要么所有数据全部写入,要么什么都不更改。另外,WRITE SCATTER命令添加了了一个32字节的变量。Knight说:2016LSFMM中讨论的atomic与scatter的组合写操作没有后续更新。一些存储厂商反对该操作,他们认为他们的理解与Linux所需要的之间存在一些差距,所以不会对该特性有更多关注。
针对流ID(stream IDs)的WRITE STREAM命令已经被添加到标准中。另外增加了BACKGROUND CONTROL命令,该命令允许对存储设备中运行的后台任务(比如垃圾回收)进行一些控制操作。后台任务会对I/O的时间产生影响,因此用户希望有能力通过改变参数对后台任务进行控制。
一些预定义的特性集合(例如:2010 Basic,2016 Basic,Maintenance,Provisioning)已经被确立,存储设备广告中可以利用这些名词,同时主机也可以对这些特性的有效性进行检查。Knight说:新的特性集合也会陆续被添加,这将会是个持续的过程。Ted Ts'o问:是否设备可以对外声称支持一些特性集合,但实现中支持了更多不属于该特性集合中的指令?Knight回答:设备厂商可以这么做。
Knight说:过去一年中,SCSI最大的一件事情是驱动器缩量(drive depopulation)。驱动器损坏与我们想象中的不一样,它通常都是只有一部分坏了。比如说:如果你有一个8TB的驱动器,该驱动器头部的存储空间无法工作,你可以简单的将该驱动器前面的部分关闭,这样你就会拥有一个6TB大小的可以正常工作的驱动器。
不管SCSI还是ATA,“损坏重用(repurposing)”都已经支持了,驱动器在缩减容量后可以继续工作。对于已经存储在驱动器中的数据,要么全部丢失,要么被映射到其他的逻辑块地址(LBA)中。GET PHYSICAL ELEMENT STATUS命令可以被用于确定哪些部分不能工作,新驱动器容量是多少。REMOVE ELEMENT AND TRUNCATE命令被用于关闭不能工作的存储空间。驱动器可以被重新格式化。如果其他存储空间再次出现问题,上述流程可以继续重复进行。
对于读整个驱动器并尝试进行数据修复已经在标准中支持了很长时间。有一些命令可以提供接下来的N个逻辑块地址(LBA)是否已经损坏的信息,这些块可以在接下来的处理中被跳过。这些特性使得在驱动器部分损坏的情况下,允许主机尽可能的修复更多数据。
对于驱动器缩量(depopulation),去年有一些“数据保留(data preserving)”模式的讨论。这是很复杂的,因此需要更多些时间。Knight说:很多人都在问这个特性是否真的需要,所以这个特性也有可能被砍掉。
过去的一年NVMe工作组是最忙的。面向光纤的官方规格书已经发布。“清洁(sanitize)”命令也已经正式支持,该命令可以在将某个驱动器用于其他用途前,清除该驱动器。其他的机制,比如:加密擦除(crypto erase)和块擦除(block erase),则会清除所有SSD,所以那些设备不实现这些机制。
一个设备的crash-dump机制称之为“telemetry”,已经被添加进规格书中。流ID(Stream IDs)也同样被加入。一些对persistent reservation的支持也已经添加,但所添加的与SCSI对应特性并不兼容,这是所不希望的。一个兼容版本正在制定中。
虚拟/仿真控制器是添加的另外一个特性。这个特性允许虚拟机认为他们可以拥有自己的专用NVMe控制器。每个客户虚拟机可以获得一个专用的队列,这些队列是由hypervisor分配的。这就允许过个客户虚拟机共享同一个物理NVMe适配器。
NVMe工作组每周的两小时会议都有大约80人参加,并且T10(SCSI)、T11(Fibre Channel)和T13(ATA)委员会每个月都有三天的会议。有一些顾虑说这个组有做不完的事情。他说:NVMe组在逐渐加速进度,而其他组则比几年前更稳定一些。
Mathew Wilcox问Knight如何评价在Linux中,存储设备汇报的所有不同的错误都被归结为EIO(I/O error)。Knight笑着表示问题在于存储设备汇报的各种类型的错误码和介质错误(译者注:设备本来就不应汇报那么多不同种类的错误)。而Martin Petersen则指出,POSIX标准需要将这些错误映射为EIO,因为应用无法理解其他错误的细节。
Darrick J. Wong是就职于Oracle的XFS开发者。他的LSFMM session通常会讨论有大量围绕XFS的讨论,本次也不例外,抛开各种杂七杂八的小话题不说,本次讨论的主线围绕XFS reverse mapping和online fsck进行。
首先让我们看看反向索引,XFS通过名为GETFSMAP的ioctl接口来实现反向索引查询,具体来说就是给定一个extent,找出它的属主到底为何(数据、元数据抑或空闲没有分配)
而反向索引和online fsck这两者之间其实有很强的依赖关系。一般实现一个online fsck时,内核仍然mount着文件系统,随时随地可能修改它,为保证元数据的一致性,online fsck的主体逻辑通常只能由内核本身来执行,在执行的过程中与其他正常的文件操作进行同步。而不是像平时offline fsck那样纯粹由一个用户态工具来完成。Darrick Wong表示在onilne fsck的过程中,他不希望依靠盘上已有的元数据,因为理论上来说它们全都有风险,不完全可信。Darrick Wong的计划是依靠反向索引来逐步把元数据重新建立起来。同时他还可以通过GETFSMAP + Direct IO把所有的数据块读一遍看看会不会有错误。这里唯一的漏洞在于当依赖反向索引工作的online fsck试图重建反射索引自身时会出死锁,即”自指悖论“。Darrick Wong的解决办法是在这里对online fsck和offline fsck取一折衷:冻结所有的inodes操作,这使得online fsck颇像一个offline fsck,然后在重建的过程中慢慢逐一解冻它们。
Darrick Wong遇到的另一个问题是如何对付已经打开的fd。如果允许这些fd继续操作,他们很可能会感知到fsck带来的不一致。他能想到的一个办法是摘掉这些文件对应的page cache页,把它们的inode_operations替换掉,让它们基本上什么都不做直接返回错误。这样处理过的inode除了被关闭进orphan list就没别的用处了。
Chris Mason表示在Facebook没有这类online fsck的需求,我相信绝大多数互联网公司或者云计算公司都不会有,一般来说高可靠性应该优先通过replication来保证,单个结点auto-healthing的难度显然远大于replication,并且引入了如此之高的复杂度之后可靠性到底是提升了还是下降了都尚且存疑。况且就算这条路可行,在online fsck执行的过程中很可能文件系统的各种性能都会下降,带来较差的用户体验。在大型系统的设计中,我们更倾向于明确地搞清楚一个子部件的状态,它要么被标记成可用,就充分地完全地可用,要么被标记成不可用,就不会有任务、请求、流量、容器或者虚拟机被调度上来,完全彻底地下线。一个半死不活正在修复自身的组件没有人会喜欢。Darrick Wong的看法是这一类特性主要是针对那些宁可牺牲性能,也绝不能允许一个存储服务,准确地说是一个存储服务的结点不可用的系统准备的。
从我的角度来看,如果存在这样一个通过期待本地文件系统永远不出错来实现高可用的系统,那么它可以被称为“根本不具备高可用性”的系统,或者“错误设计的高可用系统”。
在2017 Linux Storage, Filesystem, and Memory Management大会上,Jeff Layton说目前Linux对writeback的错误处理有点混乱。他与其他参会者讨论,并提出了一种方案用于更好地处理writeback error。Writeback是指先写入缓存中,然后再落到文件系统上。
最初是他在查看Ceph文件系统时出现的ENOSPC(out of space)错误。他发现PG_error(这个页flag用于标记writeback出错)会覆盖其他error 状态,从而造成EIO (IO error)。这启发了他去了解其他文件系统是如何处理这种错误。最后他发现目前其他文件系统对此并没有一致的解决方案。Dmitry Monakhov认为这是因为writeback的时机太晚了,而导致ENOSPC。但是Layton说这个错误也会以其他方式触发。
Layton说,如果在writeback时出错,这个错误应该在用户态调用close()或fsync()时返回告诉用户。这些错误应该用struct address_space中的AS_EIO和AS_ENOSPC跟踪。同时也被page level的PG_error跟踪。
他也说,一个不小心的sync操作可能清除error flag,然而这些错误并没有返回到用户态。PG_error也用来跟踪和报告读错误,因此读写混合可能会在flag报告前丢失。另外还有一个问题,当发生wirteback error时,page做了什么。目前,page被留在了page cache中,并标记了clean和up-to-date,因此用户态并不知道发生了错误。
因此,Layton问到,遇到这种情况怎么办。James Bottomley也问到,文件系统想以什么样的细粒度来track error。Layton讲到address_space是个合适的level。Jan Kara指出PG_error本打算用于读错误。但Layton说到一些writeback的机制也用它。
Layton建议,清理tree来去掉PG_error对于writeback errors。这样的话就会透过tree来看writeback上的error会不会传播到用户空间,或者它们是否可能被错误地清除。Ted Ts'o 讲到,可能有必要在不发生任何错误下writeback,因为这些错误不会返回到用户空间上。
Bottomley讲到,他更不愿标记page clean,如果这些page还没有写入磁盘上的话。这些错误信息有必要被跟踪,以扇区为单位。因此block layer可以告诉文件系统bio错误发生在哪里。Chris Mason建议那些做“redoing the radix tree”的人可能想提供一种方法来存储发生在文件上的错误。这样的话,错误就可以被报告出来,一旦报告,即可清除掉。
Layton提出一个想法。可以在address_space结构上加上writeback error counter和writeback error code fields,同时在struct file上加上writeback error counter。当有writeback error发生时,address space的writeback error counter就会自增,并记录下error code。当fsync()或close(),这些错误就会被报告出来 ,不过只在file结构里的wirteback error counter小于address space里的couter。这样的话,address_space里的counter将会被拷贝到file结构中。
Monakhov问到,为何需要counter,Layton解释到,这样可以更高效处理multiple overlapping error,不管是在writeback出错在file打开时还是在最后fasync()。Ts'o 同意了这个说法。那些需要更多信息的应用应该使用O_DIRECT。而对于大多数应用程序,知道一个错误发生在文件哪里是必要的;所有的应用程序需要更好的粒度已经在使用O_DIRECT。
Layton's的方法将会出现一些false positive,但是不会出现false negatives。避免false positives可能是个比较大的目标,但提出的这个方法更简单些,副作用也少。Layton说到这种方法将会提高用户空间的表现。
Ts’o 指出现在处理ENOMEM(out of memory)上有一些方法。如果writeback内部的error返回的话,Layton描述的一些问题也会发生。因此,一些文件系统并不会返回ENOMEM错误,为了避免,他们不得不使用锁。Ts’o 已经有一些patch来推迟writeback,让pages处于脏状态,而不是获得锁,来阻止oom kills。
但是Layton认为,首要的应该是想办法来处理wirteback error。文件系统现在避免这个问题,是因为他们现在处理不好。更好的处理temporaay error方法应该加进来。另一件需要做的事是当进行wirteback时,应该拥有更多的killable或可中断的调用。
另外,Laytony讲到,当发生writeback error,page应该做哪些。目前,是将其置为clean。如果发生一个hard error(will never be retries),难道这个页就无效了吗?Ts’o讲到这些页不能一直为脏状态,因为如果有大量writeback error,系统将会OOM。
Steve French认为error handling应该处理在higher level,但Layton讲,这就像一个不清楚怎么使用的bad api。他现在在尝试解决,也希望开发者关注他的patches(https://lwn.net/Articles/718648/).
整个Kernel对于多队列块设备的支持一直存在一个短板,那就是面向多队列设备的IO调度器。而4.12开发周期,同时引入了两个新的支持多队列的IO调度器。
对于多队设备的支持,缺乏IO调度器看上去是致命的,但事实却是,最初对于是否需要一个调度器并不明确。高端固态设备并不存在旋转延迟(Rotational delay,指普通磁盘中,从磁盘上读取数据的过程中,需要磁盘旋转所产生的延迟)问题,因此固态设备对操作的顺序(Ordering)并不敏感。但IO调度即使对于最快速的设备也是有价值的。调度器可以合并相邻的块访问,减少操作的次数,并且可以对不同操作进行优先级处理。所以虽然对引入一个针对多队列设备的IO调度器期待了很久,但一直没有相应的实现。
第一个支持多队列IO调度的是BFQ(Budget Fair Queing)调度。这个调度器对低速设备有更好的IO响应,适合用于手机等设备中。
BFQ是在CFQ基础上进行了改善,但将其合并仍然是一个漫长的过程。最主要的障碍就是:他是个传统IO调度器,并非面向多队列调度的。块子系统的开发者们有一个长期的目标,就是将所有驱动更改为多队列模型。因此,合并一个对多队列IO调度提升有限的调度器,并不合适。
过去几个月,BFQ的开发者Paolo Valent将其代码移植到了多队列接口上。已知的问题都已经被解决。按照计划,BFQ将会出现在4.12中。
BFQ是一个比较复杂的调度器,是为了提供好的交互响应而设计,尤其对于低速设备。但对于IO操作较快,吞吐量作为最主要的问题的情况下,这种复杂度变的没有意义。固态设备需要一个更为简单的调度器。
Kyber IO调度器正是这个定位。它只有1000行的代码,比BFQ简单很多。IO请求进入Kyber中后,将被分为以读为主的同步队列和已写为主的异步队列。读写在应用中的行为特性使得读的优先级高于写,同时写操作也不能一直处于饥饿状态。
Kyber调度器会将所有请求送到分发队列中,然后测量每个请求的完成时间,根据完成时间调整队列的限制,以达到配置所需要的延时。
Kyber也已经被4.12的合并窗口接受。根据计划,4.12内核将同时发布两个新的针对多队列设备的IO调度器。用户关注响应或者应用了低速设备,可以选择BFQ。如果用户对吞吐更敏感,则可以选择Kyber。