,并提出首次初始提交应“包含尽可能少的功能”的要求,Rust-for-Linux团队对部分开发的驱动以及驱动支持所需的代码进行了修剪后,Linus于2022年10月16日发布.1-rc1开发版本,正式包含Rust相关代码,实现了将Rust语言代码模块加载到内核中所需的最低限度的基础支持和一个小示例模块。
.1 版本的最终发布应该会在 2022 年 12 月中旬左右。让我们提前看一下这个最初的提交,了解 Rust 语言和社区如何创造历史,打破 30 年只能使用 C 语言的规则开发Linux内核;此次提交可能会成为 Rust 语言和 Linux 内核的一个重要里程碑,记录并引领未来 Rust 语言和 Linux 社区的重要发展;
1.初次提交相关链接
1.初次提交申请@/
2.合并内容初次提交
3..1-rc1开发版本发布及下载
二、初稿主要内容解读 1、初稿概要
可以看到,本次提交共包含89个文件、12552个、51个。虽然要求“包含的功能尽可能少”,但内容仍然较多,主要包括开发文档、Rust相关基础和示例代码,以及内核编译和构建系统。配置修改等编译支持工具代码;
由于内容比较大而且很具体,所以我只记录和分享我个人认为重要的部分。毕竟Linux内核开发门槛比较高,参与者也比较少,但是作为一名普通的Rust开发者,或许能够对Rust语言有所发现。不经常使用但特别有用的功能,特别是对于嵌入式 Rust 开发者来说,可能会提供非常好的参考和参考;
建议自行阅读初次提交的相关内容。如果您有疑问或对此感兴趣,请参阅本文。如有不准确之处,敬请谅解;
2. 开发前的安装和准备
/rust/quick-start.rst和//.rst中需要安装rusc 1.62.0和0.56.0版本,以及llvm和;
由于内核代码依赖于编译器的某些不稳定性,因此需要使用特定的编译器版本。较新的版本可能无法用于编译内核 Rust 代码;
还包括Rust语言生态工具,cargo、rust-、rust-src;
2.1.
用于静态绑定内核C语言头文件代码并转换为Rust语言代码,使得Rust代码可以调用C语言实现的函数;
这个绑定转换过程首先识别并解析C代码生成IR中间描述,然后根据提供的参数和Rust语言规则生成相应的Rust代码;
使用环境变量或动态切换不同版本;
2.2.rust-src标准库源码
普通 Rust 开发者可能对这个 rust-src 有点陌生。其实就是平时使用的标准库std的源码。然而,当使用cargo编译Rust程序时,它通常不会被编译。相反,它是使用 Rust 语言编译工具直接链接的。包中包含的二进制std库;
rust-src主要包含三个部分:core crate、alloc crate、std crate;
Core 是 Rust 语言核心实现的一部分,以 crate 的形式出现。例如,它提供了Copy、Send、Sync、Sized、Add、Div、Fn、FnMut特征定义以及与panic相关的函数来实现panic!等。这些元素以lang开头。它以项的形式出现,以便编译器 rustc 可以识别 Rust 语言所需的这些核心元素;在一定程度上,它解耦了语言和语言的实现者编译器rustc,方便扩展语言的核心元素,而不需要直接依赖于编译器的修改;
alloc基于core,为Rust语言提供了内存分配、box、Vec、Rc、Arc等相关的实现;
std往往是在前面的core和alloc的基础上,提供基于win、linux、mac等不同操作系统平台的env、、、mutex、fs、io、net等相关实现,包括程序启动入口和main函数、panic + 打印调用堆栈运行时支持等;
为了方便使用Rust语言进行应用程序开发,将上述core、alloc、std crate进行了封装,生成标准库,为外部应用程序开发提供稳定的接口;
2.3.Rust语言用于内核级开发
有了上述 core 和 alloc 的抽象和隔离,再加上 Rust 语言和编译器提供的强大可配置性,它提供了 #[cfg(**)]、#[]、#[]、# 等内置属性[]、#[]、#[]、#[]等定义;
允许外部自定义这些属性的实现,以实现 Rust 语言对新的运行时环境的支持,例如新的嵌入式内核或 Linux 内核,它们本身提供内存分配、线程调度、引用计数、锁和异常。处理panic等机制和逻辑,并复用现有core中Rust语言的核心通用逻辑并在rust-src中分配crate,这样就可以用“尽可能少的代码”构建新的开发和开发平台对于 Rust 语言。运行环境;
在这样一个新的开发和运行环境中,它还保留了Rust语言本身提供的高级功能,例如所有权、借用检查、内存安全机制等;或许这就是Rust能够超越C++、Java、Go等语言,融入Linux内核开发的原因。这是可能的主要原因之一;
2.4.货物的使用
从逻辑上讲,Rust代码可以使用Rust原生的构建工具cargo开发到Linux内核中。社区里已经有了一些尝试和讨论。最终,我们在最初的提交中看到的是,没有使用编译内核时使用的 Rust 代码。 Cargo,而是直接使用rustc,结合Linux内核本身的编译和构建系统,来编译Rust代码和模块;
不使用cargo的主要原因可能是Linux内核社区的特殊要求,比如简单性原则、不依赖网络自动下载编译代码、相对独立的自我封闭等对代码质量的要求;
但为什么我们需要像前面提到的那样依赖安装 Cargo 呢?这是为了测试Rust代码中的测试代码包括注释中的测试代码。详情请参考初次提交中关于这一点的rust/说明;
2.5.铁锈-
rust - 作为 IDE 支持工具,用于实现 Rust 语言语法高亮、自动补全、代码跳转等功能,默认支持使用 Cargo.toml 来分析其描述的 crate 和依赖项。现在编译内核的Rust代码不需要cargo和Cargo.toml;
为了解决这个开发体验和效率问题,初始代码提供了生成rust-.json的支持,满足rust-所需的标准接口。这样,生成rust-.json后,IDE支持rust-后,就可以阅读和编辑内核开发Rust代码了,就像其他应用项目一样,可以自由方便地用语法高亮、自动高亮来高亮-补全、代码跳转等;
2.6.关于,,
Rust-for-Linux 核心开发人员多次提到使用 Rust 语言的潜在好处,尤其是它的生态工具,例如 、 、 和 。在内核中开发Rust代码也会使用这些工具来带来工程效率和代码质量。改进;
3. 最小示例模块
上面列出了用Rust代码编写的可以在Linux内核中运行的最小模块示例代码;
很简单,它使用提供的板条箱;
然后使用宏定义一个内核模块,作为该模块实例的结构。它实现了 :: Trait 提供的 init 方法来完成实例的初始构造,并使用 !宏在内核日志中打印输出日志,同时分析构建时也会打印日志,以表明是否正常退出;
乍一看,与传统 Rust 应用程序代码几乎没有太大区别,但有一个微妙的区别。向 vec 添加元素使用 '.(72)?'而不是应用程序开发人员经常使用的“.push”。 (72);',原因隐藏在crate的实现中,后面会提到;
4. 板条箱
crate作为未来为内核驱动开发者提供公共接口支持的库,对于应用程序开发者来说类似于标准库std的作用;
目前为了“包含尽可能少的功能”,它提供了上面看到的宏和宏、特征定义、对 Vec 的支持以及前面提到的支持新 Rust 代码运行环境所需的自定义属性。实现等;
该crate是基于rust-src中提供的core crate、内核开发提供的alloc crate以及上述生成的crate来实现的;
它必须包含表明它不会链接标准库内容的属性;
初次提交时并未修改或提供核心 crate 相关代码,而是在编译时添加了 –cfg 条件编译选项,根据使用需求直接编译 rust-src 中的核心 crate,然后提供给其他 crate供使用;
初次提交中提供的alloc crate部分代码是根据标准库std中的alloc crate代码进行修改的。将来,随着接口变得稳定,它可能会与标准库中的alloc crate集成,而不必将其包含在内核代码中。像处理核心 crate 代码一样保留重复的代码;
我们来看看crate提供了哪些自定义属性实现以及其他主要实现;
4.1.宏实现
宏实现会在内核日志中显示 Rust 代码中任何格式化内容的输出。内核日志的输出显示有对应的::接口。然而,为了支持 Rust 中任何格式化的 Rust 内容,内部内核被重写。完成;
添加“%pA”格式指示符。一旦遇到这个指示符,就说明要显示的内容在一个 core::fmt:: 对象指针中。如何从这个core::fmt::内容中检索出来,调用Rust代码中实现的函数;
函数的实现使用了一个自定义对象,该对象实现了 core::fmt::Write Trait 的 write 方法,并提供函数触发器来输出指定 buf 中 core::fmt:: 中的所有格式化内容,从而完成内核日志的输出;
与std标准库类似,可以记录或显示格式化的内容,只不过对象的输出显示目标是内核中提供的内存空间地址;
整个宏的实现充分展示了Rust代码和C代码如何相互调用,以及调用过程中如何保证安全。代码注释中有详细说明。详情请参考初次提交的相关内容;
4.2.宏实现
宏对用户来说相当简洁明了,但需要实现与C语言实现的功能相同的功能,并且可以初始化Rust代码提供的功能。 Rust-for-Linux 开发人员使用 Rust 语言提供的过程宏来实现此目的;
对于普通开发人员来说,实现一个 Rust 语言流程宏通常很困难,但是在初次提交时的宏实现是相当直观的。它不使用传统的 syn 和 quote 包,主要使用 ::,就像格式化段落一样。定制的Rust代码是一样的;
它提供了“C”和函数的定义和实现,并根据是否是内置内核模块等生成不同的代码来初始化指定类型的实例,具体请参考初始中的相关内容提交;
另外,进程宏本身的实现代码是在当前编译环境下编译运行的。使用非内核代码运行的内核空间。它生成的.so供编译时使用的编译器rustc使用。这也包含在初始提交中。提及;
对这一点和程序宏观逻辑感兴趣的朋友可以参考之前的文章,里面提到了它的相关逻辑;
4.3.定制
为了定制由任何 Rust 代码触发的恐慌,板条箱定制了它的实现。它首先打印一条emerg日志,然后根据内核中出现的BUG调用::BUG()进行处理,从而触发内核内部已有的panic。 C语言提供的panic处理机制;
下面的loop{}是为了满足Rust语法需要!,工具支持后可以去掉;
4.4.全局内存分配定制
使用属性定制一个全局类型对象,该对象实现了trait的alloc和方法,分别调用::和::kfree,并对应实现、函数;
这样就可以满足Rust代码中的堆内存分配和释放需求,比如Box的实现、Vec中的自动内存分配和释放等;
4.5.关于ng
需要提到的是,当内核中的Rust代码无法分配内存时,它必须自行处理内存分配错误之后的逻辑;与传统Rust应用程序开发的程序不同,当遇到分配内存失败时,会触发painc等;
这里的区别在于,编译alloc crate时,内核代码中使用了--cfg ng,而标准库std在编译alloc时,并没有使用这个条件编译;
因此,“.(72)?”用于代替 '.push(72);'上面提到的模块示例代码中,这意味着当内存无法分配时,会通过 ?; 直接返回给调用者。
另外,为了保证这个逻辑需求的一致性,内核中实现的对象实际上并没有push方法。 Push方法不能像std标准库中的Vec类型对象一样使用。差异也是由ng控制的;
定制
上面的评论清楚地描述了为什么需要这种定制,并指出为什么恐慌!用于模拟实现;
5..json
定制.json的目的是为了描述内核中的Rust代码将运行在新的目标运行环境中,这样在编译时就可以添加这样的描述,以在编译这些Rust代码时添加一些限制来告知这个目标。有哪些等等;
当前Rust内核代码生成的.json内容如下:
{
"arch": "x86_64",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
"features": "-3dnow,-3dnowa,-mmx,+soft-float,+retpoline-external-thunk",
"llvm-target": "x86_64-linux-gnu",
"target-pointer-width": "64",
"emit-debug-gdb-scripts": false,
"frame-pointer": "may-omit",
"stack-probes": {"kind": "none"}
}
另外需要提一下的是,这个.json没有=”64”、=”ptr”、=”ptr”等相关描述,因此当前内核Rust代码无法使用Rc、Arc等相关接口。 ;
6.条件编译
从上面可以看到编译alloc crate时添加了–cfg no_rc和–cfg、–cfg ng等。结合前面提到的一些内容,你就会知道为什么要添加这些cfg了;
另外,为了方便后续内核开发,传统内核中配置的各种选项在初次提交时生成,成为Rust代码的条件编译选项,这样可以方便地针对Rust中的不同选项实现不同的逻辑;
#[cfg(MODULE)] // as Module
#[cfg(CONFIG_X)] // Enabled (`y` or `m`)
#[cfg(CONFIG_X="y")] // Enabled as a built-in (`y`)
#[cfg(CONFIG_X="m")] // Enabled as a module (`m`)
#[cfg(not(CONFIG_X))] // Disabled
如果你不熟悉上面提到的属性的含义和语法,比如条件编译cfg、、、、、等,
请参考Rust内置索引
7.,,
对于Rust语言中的关键字及其背后的处理逻辑的理解,人们常常感到困惑,或者过于理想化,或者过于绝对;例如:他们不相信Rust语言和生态能够达到所声称的安全性,当他们看到这段代码时就认为这段代码可能存在问题、Bug、不可靠,造成不信任;如果你还没有看过代码,你就认为它是100%安全的;
初次提交有相关说明,包括如何使用、文档注释和代码行注释,还有示例和原因说明,非常清晰简洁;
代码块需要这些注释的原因是编译器在遇到块内的代码时不会执行静态安全检查。是否安全由开发商自行保证;
这在某种程度上反映出,处理代码安全问题是一个系统问题,甚至是一个哲学问题,正如文中提到的,现实世界的代码不存在绝对的安全;
为了实现相当高水平的代码安全性,需要额外的加固。可以使用编译器等工具进行非手动自动扫描和诊断,提前发现代码调用之间的约定是否得到遵守。即使不遵守合同,也有公开透明的文件支持或限制。 ,以便快速做出后续响应,系统变得更加安全可靠;但当有些代码无法依赖工具时,需要人工确认并保证其安全性、可靠性;
或许Rust语言和生态工具正是这种代码安全理念的忠实践行者,才使得它能够得到Linus和内核社区开发者的认可,从而做出了这次初次提交;
3.初次提交试用经历
.0.4和vim开发环境中验证步骤如下:
1.下载linux-6.1-rc1版本
$wget https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/snapshot/linux-6.1-rc1.tar.gz
$tar xvf linux-6.1-rc1.tar.gz -C .
2.下载并确认开发依赖工具
// 如果出现错误,请参考quick-start.rst
$cd linux-6.1-rc1
$make LLVM=1 rustavailable
3.配置Rust和Rust
// 先选择Rust,然后在菜单中找到Rust和Rust
$make LLVM=1 menuconfig
// a.选中Kernel hacking-->Compile-time checks and Compiler options菜单,取消选中Generate BTF typeinfo,退出返回主菜单;
// b.选中General Setup,选中Rust Support,退出返回主菜单;
// c.选中Kernel Hacking-->Sample kernel code-->Rust Samples菜单,选中Mininal,保存成.config退出配置;
4.编译Rust模块和内核并验证相关功能
// 首先试验一下Rust示例模块是否能编译正常
$make LLVM=1 samples/rust/rust_minimal.o V=1
// 检查是否能在rust/doc/下生成文档
$make LLVM=1 rustdoc
// 生成rust-project.json
$make rust-analyzer
// 验证标识符Module是否可跳转
$vim samples/rust/rust_minimal.rs
//生成完整内核
$make LLVM=1 -j6
//编译完成后,使用qemu来启动新编的内核;
//检查其是否正常启动并输出Rust minimal sample相关日志;
四、总结与展望
随着.1-rc1开发版本的发布,Rust首次提交到Linux内核已经完成,进一步验证了Rust语言以安全性和高性能着称的特点。 Rust语言和生态必将受到更多关注或挑战;
但由于Rust语言本身的复杂性和学习曲线,整体生态尚未大规模进入人们的日常工作,市场上对Rust开发者的需求并不大;
然而,有了这个初次提交的小突破,前途是光明的,但道路却是曲折的。我们仍然需要继续努力,利用和应用 Rust 语言和社区的现有成果;
随着越来越多的开发者参与Rust语言,相信在不久的将来,使用Rust语言实现的Linux驱动和应用服务一定会进入人们的日常工作和生活;
参考
初步了解 6.1 中的 Rust
扫一扫在手机端查看
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。