Rust 和 C 文件系统 API

随着 Rust-for-Linux 项目的推进,内核正在逐步积累抽象层,使 Rust 代码能够与现有的 C 代码对接。不过,正如 Wedson Almeida Filho 在 12 月份发布的关于文件系统抽象层的讨论所示,在设计这些抽象层的两种方法之间存在一些矛盾。大多数内核 C 语言程序员所青睐的方法看起来会胜出,但随着 Rust 在内核中的使用越来越多,这种讨论很可能会再次出现。

如果 Rust 开发人员想使用发布的抽象实现一个文件系统,他们的工作就是把一个看起来像求职信中这个例子的实现组合在一起:

 impl FileSystem for MyFS {
        fn super_params(sb: &NewSuperBlock<Self>) -> Result<SuperParams<Self::Data>>;
        fn init_root(sb: &SuperBlock<Self>) -> Result<ARef<INode<Self>>>;
        fn read_dir(inode: &INode<Self>, emitter: &mut DirEmitter) -> Result;
        fn lookup(parent: &INode<Self>, name: &[u8]) -> Result<ARef<INode<Self>>>;
        fn read_folio(inode: &INode<Self>, folio: LockedFolio<'_>) -> Result;
    }

这里定义的函数执行内核可能要求文件系统实现的任务:例如,读取目录内容的 read_dir(),或查找目录中文件名的 lookup()。所有这些操作都被定义为名为 FileSystem 的单一 Trait 的一部分。

元素周期表

这种组织方式与 C 代码的 API 定义方式不同,在 C 代码中,与文件和文件系统相关的操作分散在多种对象类型中。文件系统作为一个整体是由 struct super_block 定义的,它在 struct super_operations 中有一组相关的操作。但文件系统还实现了许多其他对象类型和相关操作,包括 inode(inodeinode_operations)、目录项(dentrydentry_operations)、文件(filefile_operations)和地址空间(address_spaceaddress_space_operations)。

作为对象模型如何在 C 端工作的一个例子,请考虑 lookup() 是一个 inode 操作,iterate_shared() (用于实现 Rust Trait 中定义的 read_dir()函数)是一个文件操作,而 read_folio() 是一个地址空间操作。

马修-威尔库克斯(Matthew Wilcox)对拟议的抽象提出了几个问题,首先是一些操作的inode参数。在内核的 C 代码中,这些函数使用的是 struct inode 指针,它很快就会被转换成文件系统专用的结构指针。这里几乎不存在类型安全问题;函数无法知道它实际上传递了一个指向正确类型 inode 的指针。在 Rust 中,似乎可以做得更好。

Almeida 回答说,这个接口确实做得更好。inode 参数的类型是 &INode<Self>,它将该参数的实际类型与文件系统类型联系在一起;向这些函数传递错误类型的 inode 是不可能不出现编译错误的。

威尔科克斯的另一个问题却很难回答。在 C 代码中,用于读取目录的文件操作是

int (*iterate_shared) (struct file *, struct dir_context *);

Rust 代码中的等价操作(上面的 read_dir())将 inode 引用作为参数,而不是struct file指针。Wilcox 指出,虽然 “toy filesystems ”只需在 inode 中存储信息即可,但其他文件系统需要文件结构中的信息。因此,不在接口中保留该结构似乎有点奇怪。Almeida 回答说,迄今为止,Rust 中已经实现的文件系统并不需要 struct file 中的任何信息;他补充说:”向 `read_dir` 传递`file`将要求我们引入一个没有人使用的不必要抽象,而我们已经被告知不要这样做。但他说,如果有必要,可以改变接口。

威尔库克斯的回答相当强硬:

那我们就不应该合并这些内容,甚至在至少有一个非玩具文件系统实现之前,都不应该再次将其送审。要么坚持我们已经定义的面向对象(即独立的 aops、iops、fops……,参数基本相似),要么建议修改我们在 C 语言中的面向对象。

阿尔梅达对此并不满意,他问道 “[你]是说 Rust 不能拥有与 C 的具有相同性能特征的不同 API,除非我们也修复 C 的 apis?” Wilcox 回答说,内核对象模型的存在是有原因的,Rust 方面不应在没有充分理由的情况下改变该模型。Al Viro 补充说,现有的对象和操作集需要被视为 “外部给定的”;他说,如果有充分的理由,这些对象和操作集是可以改变的,但这里没有这样的理由。

肯特-奥弗斯特里特(Kent Overstreet)则认为,Rust 抽象是一种设计更简洁界面的方法,这种界面不应需要与 C API 相匹配。由于需要同时改变所有现有的文件系统,清理后者是 “巨大的麻烦”,而在 Rust 中创建更好的东西则相对容易。

因此,在我看来,在 Rust 一侧做更简洁的版本反而更容易,一旦我们知道了它的样子,也许我们就会更新 C 语言版本以与之匹配–或者我们点燃一切,继续用 Rust 重写一切。

与此同时,阿尔梅达抱怨说,在没有任何东西使用的情况下,将文件结构传入 read_dir(),这正是 Rust 开发人员一直被建议要避免的事情。长期以来,Rust 开发人员一直在努力解决合并抽象的问题,以便在使用这些抽象的同时又无法合并用户。Wilcox 回答说,这些建议被误解了;Rust 开发人员被要求不要合并没有用户的抽象,不要改变所合并抽象的接口。Greg Kroah-Hartman 表示同意,他说这些抽象应该适用于所有文件系统,而不仅仅是那些现在已经实现的文件系统。Dave Chinner 说,这个问题正是他一直建议 Rust 开发者重新实现 ext2 的原因,因为该文件系统虽然相对简单,但使用了大部分核心文件系统 API。

最终,阿尔梅达妥协了,他说他将制作一个新版本的抽象,其中包含独立的文件、inode 和地址空间 Trait;read_dir() 将更新为接收 File<T> 引用。Wilcox 也认为这种方法是正确的。

因此,这场特殊的讨论似乎已经告一段落。不过,用 Rust 实现内核功能肯定会提供无数的机会来创建新的接口,这些接口要比内核 C 代码中那些已经发展了几十年的接口更简洁、更安全。有时,这些 API 会让人误解 C 代码演变的原因;有时,它们确实会更好。但无论如何,与 C API 有明显差异的 Rust API 都会增加维护和未来开发的难度,因此在 Rust 端创建与 C 端不同的 API 的想法将继续受到强烈抵制。

2023 维护者峰会上也讨论过一个答案,那就是演化 C 代码,使其与正在为 Rust 开发的更好接口相匹配。这个想法有一定道理,但同时也要求 Rust 开发人员在 C 语言中完成大量工作,而这恰恰是他们想要摆脱的。更改核心内核 API、更新这些 API 的所有用户,以及获得对更改的认可,这些都不是一件容易的事。这样的政策无疑会阻碍 Rust 方面开发更好的接口;其结果是可维护性更高,但这需要付出实际的代价。

奥弗斯特里特在上文提到了可能会发生的情况: “点燃一切,继续用 Rust 重写一切”。如果大家使用的 API 都在 Rust 代码中,那么 API 分歧就不存在问题。你的编辑的预测能力严重受限,但有几件事似乎很有可能发生:在某些时候会有人提议用 Rust 实现来替换一些核心代码,而这样做的阻力会非常大。即使在这次讨论中,戴维-豪威尔斯(David Howells)也明确表示,他不希望看到 Rust 靠近核心内核。

不过,这是后话,Rust 必须先在内核边缘证明自己。不过,一旦骆驼的鼻子(或螃蟹的鼻子)进了帐篷,其他部分似乎也会跟进。敬请期待,这将会很有趣。

本文文字及图片出自 Rust and C filesystem APIs

你也许感兴趣的:

共有 48 条讨论

  1. 不久前,我重读了约翰-雷格尔(John Regehr)的《友好 C 语言的问题》[0],结尾处的一段评论让我想到了 Rust in Linux 的努力。他推测,“一个有影响力的团体(如 Android 团队)可以创建一种友好的 C 语言方言”,这反过来又可以推动 C 语言的采用,并就更好的 C 语言应该是什么样子达成共识。当然,我们知道,自 2015 年撰写那篇文章以来,这种情况并没有发生,但已经发生的是,Android 团队用 Rust 重写了 Binder。因此,我认为该论点的主旨是正确的:有关各方正在就编写安全 C 语言所面临的挑战达成共识。

    0: https://blog.regehr.org/archives/1287

    1. 我认为 Rust 是对 C 语言的一种改进,但我也认为,没有与 C 语言相似但更安全的东西是很不幸的。C 语言非常容易理解。(即使 “理想化机器模型 ”随着新标准和优化的出现而变得更加复杂)。我可能用错了术语,但我指的是未定义的行为)。

      再提醒我一次,为什么我们不能使用 Pascal?(除了它老土、看起来滑稽,而且我年轻时对它嗤之以鼻)。

      我想知道 “带有 x 的 C ”会是什么样子,其中 x 是指什么:

      – 真正的可组合类型

      – 检查数组

      – 也许还有指针检查?

      但是没有:

      – 继承

      – 对象

      而且与普通 C 语言非常兼容,不知道 100% 语言兼容是否是个好主意。保持与 C 的兼容性是 C++ 变得如此复杂的原因之一。

      但保持链接器和头文件的兼容性将是天赐良机。

      也许现实中我想要的是 Zig,我意识到它是现有的最接近的语言,尽管它有元编程,语法也与 C 有点不同。

      我真的应该学学它,看看自己是否会喜欢。目前我的首选语言是 C++(如果我有时间)、C#(如果我没有时间)和 Python(如果我只是想探索一下思路)。

      1. > C 语言非常容易掌握。

        编写优秀、安全的 C 语言太难了,以至于根本没人能大规模完成,莱纳斯不行,西奥不行,甚至连 djb 也不行。

        为什么你觉得 “学会写糟糕的代码并不难 ”很有价值?

        缓存、NUMA、投机执行、mmap、大量能力各异的 CPU 内核和异步代码让 C 语言更像是 “20 世纪 70 年代 PDP 工作原理 ”的近似版本(抽象层是关键安全问题的无尽噩梦),我想这是有点用处,但不能作为我们的世界基础。有了数组边界检查和长度前缀字符串的 C 语言,仍然会有一个糟糕的类型系统,没有元编程,甚至没有迭代协议(或添加一个好协议的能力)。

        > 再提醒我一次,为什么我们不能使用 Pascal?

        我猜是因为 Unix-alikes(以及一段时间的 Windows,虽然我猜它早期使用的 C 语言较少而 C++ 较多)吞噬了整个世界,而使用操作系统所使用的语言(几乎全部)会产生巨大的网络效应。

        1. 如果你写的是商业代码,而不是超级优化的内核,那么写出好的、安全的 C 语言并不难。但这需要一种纪律,我是在使用其他语言时才学会的。

          – 不要使用 libc 字符串函数。

          – 使用大量的结构体。

          – 为这些结构体编写大量的操作函数,这样就不会得到不一致的数据。

          – 以函数式风格编写代码,不要随意丢弃指针。

          当你遵循这些自我规定时,这并不难。但是,你会发现自己手动编写了大量代码,而更高级的语言会直接帮你完成这些工作。然后你就会开始质疑自己当初为什么要用 C 语言编写代码。(我说的 “你 ”是指 “我”)。

          但我发现,用 C 语言编写非常乏味,它会让我认真思考要解决的问题,从而迫使我做最基本的事情来解决问题。在使用集成开发环境的 C# 中,很容易就能 “以防万一 ”地加入一些抽象概念和选项什么的,这在 C 语言中是非常痛苦的。

          1. > 当你遵循这样的自定规则时,这并不难。

            这是一个非常有趣的回答,谢谢。

            1. 很高兴至少能给您带来乐趣。

              编辑:

              我认为 C 就像国际象棋一样简单。规则很容易理解…

          2. 听起来是个有趣的建议。为什么要大量使用结构体?

            1. 在看了太多的字符数组,以及用各种 #define SOMEINDEX 6 索引的字段之后,我得出了这样的结论。在这样的代码中,数据结构的知识被 “分散 ”在大量带有 if-s 和其他条件的代码中,如果要改变数据结构的外观,就必须更新整个代码库。

              我不想指责别人,所以我不会指名道姓,但我对某个用于嵌入式 Flash 的文件系统很感兴趣,因为它非常好,在功能、大小和速度之间取得了很好的平衡,并有大量的回归测试等。从很多方面来说,它都是一个高质量的产品。

              但是,它的编码方式非常类似,要读取代码,你必须对 “磁盘 ”上的布局和 RAM 中的数据结构布局有一个完整的心智模型。

              如果它有对结构体进行操作的 set/get/manipulate_or_update 函数,然后将结构体的代码序列化到磁盘上,那么阅读起来就会容易得多(我指的不是繁重的代码)。(我指的不是繁重的序列化,结构体加上适当的打包指令给编译器就足够了)。

              编辑:

              使用结构体的另一个原因是,不要使用裸数组,把 size_t 和数组放在结构体中。

              https://news.ycombinator.com/reply?id=39440071&goto=threads%

        2. 语言书呆子们努力创造更加复杂的语法,其实与代码的安全和保障毫无关系。因此,C#、Python 等语言都是如此。

          1. 不知道你想说什么,但有一类错误(死机和内存中运行外来代码的漏洞)在 C# 中是可以避免的。另一方面,PHP、C# 和 Python 对逻辑错误和诸如路径验证绊倒之类的问题没有任何保护,而这些问题往往会以其他方式被利用。

      2. 我相信在设计空间里有很多很多可行的方案,但无论好坏,这就是我们得到的方案。我不知道为什么 Pascal 没有出现(但不应该忽视大部分开发人员对这种语言的嘲笑: 但就 “更简单的 C 语言 ”与 Rust 的问题而言,在我看来,C 语言存在某种局部最优化,即对安全感兴趣的人希望对 C 语言做更多的小改动,而希望保留 C 语言的人则没有足够的动力去做实际的改动(Regehr 在文章的第一部分提到了这一点,要就友好 C 语言应该是什么达成任何形式的共识都是非常困难的)。

        1. 我觉得只要加入校验数组就会有很大的不同。也许现在改变文化还为时不晚,我认为 GCC 和 LLVM 都有编译选项。

          我还认为,如果 Pascal 有 C 语法前端、0 索引数组,并抛弃 Pascal 库,默认使用 libc,我可能就会改用 Pascal 了。

          1. 这一点 https://digitalmars.com/articles/C-biggest-mistake.html 会有很大帮助。鉴于:1)GCC 和 LLVM 很早以前就有了类似的扩展;2)大多数人都会在某一时刻到达那个/类似的目的地(因为它太有用了);3)据我所知,标准化本应是将当前普遍存在但非标准的最佳/流行做法标准化(而不是发明未经测试的新东西),我认为就 C 数组(以及矩阵和 ndarrays 等)而言,C 标准化委员会丢掉了球(而且丢了很长时间)。

          2. > 默认使用 libc

            我知道这与最初的讨论无关,因为内核并不使用 libc,但在用户领域,libc 设计上的缺陷与 C 语言本身的缺陷一样严重。

            举例来说,也许你知道什么时候该使用 strcpy()、strncpy()、strlcpy()、memcpy(),但说实话,我不记得在什么情况下该使用它们,而且我写的 C 代码也不够多,没必要把所有细微差别都记在脑子里。

          3. >0-index 数组

            每个 pascal 数组都指定了两个边界。

            因此,没有什么能阻止你做 `array[0..4] of Integer` 或其他事情。

          4. 我的意思是,从某种意义上说,现在改变还为时不晚。但我确实认为,作为技术人员,我们的职业很容易低估(和低估!)这种变革的社会因素。如果大家就前进的道路达成了一致,那么完成变革就变得易如反掌。大多数时候,困难的部分在于如何从众多相互排斥的选项中选择一条合适的道路。Linux Rust 的努力就是一个很好的例子: 我认为,让人们一致认为这是一件好事,而且是可取的,其难度至少不亚于编写代码的技术工作。正如 LWN 的文章所指出的,在过去几年中,这方面已经发生了很多变化,但似乎仍然存在相当大的阻力。

          5. 在某种程度上,这就是 Zig,C 语言用户的 Modula-2 (1978)。

      3. 如果你喜欢 Pascal,喜欢 Python,优先考虑你提到的事情,并且正在考虑像 Zig 那样年轻/直白但与 C 语言不直接兼容的语言,但有简单的声明–执行 FFI,那么 Nim https://nim-lang.org/ 可能真的是你正在寻找的语言。

        虽然继承/对象(甚至多方法动态派发)确实存在于该语言中,但它们在 stdlib 或整个生态系统中并不被广泛依赖。与 C++ 相比,Nim 拥有更多类似于 lisp 的元编程/语法宏(但与 sexp 相比,Nim 丰富的语法表现力使元编程变得更为复杂)。另一种描述方式是,它可能是一种更简洁、要素更少、词法更现代的 Ada。

        虽然我不知道有什么具体的 Linux 内核模块–Nim 项目,但由于 Nim 可以编译成 C 语言,因此它与 Linux 的集成应该不需要任何牵制和许可,尽管我确信它可以做得 “更漂亮”,就像任何东西一样。(人们也在用 Nim 编写一些操作系统内核)。

      4. > 我认为 Rust 比 C 语言有进步,但我也认为没有与 C 语言相似但更安全的语言是很不幸的。

        Modula-2 早在 1978 年就存在了,与 C 语言相比,它在安全性方面唯一的缺点就是免费后的使用仍然是个问题,其他方面都已经涵盖了。

        Modula-2的灵感来源于Mesa,Mesa是由施乐PARC创建的,因为他们需要一种安全的系统编程来摆脱BCPL。

        在很多方面,Zig 就是 Modula-2,其语法对 C 语言用户更有吸引力。

      5. > C 语言非常容易掌握

        确实不容易。新手在指针的生命周期、什么是安全指针运算、什么不是安全指针运算等问题上还没搞清楚,就已经被指针搞得焦头烂额了。

        > “C with X”

        我认为这里最有趣的领域是 Zig 和 D,尽管我自己对它们不太熟悉。

        我们真正想要的是 “不使用 C 语言就无法拼写 CVE ”的终结:一种能让编写可被远程入侵的软件变得尽可能困难的语言。

        1. 我来自帕斯卡,但最难理解的不是指针,而是 C 语言中的一切都是表达式,可能还有副作用。这与 Pascal 语句不同。

          1. 你说的难以理解的副作用是什么?

        2. 问题在于,我们首先还需要一种能让软件编写变得简单的语言。

          我仍然怀疑 Rust 就是这种语言。Rust 是一种比较成功的语言,有很多人在用它修修补补,也有一些轻微成功的软件(顺便问一下,用它编写的最成功的软件是什么?) 但在我所在的团队(做分布式文件系统)中,大家都在努力将 Rust 带入团队,但几年过去了,要在团队中获得采用仍然不容易。这部分归功于语言编写的难度。

          我认为,Rust 的成功很大程度上归功于该语言周围的工具,这使得依赖现有基础架构变得非常容易(尤其是与 C 语言相比)。但这种基础架构并不一定特别容易编写或维护。

          1. > 用它编写的最成功的软件是什么?

            这取决于你如何定义 “成功”,但我认为 “最成功的软件 ”可能是 “Facebook 内部的几百万条线在做的事情”,或者是 “亚马逊内部的几百万条线(可能吧,我也不知道),他们说这些线为 S3、EC2 和 CloudFront 提供动力。可能是 CloudFlare 的各种产品,因为给定的 GET 请求很可能会触及它们的基础设施。

        3. 你可能是对的。) 如果你懂汇编语言,C 语言就会非常容易上手。

          1. 有时我觉得,对于某些 C 语言用途,我们最好使用 “类型安全宏汇编器”。人们一直在使用 C 语言,但实际上它并不安全,然后就会被 UB/优化器交互删除安全关键代码踢到脸上。

            1. LLVM IR 在大多数情况下都是可以接受的元汇编器。它被直接使用的程度大致与人们认为这类工具有用的程度相当,而且它在某种程度上被直接使用。

              编写一门语言,目标是为 LLVM IR 提供最基本的功能,让它既能让人愉快地编程,又不会完全掩盖你正在做的事情,这将是一个很有价值的项目。我想玩一玩。

            2. 是的,djb 想要一个没有 UB 的 C 语言。这也是可行的。宏汇编器可能会落后于优化,但我认为没有 UB 的 C 语言仍然可以优化得很好。也许没有 UB 的优化语言会有一个小市场。

              1. 要做到 “无 UB ”而不牺牲一些可移植性确实很难。例如,C 语言花了几十年的时间才明确规定算术是双补码。然而,“UB 意味着优化器可以在假设不触发 UB 的情况下自由更改语义 ”则会带来巨大的惊喜。

            3. 如果我在过去 5 年中被 UB/优化器踢了一脚,那么我并没有注意到。

      6. 世界上最优秀的程序员和最大的公司都放弃了 C 语言,因为没有人能安全地编写 C 语言。我保证,你并不像你自己认为的那样精通 C 语言。

        相信我,我曾经从某个群体那里读到过这种观点。他们的编辑器每周都会出故障。

        Rust既没有对象,也没有继承,所以。

        1. 我无意尖酸刻薄,我可能误解了 “最好的 ”和 “没有人 ”的某些细微差别。但我并不认为 C 语言失宠是因为安全问题。我认为是因为生产力。(当然,这两者是有联系的,但并不是那么紧密,WordPress 可以证明这一点)。

      7. 引发转换的事情其实是在 “颠覆性的 10 倍提升 ”领域,Rust 确实围绕资源管理的某些方面提供了这些服务。Zig 则围绕着另一个 “更好 ”的轴心,这个轴心就是制作一个小核、可引导、兼容 C 语言的系统。对于内核项目来说,构建和引导问题在很久以前就已经从内部意义上解决了–有一个记录在案的流程。这个过程可能并不特别漂亮或优雅,但换汤不换药也不会让它好上十倍。

        另一方面,对于那些需要 “用字节做事 ”并希望链接到大量依赖关系的小型应用程序来说,Zig 是一个很有吸引力的答案,因为它提出了以系统化的方式来解决他们的问题。

        我喜欢 Pascal,但它并没有达到 10 倍的效果:在很多情况下,它可能比 C 语言(Dephi 或 FPC 变体)好 2-3 倍。它拥有人们会 “用 C++ 写 C ”的部分,而 WTF 要少得多。

  2. 我是 Rust 开发者。我的建议是:减少抽象。尤其是在与 C 打交道时,你总能做出几乎 1:1 的 Rust 接口。先从这个开始。然后,当常见模式开始出现时,先做数据抽象,即定义不同实现共享的数据结构。

    没有理由不能用 Rust 写简单的代码。除了容易把事情搞得过于复杂之外。如果开始到处使用 Trait 和泛型,你就会进入泛型地狱中的元编程。不久之后,你就会开始要求新的编译器特性,以便在那里生存下去。

    1. 很好的建议,但由此产生的一对一映射并不一定安全(就 Rust 而言),这在一定程度上也是人们早早跳槽到高级 Rust 功能的原因。因此,我想把你的建议改写如下:在没有充分考虑和理由的情况下,不要通过抽象来实现安全性。

      1. 我承认我对内核开发了解不够,但根据一般经验,一个常见的例子就是资源清理。

        比方说,你有几个资源必须按一定顺序清理。在 C 语言中,你只需调用相应的 free 函数。当用 Rust 对其进行抽象时,你就必须做出一个恼人的选择:

        和 C 语言一样,不实现自动删除,但将函数标记为不安全。这样可以实现最精简的零成本实现,而且简单易懂,但需要额外的维护以防止出现错误。

        将资源包裹在不安全的结构体中,这些结构体具有自动丢弃功能(当资源离开作用域时)。在我看来,这是一个糟糕的选择,因为维护者突然必须知道哪些结构有这种不安全的清理。简单的作用域突然变得重要,项目的顺序突然变得重要,真是一团糟。

        使用引用计数包装器来跟踪使用情况,并在不再使用时删除项目。大多数库都是这么做的,但这已经不再是最精简的应用程序接口了。

        或许还有另一种选择,即使用宏、泛型或两者兼而有之的元编程方式,既能实现零成本执行,又能进行正确的清理。这正是我最担心的。

    2. 这就是为什么我也在关注 zig,看看他们在未来 5 年左右会如何发展。希望他们不要把元编程看得太重。

      1. Zig 有一个特别有趣的功能,那就是它可以编译 C 代码。

        1. 不过,这更多地是 zig 编译器附带的整个 LLVM/Clang 软件包的特性。

          1. 无论机制如何,它都会对 Zig 的实际使用产生重大影响。

            1. 这是一种适用于任何喜欢使用 clang 的语言的机制。

              这也是 Swift 与 C 和 Objective-C 交互的方式,也将与 C++ 交互。

              或者说,Java Panama 项目是如何提取头文件信息以实现本地互操作的。

  3. Rust开发人员基于一个 “玩具 ”文件系统(即功能有限的子集)创建API的问题在于,API抽象并不能满足所有文件系统的需求。

    提供的建议很好–在 rust 中为 ext2 开发一个文件系统模块,然后再提出一个新的 API。

    1. > 提供的建议很好–用 Rust 为 ext2 开发一个文件系统模块,然后提出一个新的 API。

      虽然我认为关于 API 的观点不值得过多争论,但我认为 “去用 Rust 重写 ext2 ”是个非常愚蠢的建议,因为这样做的目的是 “让我们知道 API 应该是什么样子”。

      一旦有人用 Rust 实现了真正有人使用的文件系统,那才是真正的 “橡皮擦”。Rust 文件系统可能会有所不同。所有人都注意到了–API 在内核中并不稳定。尤其是在无人使用的情况下。应该有一些东西来避免 8 种不同的实现,但这种无休止的–去重新实现一个现有的设施,作为一种练习,教给我们的东西很少,而且主要是在浪费资源(我开始相信这才是重点)。

  4. “这里几乎不存在类型安全;函数无法知道它实际上传递了一个指向正确类型 inode 的指针”。

    inode 结构中没有类型标记吗?

    编辑:看了它的定义,我没发现问题所在。

    1. 让我们举个具体的例子,inode_operations 结构中的 link() 定义如下:

       int (*link) (struct dentry *,struct inode *,struct dentry *);
      

      让我们来看看 link() 操作的 ext4 实现:

       static int ext4_link(struct dentry *old_dentry、
                 struct inode *dir, struct dentry *dentry)
          {
          ...
             if ((ext4_test_inode_flag(dir, EXT4_INODE_PROJINHERIT)) &&
           (!projid_eq(EXT4_I(dir)->i_projid、
         EXT4_I(old_dentry->d_inode)->i_projid))))
        返回 -EXDEV;
          ....
      

      特别是调用 EXT4_I():

       static inline struct ext4_inode_info *EXT4_I(struct inode *inode)
          {
              return container_of(inode, struct ext4_inode_info, vfs_inode);
          }
      

      这基本上只是直接进行未检查的指针类型转换。因此,在任何非 ext4 创建的 struct inode 上调用 ext4_link(),都会出现严重问题。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注