LWN:删除file_operations里的成员!

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

The file_operations structure gets smaller

By Jonathan Corbet
May 3, 2024
ChatGLM translation
https://lwn.net/Articles/972081/

内核开发者总是被鼓励来把改动采用很多小 patch 的形式来发送出来,这样可以让 reviewer 更加好处理一些。因此,当一位长期开发者兼维护者提供了一组修改了 859 个文件的 437 个 patch 来等待 review 的时候,人们显然感到无比惊讶。具体来说 Jens Axboe 的这组patch 是用来清理 Linux kernel 里很早期就具备的一个核心的抽象结构的。所有开发设备驱动的开发者得好好关注一下了。

struct file_operations 的起源

在 Linux 内核的早期阶段,并没有一个虚拟文件系统的抽象层。例如,查看一下 0.01 版本的实现 的 read(),其中包含对每种可能的文件描述符类型的显式检查。这种方法足够让初始内核启动,但不久之后,Linus Torvalds 意识到它的可扩展性不佳。随着开发人员试图添加更多的设备类型,并实现多个文件系统类型,对抽象层的需求变得更加迫切。

1992 年 3 月发布的 Linux 0.95 版本带来了许多变化,其中也包括转换为 GPL 许可证。它还添加了成为内核虚拟文件系统层的第一部分代码。该层的核心部分是 首次出现的 file_operations 结构,其完整定义如下:

struct file_operations {
  int (*lseek) (struct inode *, struct file *, off_t, int);
  int (*read) (struct inode *, struct file *, char *, int);
  int (*write) (struct inode *, struct file *, char *, int);
};

该结构包含实现特定系统调用所需的函数指针,针对可以用文件描述符表示的任何内容。内核不再持续扩展 if-then-else 来确定正在操作的文件类型,而是可以直接调用适当的 file_operations 成员。正如可以预料的那样,最基本的操作 — 读取、写入和寻址 — 首先就出现在这里。在内核的早期版本中,文件描述符的用途并不多。

struct file_operations 结构随后不断增长。1.0 版本的结构 包含了十个成员,实现了像 readdir()、ioctl() 和 mmap() 这样的系统调用。2.0 版本的 struct file_operations 有 13 个成员,而 2.2 版本则添加了两个。在这一历史过程中, read() 和 write() 成员始终是从文件描述符来进行读取和写入的实现方式,尽管它们的原型略有变化。

情况变得更加复杂

2001 年初发布的 2.4 版本包含了 一个具有以下新成员的 struct file_operations:

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

用户空间开发人员经常需要执行分散/聚集(scatter/gather) I/O — 即涉及多个内存段的操作,需要在单个操作中传输这些段。作为回应,内核增加了对 readv() 和 writev() 的支持,但为了正确支持这些系统调用,内核需要将它们传递给底层实现。这些新成员接受了一个包含每个段的(在用户空间中)地址和大小的 iovec 结构 数组。对于未实现新功能的设备驱动程序或文件系统,内核会通过一系列 read() 或 write() 调用来模拟它们。

随后的工作中添加了更多的成员到 struct file_operations ,包括 read() 和 write() 的其他变体。用于实现内核有点不受欢迎的 异步 I/O 机制 的 aio_read() 和 aio_write() 进入了 2.5.33 开发版本。实现 splice() 系统调用的 splice_read() 和 splice_write() 是在 2.6.17 中 添加。与内核代码的惯例一致, file_operations 成员的删除是罕见的,但在所有用户都转换为使用 aio_read() 和 aio_write() 后, readv() 和 writev() 在 2.6.19 中 被删除。

struct file_operations 的 3.16 版本,已经增长到了 27 个成员,其中包括了如下这些代表内核新的 I/O 方式的新增成员:

ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);

越来越多的 I/O 操作是由内核发起的,而不仅仅是来自用户空间。它们通常涉及多个段,并且需要异步执行。所涉及的数据缓冲区可以用多种方式引用。用于描述这些更复杂的 I/O 操作的 iov_iter 结构在当时 看起来是这样的:

struct iov_iter {
int type;
size_t iov_offset;
size_t count;
union {
    const struct iovec *iov;
    const struct bio_vec *bvec;
};
unsigned long nr_segs;
};

这个结构的关键特点与 type 字段相关。如果它是 ITER_IOVEC ,那么 iov 这个 union 成员包含一个使用用户空间地址的段的数组。如果是 ITER_KVEC ,那么地址位于内核空间。如果 type 是 ITER_BVEC ,那么 bvec 字段指向一个 bio 结构数组(用于描述块 I/O 请求)。这样定义的 I/O API 可以从许多上下文中调用,并且无论操作是从用户空间还是内核中发起的,都可以正常工作。

kiocb 结构 被内核用于协调异步 I/O 操作。驱动程序不需要实现异步 I/O(尽管如果不这样做,它们可能不会表现得很好),但如果它们实现了,就需要这个结构中的信息。使用 struct kiocb 反映了一个事实,即新方法的目标之一是取代 aio_read() 和 aio_write() ,它们已经为 4.0 版本 删除。

到处都有 struct iov_iter

随着时间的推移, struct iov_iter 已经发展得相当复杂;请参阅 6.8 版本 以获取详细信息。内核还积累了一套辅助(helper)程序,大部分时间都可以使代码摆脱对这些复杂情况的处理。同时,6.8 版本中的 struct file_operations 已经达到了 32 个可调用成员。但是,在所有这些变化之后, read() 和 write() 基本保持不变,尽管它们只处理了在已成为复杂世界中最简单的 I/O 操作。

Axboe 决定,也许,这两个成员已经到达了它们有用生命周期的尽头:

10 年前,我们向 struct file_operations 添加了 ->read_iter() 和 ->write_iter()。它们非常好用,因为它们传递了一个 iov_iter 而不是用户缓冲区 + 长度,它们还接受了一个 struct kiocb 而不仅仅是一个文件。从那时起,对于任何读取或写入,我们有了两条路径 - 一个是传统的路径,无法执行每个 I/O 提示(例如“此读取应为非阻塞”),它们严格只能与文件上的 O_NONBLOCK 一起使用,并且一个较新的路径,支持旧路径支持的所有内容和更多。

由于 read_iter() 和 write_iter() 可以做到 read() 和 write() 能做的一切,因此删除较旧的成员是有道理的。唯一的问题是,当然,内核中只有很多代码实现了 read() 和 write() ;其中许多代码位于多年来可能没有见过重大开发(甚至可能无人使用)的驱动程序中。其中一些肯定正在使用,但是破坏它们无疑会增加网络上(已经很高的)抱怨反馈。

许多使用较旧接口的模块在一定程度上可以转换为使用 read_iter() 和 write_iter() ,也许在这个过程中能达到功能可用。但是有很多这样的模块,尝试理解每一个模块以进行这样的转换是一个疯狂的想法,而收益甚微。因此,Axboe 首先通过实现 一组辅助程序 来模拟新功能,其中涉及一系列对 read() 或 write() 的调用;这最大程度地减少了对任何特定模块的更改量,同时最大程度地增加了得到正确结果的机会。例如,可以查看 此补丁 以了解最简单转换的示例。

系列中的 最终补丁 删除了 read() 和 write() ,这令人惊讶地没有仪式感,尽管它们已经存在了 32 年。

这个系列还没有收到很多评论;也许许多开发人员仍在等待把整个系列下载到他们的收件箱中。Al Viro 指出,一些转换可能需要更加谨慎地进行。但是迄今为止,没有人反对总体概念。

要接受这样的改动,就需要先分成更易管理的片段 — 这一点 Axboe 在一开始就承认了。但是,这组更改确实简化了内核,并且删除了相当数量的旧代码,所以很有可能迟早都会采用某种形式来实现。到那时,很可能会有许多 out-of-tree 模块需要更新,然后才能在较新的内核上构建。好消息是,开发人员现在可以进行这些更改,并在预先取得进展。

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

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

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

861072c674d37699ac0c497c0cb9ab2a.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值