0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于Rust基础上如何去编写一个Linux内核模块

开关电源芯片 来源:Linux中国 作者:苏子彬 2021-08-27 09:59 次阅读

编者按:近些年来 Rust 语言由于其内存安全性和性能等优势得到了很多关注,尤其是 Linux 内核也在准备将其集成到其中,因此,我们特邀阿里云工程师苏子彬为我们介绍一下如何在 Linux 内核中集成 Rust 支持。

2021 年 4 月 14 号,一封主题名为《Rust support[1]》的邮件出现在 LKML 邮件组中。这封邮件主要介绍了向内核引入 Rust 语言支持的一些看法以及所做的工作。邮件的发送者是 Miguel Ojeda[2],为内核中 Compiler attributes、.clang-format 等多个模块的维护者,也是目前 Rust for Linux 项目的维护者。

Rust for Linux 项目目前得到了 Google 的大力支持[3],Miguel Ojeda[4] 当前的全职工作就是负责 Rust for Linux 项目。

长期以来,内核使用 C 语言和汇编语言作为主要的开发语言,部分辅助语言包括 Python、Perl、shell 被用来进行代码生成、打补丁、检查等工作。2016 年 Linux 25 岁生日时,在对 Linus Torvalds 的一篇 采访[5]中,他就曾表示过:

这根本不是一个新现象。我们有过使用 Modula-2 或 Ada 的系统人员,我不得不说 Rust 看起来比这两个灾难要好得多。

我对 Rust 用于操作系统内核并不信服(虽然系统编程不仅限于内核),但同时,毫无疑问,C 有很多局限性。

最新的对 Rust support[6] 的 RFC 邮件的回复中,他更是说:

所以我对几个个别补丁做了回应,但总体上我不讨厌它。

没有用他特有的回复方式来反击,应该就是暗自喜欢了吧。

目前 Rust for Linux 依然是一个独立于上游的项目,并且主要工作还集中的驱动接口相关的开发上,并非一个完善的项目。

项目地址:https://github.com/Rust-for-Linux/linux

为什么是 Rust

在 Miguel Ojeda[7] 的第一个 RFC 邮件中,他已经提到了 “Why Rust”,简单总结下:

在安全子集safe subset中不存在未定义行为,包括内存安全和数据竞争;

更加严格的类型检测系统能够进一步减少逻辑错误;

明确区分 safe 和 unsafe 代码;

更加面向未来的语言:sum 类型、模式匹配、泛型、RAII、生命周期、共享及专属引用、模块与可见性等等;

可扩展的独立标准库;

集成的开箱可用工具:文档生成、代码格式化、linter 等,这些都基于编译器本身。

编译支持 Rust 的内核

根据 Rust for Linux 文档[8],编译一个包含 Rust 支持的内核需要如下步骤:

安装 rustc 编译器。Rust for Linux 不依赖 cargo,但需要最新的 beta 版本的 rustc。使用 rustup命令安装:

rustup default beta-2021-06-23

安装 Rust 标准库的源码。Rust for Linux 会交叉编译 Rust 的 core 库,并将这两个库链接进内核镜像。

rustup component add rust-src

安装 libclang 库。libclang 被 bindgen 用做前端,用来处理 C 代码。libclang 可以从 llvm 官方主页[9] 下载预编译好的版本。

安装 bindgen 工具,bindgen 是一个自动将 C 接口转为 RustFFI 接口的库:

cargo install --locked --version 0.56.0 bindgen

克隆最新的 Rust for Linux 代码:

git clone https://github.com/Rust-for-Linux/linux.git

配置内核启用 Rust 支持:

Kernel hacking -》 Sample kernel code -》 Rust samples

构建:

LIBCLANG_PATH=/path/to/libclang make -j LLVM=1 bzImage

这里我们使用 clang 作为默认的内核编译器,使用 gcc 理论上是可以的,但还处于 早期实验[10] 阶段。

Rust 是如何集成进内核的

目录结构

为了将 Rust 集成进内核中,开发者首先对 Kbuild 系统进行修改,加入了相关配置项来开启/关闭 Rust 的支持。

此外,为了编译 rs 文件,添加了一些 Makefile 的规则。这些修改分散在内核目录中的不同文件里。

Rust 生成的目标代码中的符号会因为 Mangling 导致其长度超过同样的 C 程序所生成符号的长度,因此,需要对内核的符号长度相关的逻辑进行补丁。开发者引入了 “大内核符号”的概念,用来在保证向前兼容的情况下,支持 Rust 生成的目标文件符号长度。

其他 Rust 相关的代码都被放置在了 rust 目录下。

在 Rust 中使用 C 函数

Rust 提供 FFI(外部函数接口Foreign Function Interface)用来支持对 C 代码的调用。Bindgen[11] 是一个 Rust 官方的工具,用来自动化地从 C 函数中生成 Rust 的 FFI 绑定。内核中的 Rust 也使用该工具从原生的内核 C 接口中生成 Rust 的 FFI 绑定。

quiet_cmd_bindgen = BINDGEN $@ cmd_bindgen = $(BINDGEN) $《 $(shell grep -v ‘^#|^$$’ $(srctree)/rust/bindgen_parameters) --use-core --with-derive-default --ctypes-prefix c_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_parameters FORCE $(call if_changed_dep,bindgen)

ABI

Rust 相关的代码会单独从 rs 编译为 .o,生成的目标文件是标准的 ELF 文件。在链接阶段,内核的链接器将 Rust 生成的目标文件与其他 C 程序生成的目标文件一起链接为内核镜像文件。因此,只要 Rust 生成的目标文件 ABI 与 C 程序的一致,就可以无差别的被链接(当然,被引用的符号还是要存在的)。

Rust 的 alloc 与 core 库

目前 Rust for Linux 依赖于 core 库。在 core 中定义了基本的 Rust 数据结构与语言特性,例如熟悉的 Option《》 和 Result《》 就是 core 库所提供。

这个库被交叉编译后被直接链接进内核镜像文件,这也是导致启用 Rust 的内核镜像文件尺寸较大的原因。在未来的工作中,这两个库会被进一步被优化,去除掉某些无用的部分,例如浮点操作,Unicode 相关的内容,Futures 相关的功能等。

之前的 Rust for Linux 项目还依赖于 Rust 的 alloc 库。Rust for Linux 定义了自己的 GlobalAlloc 用来管理基本的堆内存分配。主要被用来进行堆内存分配,并且使用 GFP_KERNEL 标识作为默认的内存分配模式。

不过在在最新的 拉取请求[12] 中,社区已经将移植并修改了 Rust的 alloc 库,使其能够在尽量保证与 Rust 上游统一的情况下,允许开发者定制自己的内存分配器。不过目前使用自定义的 GFP_ 标识来分配内存依然是不支持的,但好消息是这个功能正在开发中。

“Hello World” 内核模块

用一个简单的 Hello World 来展示如何使用 Rust 语言编写驱动代码,hello_world.rs:

#![no_std]#![feature(allocator_api, global_asm)]use kernel::*;module! { type: HelloWorld, name: b“hello_world”, author: b“d0u9”, description: b“A simple hello world example”, license: b“GPL v2”,}struct HelloWorld;impl KernelModule for HelloWorld { fn init() -》 Result《Self》 { pr_info!(“Hello world from rust!

”); Ok(HelloWorld) }}impl Drop for HelloWorld { fn drop(&mut self) { pr_info!(“Bye world from rust!

”); }}

与之对应的 Makefile:

obj-m := hello_world.o

构建:

make -C /path/to/linux_src M=$(pwd) LLVM=1 modules

之后就和使用普通的内核模块一样,使用 insmod 工具或者 modprobe 工具加载就可以了。在使用体验上是没有区别的。

module! { } 宏

这个宏可以被认为是 Rust 内核模块的入口,因为在其中定义了一个内核模块所需的所有信息,包括:Author、License、Description 等。其中最重要的是 type 字段,在其中需要指定内核模块结构的名字。在这个例子中:

module! { 。。。 type: HelloWorld, 。。。}struct HelloWorld;

module_init() 与 module_exit()

在使用 C 编写的内核模块中,这两个宏定义了模块的入口函数与退出函数。在 Rust 编写的内核模块中,对应的功能由 trait KernelModule 和 trait Drop 来实现。trait KernelModule 中定义 init() 函数,会在模块驱动初始化时被调用;trait Drop 是 Rust 的内置 trait,其中定义的 drop() 函数会在变量生命周期结束时被调用。

编译与链接

所有的内核模块文件会首先被编译成 .o 目标文件,之后由内核链接器将这些 .o 文件和自动生成的模块目标文件 .mod.o 一起链接成为 .ko 文件。这个 .ko 文件符合动态库 ELF 文件格式,能够被内核识别并加载。

其他

完整的介绍 Rust 是如何被集成进内核的文章可以在 我的 Github[13] 上找到,由于写的仓促,可能存在一些不足,还请见谅。

作者:苏子彬,阿里云 PAI 平台开发工程师,主要从事 Linux 系统及驱动的相关开发,曾为 PAI 平台编写 FPGA 加速卡驱动。

参考资料

[1]Rust support:https://lkml.org/lkml/2021/4/14/1023

[2]Miguel Ojeda:https://ojeda.dev/

[3]Google 的大力支持:https://www.zdnet.com/article/rust-in-the-linux-kernel-just-got-a-big-boost-from-google/

[4]Miguel Ojeda:https://ojeda.dev/

[5]采访:https://www.infoworld.com/article/3109150/linux-at-25-linus-torvalds-on-the-evolution-and-future-of-linux.html

[6]Rust support:https://lkml.org/lkml/2021/4/14/1023

[7]Miguel Ojeda:https://ojeda.dev/

[8]Rust for Linux 文档:https://github.com/Rust-forLinux/linux/blob/rust/Documentation/rust/quick-start.rst

[9]llvm 官方主页:https://github.com/llvm/llvm-project/releases

[10]早期实验:https://github.com/Rust-for-Linux/linux/blob/rust/Documentation/rust/quick-start.rst#building

[11]Bindgen:https://github.com/rust-lang/rust-bindgen

[12]拉取请求:https://github.com/Rust-for-Linux/linux/pull/402

[13]我的 Github:https://github.com/d0u9/Linux-Device-Driver-Rust/tree/master/00_Introduction_to_Rust_Module_in_Linux

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Google
    +关注

    关注

    5

    文章

    1762

    浏览量

    57493
  • python
    +关注

    关注

    56

    文章

    4792

    浏览量

    84614
  • C 语言
    +关注

    关注

    0

    文章

    18

    浏览量

    14224
  • Rust
    +关注

    关注

    1

    文章

    228

    浏览量

    6595

原文标题:如何用 Rust 编写一个 Linux 内核模块

文章出处:【微信号:gh_3980db2283cd,微信公众号:开关电源芯片】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    linux 了解内核模块的原理 《Rice linux 学习开发》

    内核模块种没有经过链接,不能独立运行的目标文件,是在内核空间中运行的程序。
    的头像 发表于 07-16 10:08 4657次阅读
    <b class='flag-5'>linux</b> 了解<b class='flag-5'>内核模块</b>的原理 《Rice <b class='flag-5'>linux</b> 学习开发》

    Linux 内核模块工作原理及内核模块编译案例

    内核模块至少包含两函数,模块被加载时执行的初始化函数init_module()和模块被卸载
    发表于 09-23 09:39 2497次阅读
    <b class='flag-5'>Linux</b> <b class='flag-5'>内核模块</b>工作原理及<b class='flag-5'>内核模块</b>编译案例

    详解Linux内核模块编写方法

    Linux 系统为应用程序提供了功能强大且容易扩展的 API,但在某些情况下,这还远远不够。与硬件交互或进行需要访问系统中特权信息的操作时,就需要内核模块
    的头像 发表于 05-11 08:55 3742次阅读

    Linux内核模块间通讯方法

    Linux内核模块间通讯方法非常的多,最便捷的方法莫过于函数或变量符号导出,然后直接调用。默认情况下,模块模块之间、模块
    发表于 06-07 16:23 2523次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核模块</b>间通讯方法

    Linux内核模块程序结构

    Linux设备驱动会以内核模块的形式出现,因此,学会编写Linux内核模块编程是学习Linux
    发表于 05-27 09:36

    高效学习Linux内核——内核模块编译

    内核是世界最大的开源项目之,但是内核是什么,它用于什么?、什么是linux
    发表于 09-24 09:11

    Linux设备驱动开发详解》第4章、Linux内核模块

    Linux设备驱动开发详解》第4章、Linux内核模块
    发表于 10-27 14:15 0次下载
    《<b class='flag-5'>Linux</b>设备驱动开发详解》第4章、<b class='flag-5'>Linux</b><b class='flag-5'>内核模块</b>

    内核模块的原理以及其模块编写

    内核模块是具有独立功能的程序。它可以被单独编译,但是不能单独运行,它的运行必须被链接到内核作为内核一部分在内核空间中运行。
    的头像 发表于 01-02 11:11 4495次阅读
    <b class='flag-5'>内核模块</b>的原理以及其<b class='flag-5'>模块</b><b class='flag-5'>编写</b>

    什么是内核模块?如何编写简单的模块

    内核模块Linux内核向外部提供的插口,其全称为动态可加载内核模块(Loadable Ke
    发表于 08-24 17:15 20次下载

    什么是 Linux 内核模块?

    lsmod 命令能够告诉你当前系统加载了哪些内核模块,以及关于使用它们的些有趣的细节。
    的头像 发表于 08-09 17:01 3238次阅读

    嵌入式LINUX系统内核内核模块调试教程

    本文档的主要内容详细介绍的是嵌入式LINUX系统内核内核模块调试教程。
    发表于 11-06 17:32 21次下载
    嵌入式<b class='flag-5'>LINUX</b>系统<b class='flag-5'>内核</b>和<b class='flag-5'>内核模块</b>调试教程

    如何在Petalinux创建Linux内核模块

    --enable”,能创建Linux内核模块,包括c源代码文件,Makefile,Yocto的bb文件。相关文件放在目录“ project-spec / meta-user / recipes-modules”中
    的头像 发表于 03-02 11:10 4357次阅读

    嵌入式LINUX系统内核内核模块调试

    嵌入式LINUX系统内核内核模块调试(嵌入式开发和硬件开发)-嵌入式LINUX系统内核内核模块
    发表于 07-30 13:55 10次下载
    嵌入式<b class='flag-5'>LINUX</b>系统<b class='flag-5'>内核</b>和<b class='flag-5'>内核模块</b>调试

    Linux内核模块参数传递与sysfs文件系统

    函数传参的内核传参机制,编写内核程序时只要实现传参接口,用户在加载内核模块时即可传入指定参数,使得内核模块更加灵活。
    发表于 06-07 16:23 2115次阅读

    linux驱动程序如何加载进内核

    ,需要了解Linux内核的基本概念和API。以下是些关键概念: 1.1 内核模块Linux内核模块
    的头像 发表于 08-30 15:02 431次阅读