编者按:近年来,Rust语言由于其内存安全和性能等优势受到了广泛关注,尤其是Linux内核也准备将其整合到因此,我们特地请来了阿里云工程师苏子斌,为我们介绍如何在Linux内核中集成对Rust的支持。2021年4月14日,LKML邮件群中出现一封主题名为《Rust support》的邮件。这封邮件主要介绍了对内核引入Rust语言支持的一些看法和所做的工作。邮件的发件人是MiguelOjeda,内核中Compilerattributes、.clang-format和其他模块的维护者,也是RustforLinux项目的现任维护者。RustforLinux项目目前得到了谷歌的大力支持,MiguelOjeda目前的全职工作是负责RustforLinux项目。长期以来,内核以C语言和汇编语言作为主要开发语言,并使用包括Python、Perl、shell在内的一些辅助语言进行代码生成、打补丁和检查。他在2016年Linux25岁生日之际接受LinusTorvalds采访时说:这根本不是一个新现象。我们有系统人员使用Modula-2或Ada,我不得不说Rust看起来比这两个灾难要好得多。我不相信Rust用于操作系统内核(尽管系统编程不限于内核),但同时,毫无疑问C有很多局限性。在对Rustsupport的RFC邮件的最新回复中,他甚至说:所以我回复了几个单独的补丁,但总体来说我并不讨厌它。他没有用他独特的回复方式反击,大概是暗暗喜欢吧。目前RustforLinux还是一个独立于上游的项目,主要工作还是集中在驱动接口的开发上,并不是一个完整的项目。项目地址:https://github.com/Rust-for-Linux/linuxWhyisRust在MiguelOjeda的第一封RFC邮件中,他已经提到了“WhyRust”,简单总结一下:safe子集中safe没有undefined子集中的行为,包括内存安全和数据竞争;更严格的类型检测系统可以进一步减少逻辑错误;安全和不安全代码之间的明确区别;更多面向未来的语言:求和类型、模式匹配、泛型、RAII、生命周期、共享和独占引用、模块和可见性等;可扩展的独立标准库;集成开箱即用的工具:文档生成、代码格式化、linter等,所有这些都基于编译器本身。编译支持Rust的内核根据RustforLinux文档,编译支持Rust的内核需要以下步骤:安装rustc编译器。RustforLinux不依赖于cargo,但需要最新的测试版rustc。使用rustup命令安装:rustupdefaultbeta-2021-06-23安装Rust标准库的源代码。RustforLinux将交叉编译Rust的核心库并将这两个库链接到内核映像中。rustupcomponentaddrust-src安装libclang库。libclang被bindgen用作处理C代码的前端。libclang可以从llvm官方主页下载预编译版本。安装bindgen工具,bindgen是一个自动将C接口转换为RustFFI接口的库:cargoinstall--locked--version0.56.0bindgen克隆最新的RustforLinux代码:gitclonehttps://github.com/Rust-for-Linux/linux.git配置内核以启用Rust支持:Kernelhacking->Samplekernelcode->RustsamplesBuild:LIBCLANG_PATH=/path/to/libclangmake-jLLVM=1bzImage这里我们使用clang作为默认内核编译理论上可以使用gcc,但目前还处于早期实验阶段。Rust是如何集成到内核目录结构中的为了将Rust集成到内核中,开发者首先修改Kbuild系统,添加相关配置项来启用/禁用Rust支持。此外,还添加了一些用于编译rs文件的Makefile规则。这些更改分散在内核目录中的不同文件中。由于mangling,Rust生成的目标代码中的符号将比同一C程序生成的符号更长。因此,内核中与符号长度相关的逻辑需要打补丁。开发人员引入了“大内核符号”的概念,以支持Rust生成的目标文件的符号长度,同时保持向前兼容性。其他Rust相关的代码放在rust目录下。在Rust中使用C函数Rust提供FFI(外部函数接口)来支持对C代码的调用。Bindgen是一个官方Rust工具,用于从C函数自动生成RustFFI绑定。内核中的Rust也使用此工具从本机内核C接口生成Rust的FFI绑定。quiet_cmd_bindgen=BINDGEN$@cmd_bindgen=\$(BINDGEN)$<$(shellgrep-v'^\#\|^$$'$(srctree)/rust/bindgen_parameters)\--使用核心--with-derive-默认--ctypes-prefixc_types\--no-debug'.*'\--size_t-is-usize-o$@--$(bindgen_c_flags_final)-DMODULE$(objtree)/rust/bindings_generated.rs:$(srctree)/rust/kernel/bindings_helper.h\$(srctree)/rust/bindgen_parametersFORCE$(callif_changed_dep,bindgen)ABIRust相关代码会从rs单独编译成.o,生成的目标文件是标准的ELF文件。在链接阶段,内核的链接器将Rust生成的目标文件与其他C程序生成的目标文件链接到内核映像文件中。因此,只要Rust生成的目标文件的ABI与C程序的ABI一致,就可以无差别链接(当然,引用的符号必须仍然存在)。Rust的alloc和核心库目前RustforLinux依赖于核心库。核心中定义了基本的Rust数据结构和语言特性,比如大家熟悉的Option<>和Result<>都是由核心库提供的。这个库被交叉编译并直接链接到内核镜像中,这就是启用Rust的内核镜像体积较大的原因。在以后的工作中,这两个库会进一步优化,去掉一些无用的部分,比如浮点运算、Unicode相关的内容、Futures相关的函数等。之前的RustforLinux项目也依赖于Rust的alloc库。RustforLinux定义了自己的GlobalAlloc来管理基本的堆内存分配。它主要用于堆内存分配,使用GFP_KERNEL标志作为默认的内存分配方式。然而,在最新的pullrequest中,社区已经移植和修改了Rust的alloc库,以允许开发人员自定义他们自己的内存分配器,同时试图确保与Rust上游的统一。然而,目前仍不支持使用自定义GFP_标志分配内存,但好消息是该功能正在开发中。“HelloWorld”内核模块使用一个简单的HelloWorld来展示如何用Rust语言编写驱动代码,hello_world.rs:#![no_std]#![feature(allocator_api,global_asm)]usekernel::prelude::*;模块!{type:HelloWorld,name:b"hello_world",author:b"d0u9",description:b"Asimplehelloworldexample",license:b"GPLv2",}structHelloWorld;implKernelModuleforHelloWorld{fninit()->Result
