【外评】Rust 版的 Linux 文件系统

在 2024 年 Linux 存储、文件系统、内存管理和 BPF 峰会上,Wedson Almeida Filho 和 Kent Overstreet 主持了一场关于在 Linux 文件系统中使用 Rust 的存储和文件系统综合会议。早在 2023 年 12 月,阿尔梅达就发布了一个 RFC 补丁集,其中包含一些用于文件系统的 Rust 抽象,结果在方法上出现了一些分歧。在五月中旬的同一天,他发布了 RFC 补丁的第二个版本,并希望与其他 Rust 相关话题一起讨论。

目标

在向与会者介绍了补丁的最新情况后,Almeida 列出了 Rust-for-Linux 项目的一些目标,这些目标体现在他提出的文件系统抽象中。首先是使用 Rust 的类型系统表达更多要求,以便在编译时发现更多错误。此外,该项目的开发人员还希望以 C 代码不易实现的方式自动执行一些任务,例如清理资源。总体想法是让文件系统开发体验更有成效,减少调试编译器可能发现的问题所花费的时间,并从整体上减少与内存相关的漏洞。

Overstreet 说,他参加过太多为期两周的 Bug 追捕行动,因此一直在想办法避免 bcachefs 出现这类问题。Rust 语言提供了比 C 语言更多的功能;它消除了未定义的行为,并提供了查看代码内部情况的功能。”如果你看不到发生了什么,你就无法进行调试”。他认为,由于使用了 Rust,内核开发 “在未来几十年内将变得容易得多”。用 Rust 编写的代码将有可能证明其正确性,这意味着可能导致功能开发脱轨的 bug 将大大减少。

阿尔梅达在幻灯片中举例说明了 Rust 类型系统如何消除某些类型的错误。他指出,当前内核中的 iget_locked() 函数有一系列复杂的要求。调用者必须检查返回值是否为空,如果不是,则需要检查返回的 struct inode 内容是新的还是现有的 inode。如果是新的,则需要在使用前对其进行初始化;如果初始化失败,则需要调用 iget_failed()。

与会者讨论了调用 iget_locked() 的人需要做些什么的细节问题,阿尔-维罗(Al Viro)不同意阿尔梅达(Almeida)幻灯片上的一些内容。Overstreet 认为,将规则封装到 Rust 类型和抽象中可以避免这种讨论/争论;编译器会知道该怎么做才是正确的。

Overstreet 指出,自第一篇文章发布以来,Christian Brauner 和 Alice Ryhl 对抽象的改进提供了很大帮助;特别是,根据 Rust 代码处理引用计数的方式,他学到了一些关于引用计数的知识。”奥弗斯特里特说:”这将使我们的生活变得更加轻松。

Almeida 放大了一张幻灯片,上面有 Rust 中与 iget_locked() 相对应的函数,即 get_or_create_inode()。他说,重要的是返回类型;与 C 语言一样,调用者必须检查是否失败,但成功的情况则大不相同。如果调用成功,调用者要么会收到一个普通的引用计数 inode(当不再引用 inode 对象时,其引用计数会自动递减),要么会收到一个新的 inode,如果它从未被初始化,则会自动调用与 iget_failed() 等价的函数。如果它被初始化(只能初始化一次),它就会变成一个普通的 inode,并自动减少引用计数。所有这些都是通过类型系统实现的。

Viro 似乎对这在实践中的效果有些怀疑。他想知道在源代码的什么地方定义这些约束。Almeida 说,整个想法是从 Viro 和其他文件系统开发人员那里确定约束条件,然后创建可以执行这些约束条件的类型和抽象。

脱节

Dave Chinner 询问了 C API 和 Rust API 名称之间的脱节问题,这意味着开发人员无法在查看 C 代码时知道等价的 Rust 调用是什么。他说,应该使用相同的名称,否则现有开发社区将完全不熟悉。此外,当 C 代码发生变化时,Rust 代码也需要跟进,但谁来做这项工作呢?阿尔梅达同意这是需要讨论的问题。

至于重命名函数的问题,他并不反对更改名称以匹配 C API,但认为 iget_locked() 并不是一个特别好的名称。利用这个机会创建更好的名称也许是有意义的。

Viro 说这不是一个好的选择,因为 iget_locked() 是一个库函数,而不是超级块对象的成员函数。Almeida 说,get_or_create_inode() 没有理由不变成一个库函数;他的例子只是为了展示如何在类型中编码约束。

Brauner 说,我们需要决定 Rust 抽象是要通用于所有内核文件系统,还是只专注于用 Rust 编写的较简单文件系统所需的功能。此外,在处理 get_or_create_inode()等函数比 iget_locked()编码更多约束的情况时,还存在一个长期问题。随着 C 代码的演进(至少在初期会比 Rust 代码演进得更快),我们需要保持两个 API 的同步。

Overstreet 说,这就涉及到在添加 Rust 抽象时是否要进行重构和清理的问题;他坚信这是必要的。James Bottomley 说,但问题还不止于此。对象的生命周期正在被编码到 Rust API 中,但在 C 语言中却没有相应的编码;如果有人改变了一方对象的生命周期,另一方就会出现错误。

Chinner 说,由于 inode 对象的生命周期有时与文件系统有关,因此也存在一些问题。将单一的生命周期理解编码到应用程序接口中,意味着其功能对某些文件系统不起作用。Overstreet 说,不使用 VFS API 的文件系统根本无法从中受益,但 Chinner 说,VFS inode 只是一种结构,管理其生命周期取决于文件系统。Almeida 说,只有目前调用 iget_locked()的文件系统才能使用该示例,并从中受益。Rust 开发人员并不是要强迫文件系统改变它们的工作方式。

分配之痛

Ted Ts’o说,问题的部分原因在于,人们正努力让 “每个人都改信 “Rust的宗教;他说,这不会发生,因为Linux中有50多种不同的文件系统,不可能立即转换。C 代码将继续得到改进,如果它破坏了 Rust 绑定,就会破坏依赖于它们的文件系统。他说,在可预见的未来,Rust 绑定是二等公民;Rust-for-Linux 开发人员而不是整个文件系统社区会遇到 Rust 绑定被破坏的问题。

他建议继续开发 Rust 绑定,同时继续发展 C 代码。随着这些变化的发生,”我们将发现将大量语义编码到类型系统中的概念是好事还是坏事”。他认为,一两年后,答案就会明朗化;但实际上,这将归结为 “痛苦在哪里分配 “的问题。在他看来,像这样的大规模变革几乎总是归结为 “痛苦分配问题”。

阿尔梅达说,他并不想让 C API 保持静态;他的目标是让文件系统开发人员解释 API 的语义,以便将其编码到 Rust 中。Bottomley 说,随着越来越多的语义被编码到绑定中,从同步的角度来看,它们将变得更加脆弱。有几个人不同意这种说法,他们提出了 “不 “等杂乱无章的回复。Almeida 说,这与 API 的任何用户都是一样的;如果 API 发生变化,用户也需要更新。但 Ts’o 尖锐地指出,并不是每个人都会学习 Rust;如果他进行了修改,他会修复所有受影响的 C 代码,但 “因为我不懂 Rust,所以我不会修复 Rust 绑定,抱歉”。

Viro 回到了他对 iget_locked() 替代方案的反对意见。他认为潜在的问题是依赖方法而不是函数;使用方法不是正确的做法,因为参数没有明确指定。但 Overstreet 说,对方法的抱怨来自于 C++ 等语言,这些语言过于依赖继承,而继承是一个 “垃圾想法”。Rust 并没有这样做;在 Rust 中,方法在很大程度上只是一个语法元素。

大家讨论了类型中编码的具体内容。Jan Kara 说,有一些行为与 inode 相关,如引用计数和处理,但还有一些行为是 iget_locked()函数固有的。Overstreet 和 Almeida 说,这两个部分都被编码到了类型中,但又是分开的;使用 inode 类型的其他函数可能会有不同属性的返回值。

Viro 介绍了他对 inode 在 VFS 中的工作方式的一些推理。他同意从小处着手,看看事情的发展方向。Overstreet 建议,也许使用的例子不是一个好的起点,”因为这是一个复杂的案例”。会议结束时,Viro 在一片笑声中回答说:”哦,不,不是这样的”。

本文文字及图片出自 Rust for filesystems

你也许感兴趣的:

共有 173 条讨论

  1. 我不明白为什么每个文件系统都有自定义的 inode 生命周期,但仍然使用相同的函数进行 inode 生命周期管理,而且显然语义不同?如果同一个函数必须根据实现细节以不同方式使用,这听起来就像是抽象层的反面。

    如果 inode 的生命周期是特定于文件系统的,就应该通过特定于文件系统的函数来管理。

    1. >> 我不明白为什么每个文件系统都有自定义的 inode 生命周期,但仍然使用相同的函数进行 inode 生命周期管理,而且显然语义不同?

      我也有同样的疑问。他们正试图理解(甚至记录)所有的 C API,以便完成rust工作。听起来,收集所有这些信息可能会带来一些[WTFs]和[refactoring],这样类似的问题就不会在一开始就出现了,这将是一件好事。

    2. 我的理解是,他们正努力在 VFS 层中尽可能广泛地抽象,但仍会有(很多?)边缘情况无法适应,需要在 FS 特定层中处理。或许,inode 生命周期只是讨论的一个初始起点?

    3. > 但仍使用相同的功能进行 inode 生命周期管理

      我绝不是专家,但我还是有点知识的,有不同的函数可以用来创建 inode,然后将它们插入缓存。这里重点介绍的 “iget_locked() “是一种特殊的操作模式,但并非每个 FS 都会出于某种原因使用这种模式(或并非在任何情况下都会使用)。例如:FAT 并不使用它,因为 inode 数字是编造出来的,而 FS 维护着自己的 FAT 位置到 inode 的映射。还有像 “proc “这样的文件系统,它们从不缓存其 inode 对象(我很确定是这样,但我并不声称了解 proc :P)

      从消费者的角度来看,inode 对象本身仍然具有相同的状态流,而不管它们来自哪里。只是 FS 层对 inode 对象的创建和内部处理取决于 FS 的需求。

    4. 我推测它应该是通过让编译器跟踪 inodes 的生命周期来实现的。编译器有望帮助处理短暂引用(文件系统仍需将链接计数存储到磁盘)。

  2. 也许他们问错了问题?

    Rust 是否需要改变才能更容易调用 C?

    我对 Rust 进行了一些研究,(作为业余爱好者,)(对我来说)仍然不清楚如何与 C 进行互操作(我相信读到这篇文章的人已经做到了)。相比之下,在 C++ 和 Objective C 中,你需要做的只是包含正确的头文件并调用函数。Swift 可以让你包含 Objective C 文件,并从中调用 C 语言。

    也许在这种情况下,Rust 作为一种语言需要稍微弯曲一下,而不是期待内核开发者向语言弯曲?

    1. 从 Rust 调用 C 语言可以非常简单。你只需声明外部函数并调用它。例如,直接从 Rust 书 https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#usin… :

        extern "C" {
            fn abs(input: i32) -> i32;
        }
        fn main() {
            unsafe {
                println!("Absolute value of -3 according to C: {}", abs(-3));
            }
        }
      

      现在,如果你有一个复杂的库,又不想手工编写所有的声明,你可以使用像 bindgen 这样的工具从 C 头文件中自动生成这些外部声明:https://github.com/rust-lang/rust-bindgen。

      有一种观点认为,类似 bindgen 这样的工具可以包含在 Rust 中,不需要第三方依赖,也不需要设置 build.rs 来调用它,但这并不是本文要讨论的问题。

      问题不在于低级绑定,而在于在 Rust 中更加习以为常的高级封装。你不可能有一个通用的工具,可以从任意的 C 代码中自动做到这一点。

      1. 传递整数很容易,共享结构体、字符串和上下文指针以用于跨越语言障碍的回调等通常要困难得多。

        1. 对于调用 C 语言的 rust 代码,可以使用 #[repr(C)]共享结构体。参见 https://doc.rust-lang.org/reference/type-layout.html#reprc-s

          (挑刺: 我认为将其称为 “C 表示法 “在技术上并不正确,因为 C 中的严格布局取决于 C 编译器/ABI。对于在 32 位和 64 位系统之间串行化数据,我不相信这种方式会足够好。不过对于在同一系统上调用代码来说,这已经足够好了)

      2. 这并不是真正的 “简单”,它与其他语言(C++ 除外)中的 C FFI 差不多,缺点也一样。

        1. 与 C++ 也差不多。在 C++ 中,你需要一个 `extern “C”`,因为 C++ 的链接并不能保证与 C 的链接相同。你可以在预处理器条件中将其包裹起来,但这并不比 rust 的 bindgen 简单多少。

          很多 C 到 C++ 的互操作实际上都是在不知情的情况下做错的。将一个 C++ 静态函数作为回调函数扔进一个 C++ 函数中通常是可行的,但这在技术上并不正确,因为如果没有外部 “C”,就不能保证链接是相同的。在实践中,通常是相同的,但这是由实现定义的,C++ 可以使用与 C 不同的调用约定(如 cdecl vs fastcall vs stdcall)。Borland C++ 编译器默认对 C++ 函数使用 fastcall,这将使它们成为 C 函数的非法回调)。

          Objective-C 和 C++ 的 C interop 与其他语言的主要区别在于没有预处理器。宏可以正常工作,因为它们使用了相同的预处理器。这在其他不会使用 C 预处理器的语言中确实不容易实现。

          1. 我觉得你混淆了一些术语。

            > 在 C++ 中,你需要一个 `extern “C”`,因为 C++ 的链接并不能保证与 C 的链接一样。

            `extern “C”`与链接无关,它所做的只是禁止命名,所以你会得到与 C 编译器相同的符号名。

            > 将 C++ 静态函数作为回调函数扔到 C 函数中通常是可行的,但这在技术上是不正确的,因为如果没有 extern “C”,就不能保证链接是相同的。

            再说一遍,链接在这里并不重要。您的 C++ 回调函数也不必声明为 extern “C”,因为符号名称并不重要。正如你正确指出的,调用约定必须匹配,但实际上这只在 x86 Windows 上才有意义。(一个显著的例子是向 Win32 API 函数传递回调,该函数默认使用 `stdcall`)。幸运的是,x86_64 和 ARM 已经摒弃了这种疯狂的做法,(每个平台)只有一个调用约定。

            1. > `extern “C”`与链接无关,它所做的只是禁用 “namemangling”,所以你会得到与 C 编译器相同的符号名。

              extern “C “还能确保使用 C 语言的调用约定,这与回调有关。这不仅仅是名称混淆。这就是 extern “C “静态函数存在的原因。实际上,你可以通过 extern “C” 与 extern “C++”来重载一个 C++ 函数,它会根据传入的函数是用 C 还是 C++ 链接声明的来进行适当的调度。

              我不确定这两个术语是否混淆,因为大多数文档都是这么说的:https://learn.microsoft.com/en-us/cpp/cpp/extern-cpp?view=ms…

              > 在 C++ 中,当与字符串一起使用时,extern 表示声明符使用了另一种语言的链接约定。C 语言的函数和数据只有在事先声明为具有 C 语言链接时才能被访问。不过,它们必须在单独编译的翻译单元中定义。

              https://en.cppreference.com/w/cpp/language/language_linkage

              你回复的帖子完全正确。extern “C “完全是关于链接的,其中包括调用约定和名称混淆。

              > 正如你正确指出的那样,调用约定必须匹配,但实际上这只在 x86 Windows 上才有意义。

              或者,如果你想让你的程序真正正确,而不是在大多数常见情况下(包括在未来的系统上)只是顺便工作一下。

              如果你从 C++ 向 C 函数传递回调,除非回调声明为 extern “C”,否则就是错误的。

        2. 这怎么不简单?你只需声明函数,然后调用它。我很难想象还有比这更简单的事情。

          1. 现在想象一下,有一二百个函数、结构和回调,其中一些只能以 CPP 宏的形式在内部实现中公开。PJSIP 底层 API 就是一个例子。

        3. … 还有呢?大多数语言都能让 C 语言的互操作变得简单。

          1. 在非繁琐的应用程序接口(API)上,它们很快就会变得臃肿不堪,需要在数十个文件中使用数百个定义,还要使用宏来引导。当然,人们仍然可以完成工作,但这已经超出了简单的范畴。

            1. 正如您回复的原始评论中提到的,这就是 bindgen 的用途。

              1. 它能很好地处理 API 中的预处理器宏吗?

                1. 我曾成功地用它处理过由 IDL 生成的 Win32 COM 接口的头文件,其中包括臭名昭著的 “windows.h “的主要部分。几乎所有类型都是宏。

                  这是一个非常好理解的领域。

                  只要打开文档就可以了。

    2. 这不是 rust 中值得注意的挑战,也与文章无关。

      这篇文章讲的是找到使用 rust 实际实现内核 fs 驱动程序等的方法。请注意,内核中的任何 rust 代码都必须使用 C 接口。

      Bindgen 很适合你所想的用例。

      https://github.com/rust-lang/rust-bindgen

      1. 是的,Rust 的支持者们的野心要大得多。不仅仅是能用 Rust 来编写文件系统代码,而是要能抓住与复杂(和不断变化的)FS 开发语义相关的大量正确性问题。

    3. 其实很简单。你只需要声明 `extern “C” fn foo() -> T` 就能从 Rust 中调用它,并通过添加 #[link] 属性或在 build.rs 中添加链接标志来传递链接标志。

      你可以使用 bindgen crate 提前生成绑定,也可以在 build.rs 中加入!() 生成的绑定。

      人们通常会创建一个只包含绑定(通常是生成的)的 “sys “板块。然后,他们的代码就可以正常地 “使用 “来自 sys crate 的绑定。

      > 相比之下,在 C++ 和 Objective C 中,您只需包含正确的头文件

      并与库链接。

    4. 问题的关键在于,Rust 可以为不变式建模,而 C 却做不到。你可以两种方式都调用,但如果 C 无法表达 Rust 可以表达的东西,那就会对 API 的设计产生重要影响,而 API 必须是两者通用的。

      1. 我不是这么理解的: 显然,我们需要能用 Rust 编写文件系统,而编写文件系统 API 的内核开发者并不想维护与 Rust 的绑定。

    5. 我写过调用 C++ 的 Rust 代码

      这并不完全简单,但总的来说,我在几天之内就弄明白了所有需要做的事情。

      调用 C 肯定会非常相似。

    6. > 为了更容易调用 C 语言,Rust 需要改变吗?

      不需要,因为这已经非常简单了。你只需将 C 函数声明为 “extern “C””,然后调用即可。(你通常需要使用 “unsafe “并将引用转换或投递为原始指针,但这也是简单的语法)。

      有一些工具(最常用的是 bindgen)可以扫描 C 语言头文件并为你生成声明,这样你就不必手动复制/粘贴并亲自键入了。

      > 也许在这种情况下,Rust 作为一门语言需要稍微弯曲一下,而不是期待内核开发人员向这门语言弯曲?

      我想你可能误解了文章的意思?这里的语言没有任何问题。争论的焦点在于 Rust 应该如何使用。Rust-for-Linux 开发者希望利用 Rust 的特性和类型系统,将语义编码到他们的 API 调用中,从而使这些调用更安全,使用时更不容易出错。而 C 语言方面的开发者则担心,这样做会增加他们改进 C 语言 API 行为和语义的难度,因为这样一来,Rust API 也需要更新,而他们并不想承担这项工作。

      另一种可能更容易接受的做法是,不使用 Rust 特性和类型系统,以便将语义编码到 Rust API 中。这样一来,C 语言开发者就会更轻松,因为当 C 语言 API 发生变化时,更新 Rust API 就会变得机械而简单。但我们可能会问,如果 Rust-for-Linux 开发者不能使用 Rust 的一些特性来开发更好、更安全的 API,那么所有这些 Rust 工作又有什么意义呢?

      > 我做了一些 Rust 的工作,(作为业余爱好者,)(对我来说)仍然不清楚如何与 C 交互。

      有点奇怪的是,你承认自己对语言的理解不够深入,无法对当前的话题发表明智的意见,但你目前的评论却得了最高票。

  3. 我不太清楚,对 Linux FS 系统也不够熟悉,不知道这个 Rust API 是要封装还是重新实现 C API?如果是重新实现(或者说是额外的 API),那么保持与 C API 相同的名称似乎会有问题,而且随着时间的推移会导致更多的混淆,即使最初它能帮助已经熟悉的开发人员更快地摸清情况。

    1. > Almeida 制作了一张幻灯片,介绍了 Rust 中的 iget_locked(),即 get_or_create_inode()。

      答案似乎是,它在重新实现,并没有使用相同的名称。

      1. 我不熟悉这些函数,但我的印象是它们实际上不应该使用相同的名称。

        由于 Rust 函数有隐式/自动行为,这取决于它的状态如何以及调用站点如何使用它,而 C 语言函数没有任何隐式/自动行为(例如,必须 “手动 “进行单独/显式的生命周期调用),我甚至不明白它们有相同名称的理由。

        也就是说,使用相同的名称在某种程度上是不对的,因为这些函数的作用和功能是不同的。

        但至少从 rust 网站上看,文档中提及原始 C 名称是合理的。

  4. 从会议记录中我得出结论,内核中的 Rust 看起来像是一种额外的复杂性税。我的意思是,如果你从头开始编写操作系统,你就可以充分利用你的语言。而将 Rust 添加到已经非常庞大的代码库中,则会产生额外的问题,正如我们在这里看到的那样。

    1. > 额外的复杂性税

      是的,但这应该会被更简单的驱动程序开发所抵消。请参阅博客中关于旭日Linux的Rust GPU驱动程序的文章,它在一个月内就完成了开发。编辑:谷歌 “tales of the m1 gpu”(作者对黑客新闻有非常负面的看法,如果您喜欢,请点击链接 https://asahilinux.org/2022/11/tales-of-the-m1-gpu/ 阅读)。

      它是通用的吗?我们将在未来几年拭目以待。

      1. 也许我看错了什么,但这篇 HN 帖子的讨论内容听起来很像是在尝试用 Rust 制作 Linux 子系统和 API,这样 Rust 的类型系统就可以通过其抽象来强制符合安全机制。

        这与使用 Linux 子系统 C API 用 rust 编写驱动程序有着本质区别,而且难度更大。

        我可以看到很多驱动程序都能轻松承担用 rust 编写驱动程序的复杂性税。用 Rust 编写整个子系统的复杂度税似乎是一个难上加难的问题。

        1. 你可以直接编写调用 C API 的 rust 代码,这样或许就能避免很多类似文章中的讨论。

          但是,制作好的封装器会让下游组件的开发变得更加容易。正如讨论中的反对者所说:目前大约有 50 个文件系统驱动程序。如果你能把接口做得更好,对每个文件系统都有好处(好吧,对每个使用 rust 的文件系统都有好处,但不需要很多文件系统就能让你的努力得到回报)。你只需为接口支付一次复杂性税,而使用接口的每个组件都能从复杂性上获益。

          如果 C 语言有足够的表现力来实现良好的抽象,我们也可以讨论更好的 C 语言应用程序接口。

      2. > 作者对黑客新闻持非常负面的看法

        我不确定作者(Asahi Lina)是否有,但项目负责人 Hector Martin 肯定有。

      3. 唉,如果直接点击该链接,只会看到一篇关于政治的咆哮。

        复制粘贴就可以了。

        1. “对政治的咆哮”,哈哈。或者像其他人喜欢叫的那样 “用恰当的方式描述真实的关切”。

          我自己也观察到过这些煽动性的评论子图,我心想,这一定是未经管理和不受欢迎的行为的巨大滋生地,因为一旦被标记,这些行为或多或少就会消失。

          1. 在这种特殊情况下,对 “政治 “的抱怨会产生一种酸溜溜的味道,即助长(或至少不纵容)骚扰行为,以至于单身人士因此而自杀。

            为什么?即使你不确定自己对同性恋运动的看法;即使你已经对同性恋运动下定了决心,反对他们的观点或其中的某些观点;我也拒绝相信任何一个人不愿意阻止别人恃强凌弱而自杀!

            匆忙写下的唠叨,送给那些想抱怨 “政治 “已经渗透到所有讨论中的人:

            看到这么多的人因为 “政治 “问题一开始抱怨,就断绝与其他群体的联系并立即否定他们,我真的很难过。

            我明白你们不想卷入这些狗屁争论,也明白要搞清楚谁对谁错是件很乏味的事。尤其是因为从来没有明确的答案。如果你有这样的感觉,然后又开始抱怨 “政治 “无处不在,那就请小心了:

            假装不关心政治在大多数情况下是行不通的,因为政治基本上是 “在某种公共领域采取行动(或故意不采取行动)”的另一种说法,而你们都在这样做,而且当 “政治 “到达一个主题时,它们就会停留在那里,至少在你们目睹的那个具体案例中是这样!无论你喜欢与否,你都只是这个充满冲突的超级互联和混乱世界的一部分。

            假装不关心政治也是为了维持现状,因为任何有可能改变现状的事情本质上都是政治话题。

            请不要在 “政治 “话题上扭头,或者,至少不要以这种方式抱怨,因为这只会让不公正的行为继续下去。是提出 “政治 “话题的人不公正,还是他们抱怨的人不公正,这都不重要)。在这两种情况下,最好的办法要么是完全避免发表评论,要么是在 “政治 “对话中表达自己的批评意见。

            1. > 我拒绝相信任何一个人不想阻止别人恃强凌弱而自杀!

              有很多人确实想要自由地说出他们所选择的内容,包括长时间的直接骚扰,如果有人因此自杀,他们也会耸耸肩。除了禁止他们进入文明空间,我们能做的并不多。

              1. “欺凌 “是一种判断。这个词的本质是一种判断,即所做的事情是不好的,而做这件事的人不会这样描述。

                我的意思不是说欺负人不是坏事(这是同义反复),我的意思是说谈论欺负人往往是在引出问题,而且是有意为之,目的是掩盖实际发生的事件。禁止谈论国王、民选官员甚至警察或官僚的 “大不敬法 “现在被冠以 “反欺凌 “的理由。

                不过,我没有在博客中看到任何关于政治的咆哮。但这个主题有一种 “我的政治不是政治,因为它们是真实的,而你的政治是政治,因为它们是谎言 “的味道。

                1. 推荐人替换页面所说的是奇异果农场,它所做的事情,即使是美国第一修正案非常宽泛的保护措施也无法保护。(这里的刑事责任是 “故意转移精神痛苦”,不过要注意的是,大多数指控这种行为的诉讼都是毫无根据的诉讼,在很大程度上都无法通过动议驳回,因为 “他们让我感觉不好 “不足以指控 “故意转移精神痛苦”(IIED)。

                  1. > 这里的刑事责任是 “故意造成精神痛苦”、

                    故意造成精神痛苦是一种侵权行为,而不是刑事犯罪。

                2. 我是说这是正确的。欺凌者,至少是成年人,不会把他们所做的事情称为欺凌(那些认为 “实际上欺凌是一种社会矫正行为,我实际上只是在帮忙 “的绝对天才除外)。

                  我们只是在互相取笑,对吧?看看乔治,他在笑!他完全是在开玩笑,而不是为了不丢面子而表现出顺从的样子。

            2. 我很高兴在这个可怕的网站上至少还有一些人有这种感觉。

            3. 在我看来,你必须区分针对某人的骚扰和关于某人的讨论。

              使用 hacker news 向某人的电子邮件发送信息是不可能的。甚至不可能向另一个黑客新闻用户发送电子邮件。你可以想象黑客新闻被用来组织对某人的骚扰,但我从未见过这样的指控或证据。

              因此,我们已经确定,既然针对他们的骚扰是不可能的,那么他们的问题就是黑客新闻上的人写了关于他们的坏话。

              接下来,这些东西似乎经常被标记或降权,从而减少了曝光率。但这显然还不够,因为在谷歌上也能找到它们。因此,问题的核心就在这里。谷歌上有关于这个人的内容,而他们却不希望这些内容出现在谷歌上。这就是投诉。因此,这个人基本上是在说,如果在谷歌上可以找到对他们不利的报道,那就是骚扰,需要清除。如果不清除,就是欺凌,可能会导致自杀。

              如果你愿意,这是一个非常有野心的 “抢地盘 “行为,它开始严重侵犯其他人的权利。

              这与 “制止恐怖主义 “或 “为孩子们着想 “等其他事情有异曲同工之妙。很显然,骚扰是不好的,恐怖主义是不好的,恋童癖也不是什么好东西。但我们不能因此而完全放弃自由。

            1. 批评和骚扰之间有明显的区别。我们不要装作不知道旭日Linux团队为什么会受到死亡威胁,也许我们可以试着改善这种状况。

              1. 我完全不知道这些情况。我之前听说过这个发行版,也听说过它专注于苹果笔记本电脑,甚至用 rust 来编写驱动程序,但仅此而已。

                为什么会有人对共享 FLOW 感到不满呢?

              2. 有人在 HN 上发布死亡威胁?如果有的话,我希望它们被迅速删除。

                1. 你也许应该看看朝日Linux的帖子。

                  1. 哪个帖子?我刚刚浏览了几篇,没有看到任何死亡威胁。

            2. > 我没有读过这些评论,但听起来这条信息的作者似乎对自己无法控制他人的谈话感到有些恼火。

              你他妈是认真的吗?你真的读过他们说的话吗?你有毛病吗?他们不是因为 “无法控制他人的谈话 “而 “恼火”,他们是在指出,由于 Hacker News 不严格、不负责任的管理,以及与更糟糕的社区的令人不安的重叠,它是无休止的仇恨和骚扰的温床,在其他场合,这种仇恨和骚扰可能并已经导致人们自杀。这才是更严重、更可以理解的问题,而把这归结为某人 “反言论自由 “则是问题的一部分。

              1. 鉴于本网站的严格审核,这听起来不太可能。因此,除非我看到任何证据,否则我会继续这么认为。

                根据我查看朝日 Linux 上以前的帖子后得出的结论,正如另一位评论者所建议的那样,该消息的作者对一些 HN 用户谈论他用来制作视频流的另一个卡通人物感到愤怒。在我看来,他有点反应过度了。

                1. 马丁写的那封信从字面上解释了为什么你无法看到这样的评论主题,这是 HN 的审核方式造成的。你读过这封信吗?

                  还有,别再提什么 “朝日莉娜=赫克托-马丁 “了。你简直是在附和奇异果农场的说法,还奇怪为什么人们会指责 HN 与该网站有重叠之处?

                  1. 我浏览了朝日 Linux 以前帖子的评论,并打开了 “show dead”(死亡显示),结果发现只有这些。我不在乎他是否在视频流中把自己表现为一个卡通人物,我之所以提到这一点,是因为那些帖子上被标记的评论就是在争论这个问题。

                    也许你可以链接一下你认为能证明他在那条信息中所说的话的评论主题?

          2. > 或者像其他人喜欢说的那样: “以恰当的方式描述了一个真实的问题”。

            哦,拜托。每个边缘问题的活动家都说同样的话。这并不能说明它是真的。

        2. 我在链接中找不到咆哮。是链接换了还是我忽略了什么?

          1. 该网站的作者详细描述了他们对 HN 审核方式的意见(我不能说我不同意,尤其是在 HN 故意禁用那些对 HN 有意见的网站的推荐头之后)。这只有在 HN 出现在引用 URL 中时才会显示。

            我不会说这是咆哮,而是礼貌地要求 HN 改变政策。

            1. > 我不会把它称为咆哮,而是礼貌地请求修改 HN 政策。

              这是由封锁的人提出的,他们对此无能为力。

              1. 什么?你需要做的就是重新提交没有 Referer 头信息的请求。对使用火狐浏览器的我来说,这意味着点击地址栏,什么都不改,然后点击回车。

                这还算不上 “无能为力”。

                1. 我指的是没有能力改变 HN 管理政策的人。

                  可以这样考虑

                       1. 我想,"哦,这看起来很相关,让我打开这个链接吧"。
                      2. 我看到满屏反对 HN 审核的声音。
                      3. 3. 我耸耸肩,关闭标签。
                  

                  由于我只是一个无法改变 HN 管理的普通用户,结果是 HN 没有改变,但我对 Asahi Linux 人员的看法却变差了。

                  1. 奇怪的是:HN 本应是技术性的,但朝日 Linux 背后的人却证明了自己是技术奇才;与此同时,HN 似乎对金钱和伪自由主义政治更感兴趣,而不是卓越的技术变革。但这就是我们的时代。

                    1. 实际上,HN 已经偏离了它的初衷,向左走了很远很远。

                      它一开始叫《创业新闻》。它当然会对钱和赚钱感兴趣。咄咄怪事。

          2. 在禁用 JS 的情况下也无法显示

          3. 如果你点击链接–任何从 HN 链接到 asahilinux.org 的链接–开始应该是 “嗨!看起来你可能来自 Hacker News”,然后是 Hector Martin 咆哮着说他并不负责 HN 的审核政策。

            在 Arkell 诉 Pressdram 一案中给出的回应是恰当的。

      4. 警告: 不要点击该链接。复制并粘贴 URL。该网站只会对其检测到的 HN 用户进行辱骂和骚扰。

    2. 读起来很像让完美成为美好的敌人。

    3. 虽然我同意 rust 有好处,但我倾向于认为所有的理性都无法对抗炒作。

      税收将被视为拥抱未来和进步的必需品。

      我在想,为什么不把我们自己限制在一个安全的子集上,而要跳进一个充满未知错误和权衡的巨大浪潮中呢?

      1. MISRA 与之相当接近,但您可能需要的各种东西(如整数运算)在 C 语言中都有潜在的 UB。

        (目前最好的尝试是 https://sel4.systems/,它是用 C 语言编写的,但附加了大量的安全证明。语言设计的问题基本上是:证明是否应该成为语言的一部分?)

        1. 鉴于未定义的行为只是指 “标准未定义的”,你是否能通过(MISRA/替代方案、特定编译器、特定体系结构)三重方式有效地接近于识别安全子集?

          1. 不,”未定义行为 “不是指 “标准未定义”,而是指标准中规定 “未定义行为 “的地方。然后是关于 “编译器可以假设 UB 不会发生,然后在此基础上进行优化 “的漫长而复杂的争论。

            在某些特定情况下,你或许可以将其收紧,而这些战斗正在其他地方进行,但像锁的生命周期这样的事情,如果没有语言内部或外部的大量额外注释,你是做不到的。

            1. 对不起,是的,我的措辞不当。

        2. 我觉得 frama-c 相当不错,包括所有的整数怪癖

      2. 我喜欢莱纳斯关于进化的观点。随着时间的推移,进化会告诉人们什么是最明智的选择。这在某种程度上就像 “市场”。让每个人都下注,等待、观察、分析、研究。就是这样。

    4. 可以说,任何额外的代码都在引入复杂性,不仅仅是编写 rust。这是否意味着,既然我们已经如此庞大,就应该停止创新,进入无限期的维护状态?

      在一个地方征税未必是净负值,如果它能像现实世界中那样用来抵消其他问题的话。而仅仅因为一次讨论就说它不会抵消任何问题,没有一个明确的结论,这就显得论据不足了。

      1. >这是否意味着我们应该停止创新,进入无限期的维护状态?

        如果你的意思是不使用 Rust(或者其他语言,比如 Zig 或 Ada?)就意味着 Linux 内核不会有创新,那我就不敢苟同了,因为普通的 c 语言已经取得了很多进步(比如 io_uring),更不用说 c 语言本身也可以通过改变来改善开发人员的工效–因为这似乎才是问题的关键所在。

        这也提出了一个问题:当 Rust 不再是当红语言时,未来会发生什么?现在有两个不同的代码库,可能由两个不同的、不断减少的活跃维护者来维护。

        1. > 如果你的意思是不使用 Rust(或者其他语言,比如 Zig 或 Ada?)就意味着 Linux 内核没有创新,那我就不敢苟同了,因为普通的 C 语言已经取得了很多进步。

          不,我不是这个意思。如果我没理解错的话,他认为使用 rust 是一种税收,而税收总是不好的,应该避免。

          我们显然无法预知未来。我们也不知道未来的维护者是什么样的,也不知道是否会有更多的人理解内核级 C 语言或内核级 Rust,或者两者兼而有之。

          我也不认为任何一个开发者能声称自己完全掌握了 Linux 内核的每一部分。因此,如果一个人想开发某个特定的子部分,他就必须熟悉它,而与所使用的语言无关。然后我们又回到了争论的话题:额外的税收是坏事,还是它能给我们带来什么?

      2. > 可以说任何额外的代码都会带来复杂性

        额外的代码可以取代现有或未来更复杂的代码,因此可以降低复杂性

  5. 考虑到这些讨论通常是如何进行的,以及变化的规模,我觉得这种讨论非常文明。

    我不同意这个主题的负面基调,鉴于参与讨论的各方都能清楚地以零废话的方式表达痛点,我还是相当乐观的。

    1. 我发现自己读这篇文章更多的是为了出色的笔记,而不是为了内容。

      我猜想,这场讨论就像我们所期望的那样,充满了争议、蜿蜒曲折、吹毛求疵,而杰克-埃奇(Jake Edge,他撰写了这篇摘要)则非常善于剔除这些因素,并写出实质内容。

      1. 当然。

        我们谈论的是那些能力超群的人,他们多年来一直在开发一款重要的软件,并为此投入了大量心血、精力、经验和责任。

        这场争论是一个仍在进行的过程,事实上,它还在不断进步,这足以证明情况是多么健康。

        我本来以为整个 Rust 事件会在一股股令人厌恶的言论中,已经被封杀 10 次了。

        这意味着,不仅 Rust 被证明有希望胜任这项工作,而且两个团队都愿意并能胜任整合工作。

        这些项目令人精疲力竭,压力巨大,而且持续时间很长。

        我还是觉得报告显示的结果是积极的。人们期待什么?快速行动,打破常规?

        一连串的 “不 “才是应该的。

        1. 我肯定是认为我们需要赶紧离开 C。Rust、Go、Zig 等并不重要,重要的是任何能够抓住一些软弱的人类不断重复的错误的东西。

          话虽如此,文件系统也是不能出错的基础架构之一。引入内存损坏 bug 导致每周四崩溃?管它呢。在日全食的闰年,0.1% 的用户会丢失全部数据?天启

          在与存储接口连接时,再小心也不为过。C 语言可能有很多缺点,但它是我们所熟悉的魔鬼。

        2. 我同意。理想情况下,每当你提出问题并得到 “不行 “的答复时,你就会对你正在修改的系统有所了解,或者评审人对你的解决方案有所了解。然后你改进你的解决方案,再回来。

          最终,大家会达成共识–要么解决方案变得足够好,要么开发人员和审核人员都认为它行不通,从而放弃这条开发线路。

          在生产过程中进行大规模变革既困难又混乱,而且涉及到很多不完美的人,我们希望这些人大部分都是出于好意。

        3. 使用 70 年代的技术会带来很多问题。Rust已经在Windows、Mac(或iOS)和Android等其他操作系统上进行了测试,并解决了C和C++的几个隐患。引用安卓团队的一些话[1]:

          “迄今为止,在 Android 的 Rust 代码中发现的内存安全漏洞为零。”

          “安全措施让内存不安全的语言变得缓慢”

          并不是说 Rust 是解决所有问题的完美方案,但在有意义的地方使用它绝对不是一个离谱的命题。

          [1] https://security.googleblog.com/2022/12/memory-safe-language

  6. 在 Linux 内核中提供更多选项总是有益的。不过,Rust 可能不是万能的解决方案。虽然 Rust 尽全力确保其编程模型的安全性,但它仍然是一个有限的模型。内存问题?使用 Rust!并发问题?改用 Rust!但如果不使用不安全的代码块,就无法做到 C 语言所做的一切。Rust 可以为这些问题提供一个全新的视角,但它并不是一个完整的解决方案。

    1. > 但如果不使用不安全代码块,就无法做到 C 语言所做的一切

      对于这项特殊工作而言,Rust 的巨大优势在于它热衷于将此类安全问题封装到类型中。这正是本文所要讨论的。

      C 语言,尤其是 C 语言在内核中的使用方式,使得每个人都有责任完全掌握默许规则。这种情况无法扩展。满屋子的内核开发人员对他们都在使用的数据结构的规则并不完全认同!

      Rust 能很好地让你意识到你需要知道的规则,并在可能是别人的问题时,让它不是你的问题,以确保规则得到遵守。有时,结果并不是最优的,但即使在 Linux 内核中,次优往往也是正确的默认值,我们可以为那些有能力学习更多奇怪规则的人提供一个(不安全的)逃生舱门,以获得更好的性能。

      1. > 不能扩展。

        笑……你说的是用 C 语言编写的 Linux 内核。

        几十年来,无论是虚拟机、操作系统还是设备驱动程序等,绝大多数软件都是以 C 语言 “起家 “的。

        C 语言的成功规模无与伦比。

        1. 在过去的 40 多年里,C 语言的应用规模当然是无与伦比的,但网络战时代的安全问题也是如此。

          https://www.whitehouse.gov/oncd/briefing-room/2024/02/26/pre

          如果不知何故,我们到了这样一个时代:(a) 操作系统广泛使用另一种语言,(b) 1988 年的莫里斯蠕虫病毒是由于缓冲区溢出问题而发生的,那么目前形式的 C 绝对不会被采用。

          1. C 语言只是一种方便的汇编语言。在一个注重性能的时代,许多软件都是为硬件编写并控制硬件的,因此很难找到替代方案。

            C 语言的选择是为了在硬件有限的系统上提高性能。我实在看不出还有什么其他的选择在历史上有意义。

            1. 在某些重要的情况下,C 语言在某些方面不如汇编语言方便,这就需要通过愚弄编译器或添加内在函数来解决。最近的一个例子:https://justine.lol/endian.html

              巨大宏是否比 “swap “指令更方便?不,但它是可移植的。

              > 我不太明白历史上还有哪些其他指令是合理的。

              Pascal 在一些地方有不同的选择。尤其是字符串的长度。

              C 语言拒绝定义算术语义。这样一来,只要你不介意不同平台上的不同行为,程序就具有了 “可移植性”。这有利于采用,但不利于理智。直到最近,减法才被定义为二进制。

              16 位 Windows 甚至使用了带有 Pascal 调用约定的 C 语言。http://www.c-jump.com/CIS77/ASM/Procedures/P77_0070_pascal_s…

            2. >在一个注重性能的时代,许多软件都是为硬件编写并控制硬件的,因此很难找到替代方案

              实际上,在性能至上的时代,汇编才是有意义的。C 语言实际上被视为更高级别的语言。

              然而,C 语言的优势在于它是跨平台的,因此你可以使用 C 编译器(Solaris、Windows、BSD、Linux 以及后来的 Mac OSX)编译或非常容易地将相同的代码移植到许多不同的平台上。这是它的优势所在(pascal 也有这样的优势,但它没能存活下来)。

              你可以从今天仍在使用的软件遗产中看到这一点–大量的 gnu 工具、shell、X windows、zlib 库、gcc、openssl,以及最近讨论的从 80 年代就开始使用的 POV Ray。

            3. > C 只是一种方便的汇编语言。

              我不知道你是不是在敷衍我,但这太荒谬了。在使用汇编语言之前,C 语言无疑是最低级的选择之一,但它仍然是一种高级语言,对程序员的机器细节进行了抽象。

              > 在一个注重性能的时代,许多软件都是为硬件编写并控制硬件的,因此很难找到替代方案。

              在那个时代,真正需要关注性能的人使用汇编。当时 C 编译器所做的优化并非一无是处,但与现在相比,它们还相当原始。

        2. 但其实不必如此。我们可以选择任何其他编译为本地语言的语言,包括内存安全语言。

    2. 但不安全代码块是可用的!你应该在必要时使用它们,但仅限于必要时。

      使用爆炸半径非常有限的不安全块并不能抵消你在所有其他代码中获得的所有保证。

      1. 请注意,不安全块的爆炸半径并不受限。一个错误的不安全块可能造成的爆炸是无限的,至少在理论上是这样。(在实践中,不正确的程度可能与影响相关,但 C 语言的未定义行为也是如此)。

        不安全代码块限制了你需要正确处理的数量,但你需要将所有代码块都处理正确。这不是一个爆炸限制器。

        1. 我认为这在技术上是正确的,但在谈到维护者如何处理 rust 中的不安全块时,却有些近视。

          根据定义,UB 的爆炸半径是无限的,您需要在所有不安全块中编写正确的代码,以确保您的应用程序是 100% 内存安全的。这一点毋庸置疑。从这个角度来看,包含一个错误的不安全代码块的 C 应用程序和 Rust 应用程序并没有什么区别。

          不过,两者之间的明显区别在于不安全代码块的可调试性和可审计性要高得多。不安全代码块通常不会太多,而且很容易被抓取。在整个应用程序中,这些(但愿是)极少的几行代码就能得到一定程度的关注和审查,而这是整个 C 代码库的团队所难以承受的。

          编辑:hardy -> hardly(错字)

        2. 是的,它们无法阻止爆炸,但它们限制了炸弹可能出现的地方,这就是它们的价值所在。

          1. 一般来说是的,但安全代码中可能存在逻辑错误,导致不安全代码块做了不该做的事情。在这种情况下,”炸弹 “可能在不安全代码块中,但错误在安全代码中。

            1. > 是的,但安全代码中可能存在逻辑错误,导致不安全代码块做了不该做的事情。

              听起来像是糟糕的设计。通常情况下,你可以限制在如此小的范围内使用不安全程序,而不是验证会导致内存问题的参数范围。检查无效值并引发恐慌。即使会引起恐慌,它仍然是 “内存安全 “的。

              1. 当然,这可能是糟糕的设计。问题的关键在于,Rust 语言本身并不能保证内存安全 bug 会被本地化到不安全的代码块中。如果你的代码具有这种特性,那是因为你以一种严谨的方式编写了它,而不是因为 Rust 强迫你这么写(尽管它可能给予了一些道义上的支持)。

                请允许我强调,我并不是在这里批评 Rust。我只是在指出一个不争的事实,即 Rust 中的不安全块是如何工作的:内存安全漏洞并不能保证被本地化到不安全块中。

            2. 我无法想象在编写一个返回值小于 n 的方法时,不在安全方法的某处验证该约束。

              1. 这只是一个说明问题的简单例子。现实中的错误可能涉及更复杂的逻辑。

                C 代码中缓冲区超限 bug 的普遍存在表明,程序员绝对有可能在计算索引时出错。但在不安全的 Rust 代码中,在计算索引时调用的任何函数中,你仍然很容易受到任何算术错误的影响。

        3. 这当然是正确的。

          其主要价值在于,您只需确保不安全代码块周围的少量代码是安全的,并希望您能提供一个安全的 API 供其余代码使用。

      2. 我的措辞有所不同–它减少了出错时寻找 bug 的空间,但并没有限制爆炸半径–你仍然可以用不安全代码块炸毁安全的 rust 代码(没有别名的规则很难遵守!)。

        但这绝对是一个强大的优势。

    3. > 但不使用不安全代码块,你就无法做到 C 语言所做的一切。Rust 可以为这些问题提供一个全新的视角,但它并不是一个完整的解决方案。

      的确,你需要不安全的代码来做低层次的事情。但如果你必须使用不安全代码,那么 Rust 就不适合你,这是一种误解。Rust 中安全/不安全二分法的意义在于清楚地标记出代码中的哪些部分是不安全的,这样你就可以把所有注意力都集中在审核这些小部分上,并有信心只要把这些部分处理好,其他部分都能正常工作。

    4. > 但如果不使用不安全代码块,就无法做到 C 语言所做的一切。

      这其中有多少是 100% 明确必要的?文件系统代码中的任何内容都必须是不安全的,这有充分的理由吗?

      我怀疑这只是少数地方需要的很小一部分。

      1. 通常,避免复制或移动数据是主要原因。在文件系统中,这一点非常突出。

    5. > 并发问题?

      我不得不承认,虽然我很喜欢 rust,因为它很有道理,有时真的能 “一针见血”。对于任何异步的东西,我觉得它的边缘真的很粗糙。引擎盖下发生的事情并不直观。

      1. 异步 != 并发。

        Rust 的一大亮点就是在类型系统中使用 `Send` 和 `Sync` 特质编码线程安全。

        1. > Async != 并发。

          没错,但任务是共享同一线程的,这没什么问题,但当我们需要将其扩展为真正的异步工作(即非阻塞、触发和准遗忘)时,就很棘手了。我就说这么多。

          1. Rust 的异步体验确实存在很多隐患,我非常赞同。

          1. async == 并发,就像正方形 == 长方形一样–也就是说,这不是一个关联的’==’,因为有很多长方形不是正方形。

      2. Rust async 并不是那么好用。另一方面,对于普通线程并发来说,Rust 是最好的语言之一。它的类型系统可以防止很多并发 bug。”毫不费力的并发 “是该语言应得的标语。

      3. 我真的很讨厌 rust 中的 async。rust 在编译器层面强制你使用互斥确实很棒,但 async 是一种在你的整个项目中蔓延的疾病,它引入了很多复杂性,而我在 C#、Python 或 JS/TS 中都感觉不到这些复杂性。

        1. 从语法上讲,async rust 和 C# 完全一样。都是基于任务的并发。

          现在,附加在函数签名上的生命周期绝对是个问题。

          1. 并非如此。C# 的任务/任务<T> 是基于后台执行的。一旦有事情等待处理,控制权就会返回给调用者。相反,Rust 的 Future<T> 默认基于轮询/步进,有点像 C# 中的 IEnumerable<T>;如果你从不轮询/等待 Future<T>,它就永远不会执行。像 Tokio 这样的执行库允许在后台运行期货,但这不是内置的。

            1. 你认为 async 还能怎么工作?另外,如果你误解了轮询在 rust 中的实际工作方式,它并不是传统 Web 开发意义上的轮询,即每 5 毫秒轮询一次,检查未来是否完成(不过如果你出于某种原因想这么做,也可以这么做)。通常情况下,当数据准备就绪时,系统会 “唤醒 “一些 “唤醒者”,当它们被 “唤醒 “时,就会进行轮询。由于只有在信息就绪时操作系统才会唤醒它们,因此除非有多个捆绑期货,否则真的不需要轮询多次。

            2. 我不想 “实际上 “这个 “实际上”,但我认为你在语法上漏掉了一个词。

              > C# 的 Task/Task<T> 是基于后台执行的。一旦等待完成,控制权就会返回给调用者。

              任何语言中的异步/等待都是在后台进行的。

              Task.Yield()(C#)期间会发生什么?任务被让渡给工作队列中的另一个等待任务。与 Rust 相同。

              > 相反,Rust 的 Future<T> 默认是基于轮询/步进的、

              await 语法抽象了 Future/Stream 轮询。真正的区别在于,Rust 引入了 Future 类型/轮询概念(这是没有标准 async 运行时的结果)。C# 中也有 “此任务是否可以继续 “的概念,只是没有向用户公开,而是由 CLR 处理。

              1. > Task.Yield()

                在 C# 中,你可能永远不会调用 yield。

                1. 这只是个例子。实际上,你是对的。

                2. 尽管细粒度的 C# 任务和聚合到大型任务中的更细粒度的 Rust Futures 之间的实现细节有很大不同,但 C# 中的 yield 还是经常被使用,原因与 Rust 中的相同。

                  C# 异步方法的同步部分将 “内联 “运行。这意味着,如果存在计算昂贵或阻塞的代码,即使调用者没有立即等待,也无法继续执行。例如

                      var ptask = Primes.Calculate(n); // returns Task<ulong[]>
                      // Do other things...right?
                      // Why are we stuck calculating the primes then?
                      Console.WriteLine("Started.");
                  

                  为了让 .Calculate 能够在空闲工作线程的 “其他地方 “继续执行,它必须屈服。

                  如果调用者不控制 .Calculate,最常见的解决方案(可悲的是,经常被滥用)就是简单地执行

                      var task = Task.Run(Primes.Calculate);
                      // Do something else
                      var text = string.Join(',', await task);
                  

                  如果委托的返回签名也是 Task,返回类型将被扁平化–只是一个 Task<T>,但返回的任务仍将是一个代理,一旦原始任务完成,代理也将完成。这成功地处理了行为不良的代码。

                  不过,更好的解决方案是插入 `Task.Yield()` 以允许调用者继续执行,而不是在继续执行长期运行的操作之前被阻塞:

                      var ptask = Primes.Calculate(n); // returns Task<ulong[]>
                      // Successfully prints the message
                      Console.WriteLine("Started.");
                      static async Task<int[]> CalculatePrimes(int n)
                      {
                          await Task.Yield();
                          // Continue execution in a free worker thread
                          // If the caller immediately awaits us, most likely
                          // the caller's thread will end up doing so, as the
                          // continuation will be scheduled in the local queue,
                          // so it is unlikely for the work item to be stolen this
                          // quickly by another worker thread.
                      }
  7. lwn.net 页面下面的一些评论相当不尊重人。

    试想一下,你所参与的开源项目会收到这样的评论:

    “科学是在一次次葬礼中进步的

    1. 这听起来更像是内核中任何重大 API 变动时都会发生的标准争论:你总是会得到关于正确做法的各种意见。更复杂的是,这必然是一个并行的应用程序接口,而且使用的语言并非内核中的每个人都懂,因此关于由谁负责保持同步的讨论就更多了。

    2. 但我宁愿有 50 行核裂变代码必须正确无误,而不是整个软件。

      Rust的危险在于,你为了避免不安全而把自己扭曲成一个脆饼,而事实上你本应该做一个经过特别良好测试和设计的不安全块,它被编译器为你检查的代码所包围。

      我仍然相信,unsafe 的命名选择会产生一些非预期的效果。每当你看到 unsafe 时,都会在心里填上 “相信我 “或 “手动覆盖 “之类的词语。告诉你程序员有理由覆盖借用检查器。如果他们很酷,他们会告诉你为什么他们做的事情确实是安全可靠的代码。

      1. 显然,在某些情况下,使用不安全块是合理的。不过,我认为这种情况可能比人们想象的要少。

        举个例子,过去流行的通用自引用板块 ouroboros 和 self_cell 都存在内存安全漏洞(链接在最后)。(末尾的链接)这两个产品在首次公开发布之前都经过了经验丰富的 rust 开发人员的仔细审核,但最终还是出现了这样的 bug。诚然,问题的部分原因在于这两个板块都在努力提高通用性,因此它们必须在更大范围的情况下保持正确。

        但是,这两个板条箱只有一项工作,都小于 1500 LOC,而且在公开发布之前都经过了仔细审核,以确保它们完成了这一项工作,但它们最终还是出现了一些没有被发现的问题。它们可能仍然存在问题。

        因此,虽然使用 unsafe 来说明你的零数组是有效的 utf-8 字符串而不进行运行时检查可能没有问题,但如果要证明的不变式并非微不足道,而且维护/运行时的开销也不会太高,那么把自己拧成麻花辫可能是个好主意。

        [0]: https://rustsec.org/advisories/RUSTSEC-2023-0042.html [1]: https://rustsec.org/advisories/RUSTSEC-2023-0070.html

        1. 是的,你刚刚重命名了 `unsafe`。

          `unsafe` 是编译器相信你能维护自己的不变式的部分,这是防止 Unsoundness 的必要条件。例如

          – unsafe fn get_unchecked(index) – 编译器相信你会确保 index < length。

          – unsafe fn set_capacity(capacity) – 编译器相信你不会将容量设置为会导致 UB 的值。即使它的代码本质上是设置一个字段–根据 Rust,这是安全的,但可能会使其他保持稳健性的不变式失效。

        1. 我觉得想到这首歌,人们就会比其他时候更愿意进入危险区域。

    3. 我从文章中没有得到这种感觉。

    4. > 内核开发者之间有趣的讨论,也许是 Rust 福音走得太远的迹象?

      LVM:嘿,有一个关于文件系统的 Linux 峰会,会上讨论了 Rust

      HN 评论员:Rust 福音是否走得太远了!?

      我发誓 HN 的评论员有时比小报的头条新闻还夸张。

    5. 他们只是希望真正的内核开发者投降,不要妨碍新的 Rust 代码。如果这些人以编写 C 语言为生,并在 10 年左右的时间里成为维护者,然后将这些想法付诸实践,也许他们就有机会了。但你不能跑到别人的项目中来,认真地指望他们对你所说的一切都点头哈腰,放弃你的顾虑,还一副他们欠你人情的样子。

      不过,我还是很高兴能有这样的对话。

      1. 据我所知,在 Linux 中推动 Rust 应用的大多数人(如果不是全部的话)都是经验丰富的 Linux 贡献者和/或维护者。他们不是 “来到别人的项目 “的局外人。

        1. > 据我所知,在 Linux 中推动 Rust 应用的大多数人(如果不是全部的话)都是经验丰富的 Linux 参与者和/或维护者。他们不是 “来到别人项目 “的局外人。

          在这种情况下不是。这是 Rust 的布道者

          > Almeida 说,他并不是要让 C API 保持静态;他的目标是让文件系统开发人员解释 API 的语义,以便将其编码到 rust 中。

          在我看来,内核团队成员对该提议的回应是合理而成熟的:

          > 此外,当 C 代码发生变化时,Rust 代码也需要跟着变化,但谁来做这项工作呢?

          > 随着 C 代码的演进(至少在初期会比 Rust 代码演进得更快),需要保持两个应用程序接口的同步。

          > 对象的生命周期被编码到 Rust API 中,但 C 语言中并没有与之对应的编码;如果有人改变了一方对象的生命周期,另一方就会出现错误。

          > 将单一的生命周期理解编码到应用程序接口中,意味着其函数将无法用于某些文件系统。

          > Ted Ts’o说,问题的部分原因在于,人们正在努力让 “每个人都转而信仰 “Rust;他说,这不会发生,因为Linux中有50多种不同的文件系统,这些系统不会立即转换。

          > Bottomley说,随着越来越多的语义被编码到绑定中,从同步的角度来看,它们将变得更加脆弱。

          > 但 Ts’o 指出,不是每个人都会学习 Rust;如果他做了修改,他会修复所有受影响的 C 代码,但是,”因为我不懂 Rust,所以我不会修复 Rust 绑定,抱歉”。

          1. > 本例中并非如此。新来的才是 rust 的布道者。FTA:

            通过快速搜索,我发现此人在过去四年中积极参与了内核提交(据我所知大多与 rust 相关,但并非全部),并参与了一些 lkml 的工作和十多年前的一些提交。我不确定这是否使他成为新人,只是想提供更多的背景信息。

          2. 该提议的另一位幕后推手是一位经验丰富的内核开发人员,在 Linux 的 FS 子系统方面拥有超过 14 年的工作经验。

            1. 这就是一个典型的例子:bcachefs 的维护者只是在探索 Rust 布道者提出的想法,目的很明确……突然间,”运动 “就把他收编到了 “提案”(将 Rust 纳入 FS 子系统)中,看来这就足够了。难道 Rust 的支持者们真的不顾一切地要拉拢任何对这一主题感兴趣的人吗?这种毒性对任何人都没有帮助。

              1. 我认为你误解了情况。

                肯特-奥弗斯特里特(Kent Overstreet)是 bcachefs 的维护者,他非常支持 rust。事实上,他曾说过想把 bcachefs 移植到 Rust,而且 bcachefs 的用户空间工具已经用 Rust 写好了。

                他一直在参与邮件列表中关于使用 Rust/ 为 FS 子系统提供 Rust API 的讨论。

                我不知道你把他和谁搞混了,但你把他和别人搞混了。

              2. 我不知道你在说什么。也许第一个提议并不是最好的,所以需要进行一些探索。

                肯特是一个不愿意在 Linux 内核中实施 Rust 的棋子吗?还是说他对在 Linux 内核中采用 Rust 并没有足够的重视,以至于不应考虑他的支持?

                就我所见,肯特非常乐意在 Linux 内核中使用 Rust 实现 bcachefs。Bcachefs-tools 确实使用了它。

                1. 在 IRC 频道中,Kent 对用 Rust 编写 bcachefs 的呼声很高。他们甚至已经开始了一些工作,但决定等到通用抽象准备就绪并合并之后再进行。

                2. > 在让 Rust 在 Linux 内核中实现的游戏中,Kent 是一个不情愿的棋子吗?还是他对在 Linux 内核中实现 Rust 还不够认真,以至于不应该考虑他的支持?

                  都不是。事实上,肯特是这个故事中唯一一个对 Linux 有任何影响力的人;正是因为他,我们才有了这次对话。事实上,这也是 rust 公司的人极力想拉拢他的原因。这种行为模式并不新鲜;事实上,几乎所有的 “革命 “运动都不会放过将(对他们的)良好态度误认为是 “战斗 “中的明确联盟的机会。

                  从理论上讲,我和其他人一样喜欢 Rust,它的代码也很棒,我认为内核开发者的担忧有一点错位,但实际上我不希望 Rust 的人靠近我的机器。Linux 是一个相当老旧的操作系统;在它上面运行需要耗费大量精力,而且只有少数发行版(Debian)能在一开始就把它做好!他们为什么不选择其他操作系统呢?有 Plan 9,有 Fuchsia,Linux 并不是唯一的出路。但他们并不想编写文件系统;相反,对他们来说,唯一重要的目标是掌握最流行操作系统的核心,从而确保长期的成功。

                  要我说,这一切都源于不安全感。

                  1. > 但他们并不想编写文件系统;相反,对他们来说,唯一重要的目标是掌握最流行操作系统的核心,从而确保长期成功。

                    最后一个错词:安全。在对抗远程漏洞的战争中,首先要加固使用最广泛的目标。

                    Rust 之所以存在,是因为让人们可靠、稳定地编写安全的 C++ 已被证明是不可能的。

                    (是的,我知道 C++ 是一种比 C 语言大得多、复杂得多的语言)

                    1. 自上世纪 90 年代以来,IBM 在可信程序转换和内存标记能力方面就具备了充分的安全性,但整个行业、Linux 小气鬼们却对 (1) ECC 内存、(2) 内存标记不屑一顾。现在,我们有了 Arm MTE,这是朝着正确方向迈出的一步,还有 CHERI 能力编程,这在 RISC-V 社区确实取得了进展。

                      如果 Linux 社区是认真的,而不仅仅是廉价和无知,我们首先就不会有这样的对话。向这个社区兜售等同于安全套广告的编程语言,这不是聪明或远大抱负的表现,而是无知和天真的表现。

                    2. 天啊,一个全方位的憎恨者,不仅憎恨试图将 Rust 带入 Linux 的人,还憎恨 Linux。这种优越感一定很有成就感吧。

                      > ECC 内存

                      这完全是英特尔的错。

                    3. 唯一 “憎恨 “Linux的人是Rust社区;事实上,他们真正憎恨的是Linux维护者,憎恨他们多年来允许自己被如此明目张胆地利用。这就好比共产主义者鄙视工人一样。

                      至于我自己: 我尊重 Rust,而不是讨厌它,只是同情/怜悯更广泛的 Rust 运动。如果他们学会停止争夺知名度和认可度,他们就能非常成功地验证借用假设。他们没有信念:如果他们有信念、真心和承诺,他们就不会为旧的代码库费心,而是会做出一些特别的东西。

                      附注:我不需要仇恨来获得优越感,我的本性足以达到这个目的。

        2. 这是一个常见的误解;就像每一场 “革命 “运动一样,它喜欢过度代表自己的支持者基础,并放大被选中的大使。因此,每当做出某种让步或认可时,它就会立即被解读和放大,被视为获得更多支持和/或变革的标志。

          1. rust 是现实世界中的一种东西。

            如今,Windows 和 Android 都在使用用 Rust 编写的有意义的组件。亚马逊的 S3 和 Lambda 就是基于 Rust 构建的。苹果公司正在招聘 Rust 开发人员,他们在这个平台上发布了相关信息 [https://news.ycombinator.com/item?id=40849188]。Dropbox 和 Discord 后端服务都是用 Rust 编写的。Cloudflare 在其基础设施中非常广泛地使用 Rust,这意味着全球互联网流量的很大一部分都要通过用 Rust 编写的路由器和服务器。微软下一代 Surface 产品的 UEFI 固件实现也是用 Rust 编写的。

            你的说法完全错误。与其争论,我建议你稍微研究一下谁在使用 Rust 以及用于什么用途。虽然在很长一段时间内,Rust 还无法与 C 和 C++ 相提并论,但它的使用范围已经足够广泛,你可能还在使用某些直接或间接使用 Rust 的工具或服务。它不是一种 “论坛和业余项目语言”。我刚才提供的清单也绝不完整–Shopify、迪斯尼、Facebook、火狐……以及许多其他公司……也使用 Rust。

            在微软直接反驳你的情况下,你所声称的通过内核工作获得的可信度完全不成立:https://www.thurrott.com/windows/282471/microsoft-is-rewriti…

            “据韦斯顿介绍,微软已经用 Rust 重写了 Windows 内核中的 3.6 万行代码,此外它还为一个概念验证 DirectWrite Core 库编写了另外 15.2 万行代码,与旧的 C++ 代码相比,性能非常出色,没有任何退步。他还呼吁,”现在有一个系统调用,在 Windows 内核中,是用 rust 编写的”。

            不管你有什么经验,都已经跟不上当前的实际情况了。人们不仅有兴趣在这些核心领域使用 Rust,而且已经开始发生了。

            1. 谢谢你雄辩地回应了 GP 的评论… 我自己的想法是,在微软、苹果、Mozilla 和亚马逊积极支持 Rust 的情况下,它肯定不会就这么消失。

              就我个人而言,我只用几个不同的 Rust 框架(Axum 等)做过一些表面工作(API 中间层开发),与 Node、C# 和其他框架相比,它的性能和低开销水平相对较好。我所读到的低级代码也特别容易理解。

              虽然在 Rust 中做全局缓存之类的事情感觉笨拙至极,但许多其他模式用起来却感觉非常不错。我喜欢这种语言本身的语义。不过我还是希望 Java 和 C# 社区中的某些企业模式不要在 Rust 中出现。

          2. ? 我有个朋友早在 2018 年就在用 Rust 编写 “智能电网 “代码了。Rust 代码也已经在 Android 和 Firefox 上运行了。

          3. > Rust 的孩子们需要放弃欺骗其他有抱负的入门级操作系统开发人员,让他们浪费时间学习这门语言。

            在这种情况下,C/C++ 开发人员也不要再欺骗全世界的人,让他们以为 C/C++ 是一种适用于任何安全相关领域的语言,对于商业产品而言,他们要对因内存安全问题导致的数据泄露所造成的经济和社会影响负责。

            (我真的不在乎它是 rust、Java 还是 C#,什么都行)。

          4. 嗯。这是认真的还是敷衍的?还是两者皆有?Linux内核(某些驱动程序)中已经有了 rust 代码。

            1. 莱纳斯的 Rust 战略虽然不道德,却是天才之举。- 不要与运动正面交锋,在无关紧要的地方做出让步。- 没人愿意写驱动?没问题,让他们编写影响最小的驱动程序。我相信,如果 rust 的人真的明白他们到底是如何被使用的,他们就不会去管它,而会去写更有影响力的代码。然而,莱纳斯却完美地执行了这一策略;这种侮辱足够微妙,不会造成重大伤害。

              1. 我很怀疑这是否是一种侮辱,而是为了尽量减少近期的影响,以防出现(而且很可能会出现)对更大生态系统的错误。在 Rust 最初更有意义的地方创建更清晰的分离是很重要的。不仅对 Rust,对 C/C++ 和其他未来语言都是如此。

                文件系统是 Rust 可以大有作为的领域,网络驱动程序也是如此。在与底层硬件和外部系统的交互点上,定义明确的控制就显得尤为重要,而且交互范围也很广。

                如果在十年内,即使不是直接用 Rust 编写的程序,也能在大量应用程序中使用 rustls 来取代 OpenSSL,那我会感到很惊讶。

              2. > 莱纳斯在 Rust 上的策略是天才的,尽管是不道德的。- 不要与运动正面交锋,在无关紧要的地方做出让步。- 没人愿意写驱动?没问题,让他们写影响最小的驱动程序。

                用 “纸牌屋 “的情节线来解释某人破坏某些编程语言在内核中使用的未明示意图,会让人觉得你疯了。

      2. 也许更好的办法是莱纳斯自己设计一个向后兼容的、更安全的 C 语言分叉,然后逐步用它来重写内核。

        他已经编写了世界上最流行的内核和世界上最流行的源代码控制系统,当然这其中也有大量的合作。如果他愿意,我希望他也能写出新的和改进的系统语言。

        1. > 向后兼容且更安全

          选一个。

          安全是通过消除无法证明安全的构造来实现的。此外,Rust 风格的安全性还涉及在源代码中明确添加更多信息,否则这些信息就必须保存在程序员的头脑中(或像 sel4 那样保存在外部):对象生命周期、锁规则(见原文)等。

          最好的结果就是 “先将所有原始代码封装为’不安全’代码,然后逐步移动安全边界”,但即使这样也非常困难。

          1. 如果 C 语言变体定义了 OOB 访问 segfault(或本例中的 panic),就会严格地提高安全性,同时与所有有效的 C 语言代码完全兼容。我并不是在鼓吹这样的 C 语言变体,但您的冷嘲热讽是完全错误的:有足够的空间让 C 语言变得更安全,而无需重新发明轮子。

          2. 我并不主张这样做,但你也可以想象一个 C 语言超集,其中所有的新特性都只适用于 “安全 “代码块,这将是向后兼容的,而且在实践中可能更安全。

        2. 请注意,Linux 内核已经是用 C 语言 “扩展 “编写的,而不是纯 C 语言。

  8. > about the disconnect between the names in the C API and the Rust API, which means that developers cannot look at the C code and know what the equivalent Rust call would be

    Ah, the struggle of legacy naming conventions. I’ve had success in keeping the same name but when I wanted an alternative name I would just wrap the old name with the new name.

    But yeah, naming things is hard.

    1. One of the two major problems in computer science (the other two being concurrency and off-by-one errors).

  9. The disconnect section of the article is a good example of exactly on how not to do the things, and how things can turn out sour if the existing community isn’t taken for the ride.

发表回复

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