LWN: epoll_pwait2(), close_range(), and encoded I/O

关注了就能看到更多这么棒的文章哦~

epoll_pwait2(), close_range(), and encoded I/O

By Jonathan Corbet
November 20, 2020
DeepL assisted translation
https://lwn.net/Articles/837816/

多年来,内核针对文件和文件系统的访问而提供的各种系统调用和 API 已经越来越全面。但这并不意味着没有改进的空间了。开发社区正在考虑对内核的文件系统相关 API 增加一些小改动,请继续阅读我们这些工作的调查和总结。

Higher-resolution epoll_wait() timeouts

内核的 "epoll "子系统,给进程提供了一种同时等待大量已被 open 的文件描述符上的 event 的方法。使用的时候会先用 epoll_create()创建一个 epoll 文件描述符,用 epoll_ctl()添加感兴趣的文件描述符进来,最后用 epoll_wait()或 epoll_pwait() 来等待事件。在等待时,调用者可以指定超时时间,是毫秒的整数倍。

epoll 机制是在 2.5 系列 kernel 开发周期中加入的,并在 2003 年底的 2.6 版本中开始正式发布使用。近 20 年前,当这项工作正在进行时,毫秒级别的超时时间已经足够解决问题了。无论如何,内核无法可靠地使用更短的超时时间。但在 2020 年,一毫秒可能是一个非常长的时间了。有一些用户会希望能用到更短的超时时间。因此,现在似乎是时候对 epoll API 进行另一次更新了。

Willem de Bruijn 适时地出现了,提出一组 patch set,为 epoll_wait()添加了纳秒级别的超时支持,但它走了一条有点迂回的路。由于 epoll_wait()没有 "flags" 参数,所以没有办法直接增加个 flag 来请求更高分辨率的超时时间。所以 patch set 反而给 epoll_create()添加了一个新的标志 (EPOLL_NSTIMEO) (其实是 epoll_create1(),这是在 2.6.27 中添加的,因为 epoll_create()也缺少 "flags" 参数)。如果一个 epoll 文件描述符是在设置了这个标志的情况下 create 出来的,那么 epoll_wait()的超时值将以纳秒为单位,而不是毫秒。

然而,Andrew Morton 对这个 API 很有抱怨。在他看来,让一个系统调用设置 flag 来改变对另一个系统调用的参数的解析方式,这种做法是 "不太好" 的;他建议增加一个新的系统调用。经过一番反复讨论,最后还是增加了新的系统调用。最新版本的 patch set 中增加了 epoll_pwait2()。

int epoll_pwait2(int fd, struct epoll_event *events, int maxevents,
                 const struct timespec *timeout, const sigset_t *sigset);

在这个版本中,超时值是以一个 timespec 结构传递进来的,其中包括纳秒字段。

关于这个系统调用的实现已经有一些讨论,但关于 API 的评论并不多,所以也许这项工作会以当前这种做法得到合入。不过,编者忍不住还是想说一句,这个系统调用里,也缺少 "flags" 参数,所以最终可能还会再出现一个 epoll_pwait3() 吗?

close_range() –eventually

close_range()系统调用是在 5.9 版本中加入的,它可以高效地关闭一系列的文件描述符。

int close_range(int first, int last, unsigned int flags);

调用时将关闭所有介于 first 和 last 之间(包括这两者)的文件描述符。flag 的取值目前只定义了一个有效值 CLOSE_RANGE_UNSHARE,用来让指定范围内的文件描述符不再共享给其他任何进程(但是并不会关闭它们)。

Giuseppe Scrivano 新提了一组 patch set,增加了另一个 flag:CLOSE_RANGE_CLOEXEC。它会在这些文件描述符上都设置 "close on exec()" flag。同样,close_range()在这种情况下并不会真正关闭文件,它只是做个标记,这样今后调用者进程执行 exec() 时会关闭这些文件描述符。理论上说,这比执行一个循环来用 fcntl()对每个文件描述符分别设置标志要快。

这个功能看起来很有用,而且也没有人真正对这个 API 提出反对意见(这组 patch set 的早期版本实现上是有一些问题)。不过,鉴于 close_range()承担了太多不涉及真正关闭文件的功能,说明这个系统调用似乎很明显地起错名字了。它从 10 月 11 日的 5.9 版本开始才出现,所以目前还没有相应的 C-library wrapper 发布出去。所以,如果愿意的话,大家其实有时间为这个系统调用想一个更好的名字。

Encoded I/O

有一些文件系统可以在写入文件之前进行压缩或加密。通常情况下,当从这些文件中读取数据时,这些数据会被恢复到原来的形式,所以用户可能完全不知道这种转换发生过。然而,如果有人希望能够绕过文件系统代码中的处理流程,而直接处理这些 "encoded" 数据呢?Omar Sandoval 提出了一组 patch set。

这项工作的主要动机似乎是用于备份场景,具体来说,是用 Btrfs send and receive operation 来对文件系统镜像进行完整备份或者增量备份。这种机制的使用方法,就是在另一个设备上创建一个相同的 Btrfs subvolume 副本。如果 subvolume 使用了压缩,那么发送前会对数据进行解压,然后必须在接收端对数据进行重新压缩,然后写入。如果涉及的数据很多,这样的操作就有些浪费,完全可以只传输压缩后的数据,效率会更高。

打上这组 patch set 后,就可以直接读取压缩或加密(或者两者都有)过的数据,并直接写入,不需要中间的处理。第一步是用新的 O_ALLOW_ENCODED flag 打开 subvolume。在这种模式下打开 subvolume 需要 CAP_SYS_ADMIN 这个 capability,试想一下,如果攻击者将损坏过的压缩数据写入文件,会发生什么情况。Dave Chinner 很早就认为,损坏的(corrupted)数据应该只是被当作坏(bad)数据,这样这个操作就可以不需要任何特权,但这种观点并没有胜出。

然后,可以使用 preadv()和 pwritev()系统调用来读取或写入编码过数据。必须使用新的 RWF_ENCODED flag 来表示正在传输编码数据。这些系统调用在被正常调用时,需要一个指向要传输的缓冲区的 iovec 结构的指针数组,但当正在进行编码过的数据的 I/O 时,第一个指针会指向一个新的 encoded_iov 结构。

struct encoded_iov {
  __aligned_u64 len;
  __aligned_u64 unencoded_len;
  __aligned_u64 unencoded_offset;
  __u32 compression;
  __u32 encryption;
};

len 字段必须用来指定这个结构的长度,它的存在是为了防止将来增加新的字段。unencoded_len 和 unencoded_offset 字段指明了此操作会影响文件的哪一部分,compression 和 encryption 字段包含了每种文件系统相关的值,描述了应该使哪种压缩和加密方式。iovec 数组中的所有其他指针都指向要传输数据的实际 iovec 结构。

这个 patch set 包含了对从 Btrfs 文件系统读写压缩数据的支持。还有一个后续的 patch set,在发送和接收操作中利用这个功能。提供的性能测试可以看出,传输数据所需的带宽显著减少了,CPU 使用时间减少,在某些情况下,还减少了真实消耗的时间。

截至本文撰写时,这组 patch set 已经经历了六次修订。第一个版本发布于 2019 年 9 月。各种实现细节问题已经得到解决,大家似乎正在达成一致,应该很快就可以合并了。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值