├── README.md ├── blk-mq.md ├── docs ├── Rust for Linux 开发技术分享.pdf ├── Rust for Linux 驱动模块开发.pdf ├── readme.md └── 网卡框架.pdf ├── exercise ├── exercise1.md ├── exercise2.md ├── exercise3.md └── exercise4.md ├── initrd.md ├── net.md ├── plan.md └── 优秀文档参考 ├── PuzzleFS-下一代Linux容器文件系统之源码分析报告-邝劲强.md ├── Rust_for_Linux驱动模块开发报告-周睿.md ├── Rust_for_Linux驱动模块开发报告-杨凯.md ├── Rust_for_Linux驱动模块开发报告-江昊.md ├── Rust_for_Linux驱动模块开发报告-童浩轩.md ├── Rust_for_Linux驱动模块开发报告-邝劲强.md ├── Rust实现内存块ramdisk设备驱动-江昊.md ├── lcx-Rust-for-Linux-Virtio驱动模块开发.md ├── lcx-Rust-for-Linux-net-driver.md ├── nnull-block-driver分析-童浩轩.md ├── readme.md └── ye-junzhe-叶钧喆-Rust_for_Linux驱动模块开发.md /README.md: -------------------------------------------------------------------------------- 1 | # Rust for Linux 驱动模块开发 2 | 3 | ## 课题目标 4 | 学生选修该课题,可以学习到如何一步一步地开发一个Rust for Linux内核驱动;逐步积累并形成学习开发文档;进而有机会为rcore-os Rust操作系统社区以及Linux社区的发展,通过编写Rust驱动等方式贡献一份自己的力量。 5 | 6 | 具体[课题计划](plan.md) 7 | 8 | ## 相关资料参考 9 | Rust for Linux社区仓库: https://github.com/Rust-for-Linux 10 | 11 | Linux之Rust api接口适配e1000网卡驱动: https://github.com/fujita/linux.git 12 |
Rust e1000网卡驱动模块,支持了`e1000`与`e1000e`网卡: https://github.com/elliott10/e1000-driver.git 13 | 14 | Rust实现的VirtIO驱动: https://github.com/rcore-os/virtio-drivers.git 15 | 16 | RISCV物理开发板平台:华山派: https://github.com/sophgo/sophpi-huashan.git 17 | 18 | 内核编译blog参考:https://github.com/rcore-os/blog/blob/master/source/_posts/%E7%BC%96%E8%AF%91Linux%E5%86%85%E6%A0%B8%E5%BC%80%E5%90%AFRust%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81.md 19 | 20 | ## Rust for Linux 设备驱动实现案例 21 | 22 | ### Linux上的Rust驱动程序 23 | 24 | * Western Digital 首席工程师 Andreas Hindborg 在2022 Linux Plumbers Summit上 25 | 展示了用户可以使用 Rust 编写一流的驱动程序,即适用于 Linux 的 SSD NVM-Express (NVMe) 驱动程序。
26 | Linux (PCI) NVMe driver in Rust
27 | https://lpc.events/event/16/contributions/1180/attachments/1017/1961/deck.pdf 28 | https://github.com/metaspace/linux/commits/nvme 29 | 30 | * Asahi Lina Linux组织开发的Apple's M1 GPU驱动
31 | https://github.com/AsahiLinux/linux/commits/gpu/rust-wip 32 | 33 | * 对async异步内核编程的支持,当前状态已经用于允许异步网络TCP连接支持;
34 | https://github.com/Rust-for-Linux/linux/commit/08358e5a955f662daf154a89f3ba43a3f18228a2 35 | 36 | * Google Android 团队正试验移植Rust Binder IPC 驱动,其它 Android 厂商也表示有兴趣开发 Rust 驱动
37 | https://github.com/Rust-for-Linux/linux/commit/d8dcaf80c9535056ea541d8dd876edad52c538a9 38 | 39 | * 9p fs server 方便地从虚拟主机与Host机进行文件系统访问
40 | https://kangrejos.com/Async%20Rust%20and%209p%20server.pdf 41 | https://github.com/wedsonaf/linux/commits/9p 42 | 43 | * Samsung - Null块驱动:Null Block Driver
44 | https://github.com/metaspace/linux/tree/null_block-RFC 45 | 46 | * Cisco - 容器文件系统: PuzzleFS
47 | https://github.com/project-machine/puzzlefs 48 | 49 | * Microsoft TarFS
50 | https://github.com/wedsonaf/linux/commits/vfs 51 | 52 | * Collabora - Video Codec
53 | https://lore.kernel.org/rust-for-linux/20230406215615.122099-1-daniel.almeida@collabora.com/ 54 | -------------------------------------------------------------------------------- /blk-mq.md: -------------------------------------------------------------------------------- 1 | # Linux blk-mq介绍 2 | 3 | Linux blk-mq是Linux块层的一个新框架,它可以让高性能的存储设备通过同时向块设备发送和处理IO请求,从而实现高达数百万的每秒输入/输出操作(IOPS),利用了现代存储设备的并行性。 4 | 5 | ## blk-mq的主要接口 6 | 7 | blk-mq的主要接口有以下几个: 8 | 9 | - `blk_mq_alloc_tag_set`:分配一个tag set结构,用于存储blk-mq的配置信息,例如硬件队列的数量,软件队列的数量,请求的大小,驱动程序的回调函数等。 10 | - `blk_mq_init_queue`:根据tag set结构,初始化一个请求队列(request queue),用于管理块设备的IO请求。 11 | - `blk_mq_alloc_disk`:根据请求队列,分配一个磁盘结构(gendisk),用于表示块设备的基本信息,例如设备号,扇区大小,容量等。 12 | - `blk_mq_free_tag_set`:释放一个tag set结构,以及与之关联的资源,例如tag位图,软件队列,硬件队列等。 13 | - `blk_mq_free_disk`:释放一个磁盘结构,以及与之关联的资源,例如请求队列,设备节点等。 14 | - `blk_mq_start_request`:开始处理一个IO请求,将其从软件队列移动到硬件队列,或者直接发送到硬件。 15 | - `blk_mq_complete_request`:完成一个IO请求,将其从硬件队列移除,或者直接返回给用户空间,同时释放tag和资源。 16 | - `blk_mq_map_queue`:根据CPU编号,返回一个对应的硬件队列,用于将IO请求发送到合适的硬件队列。 17 | - `blk_mq_tag_to_rq`:根据tag编号,返回一个对应的IO请求,用于在硬件队列中查找或操作IO请求。 18 | 19 | ## blk-mq的代码示例 20 | 21 | 下面是一个使用blk-mq的简单的代码示例,它实现了一个虚拟的块设备,可以在内存中读写数据。 22 | 23 | ```c 24 | #include 25 | #include 26 | #include 27 | 28 | #define VBLK_NAME "vblk" 29 | #define VBLK_SECTOR_SIZE 512 30 | #define VBLK_CAPACITY (1024 * 1024 * 1024) // 1 GB 31 | #define VBLK_MAX_REQUESTS 256 32 | #define VBLK_HW_QUEUES 4 33 | #define VBLK_SW_QUEUES 8 34 | 35 | static struct vblk_device { 36 | struct blk_mq_tag_set tag_set; 37 | struct request_queue *queue; 38 | struct gendisk *disk; 39 | u8 *data; 40 | } vblk_dev; 41 | 42 | static int vblk_open(struct block_device *bdev, fmode_t mode) 43 | { 44 | return 0; 45 | } 46 | 47 | static void vblk_release(struct gendisk *disk, fmode_t mode) 48 | { 49 | } 50 | 51 | static const struct block_device_operations vblk_fops = { 52 | .owner = THIS_MODULE, 53 | .open = vblk_open, 54 | .release = vblk_release, 55 | }; 56 | 57 | static blk_status_t vblk_queue_rq(struct blk_mq_hw_ctx *hctx, 58 | const struct blk_mq_queue_data *bd) 59 | { 60 | struct request *rq = bd->rq; 61 | struct bio_vec bvec; 62 | struct req_iterator iter; 63 | u64 offset; 64 | u32 len; 65 | u8 *buf; 66 | 67 | blk_mq_start_request(rq); 68 | 69 | offset = blk_rq_pos(rq) * VBLK_SECTOR_SIZE; 70 | 71 | // rq_for_each_segment(bvec, rq, iter) { 72 | // len = bvec.bv_len; 73 | // buf = page_address(bvec.bv_page) + bvec.bv_offset; 74 | // if (rq_data_dir(rq) == READ) { 75 | // memcpy(buf, vblk_dev.data + offset, len); 76 | // } else { 77 | // memcpy(vblk_dev.data + offset, buf, len); 78 | // } 79 | // offset += len; 80 | // } 81 | 82 | blk_mq_complete_request(rq); 83 | 84 | return BLK_STS_OK; 85 | } 86 | 87 | // 定义一个自定义的数据结构,用于存储请求相关的信息 88 | struct vblk_request { 89 | struct request *rq; // 指向blk-mq的请求结构 90 | void *data; // 指向自定义的数据缓冲区 91 | // 其他字段 92 | }; 93 | 94 | // 实现.complete接口的函数 95 | static void vblk_complete(struct request *rq, blk_status_t status) 96 | { 97 | // 获取自定义的数据结构 98 | struct vblk_request *vreq = blk_mq_rq_to_pdu(rq); 99 | 100 | // 释放自定义的数据缓冲区 101 | kfree(vreq->data); 102 | 103 | // 调用blk-mq的默认完成函数 104 | blk_mq_end_request(rq, status); 105 | } 106 | 107 | static struct blk_mq_ops vblk_mq_ops = { 108 | .queue_rq = vblk_queue_rq, 109 | .complete = vblk_complete, 110 | }; 111 | 112 | static int __init vblk_init(void) 113 | { 114 | int ret; 115 | 116 | // allocate tag set 117 | vblk_dev.tag_set.ops = &vblk_mq_ops; 118 | vblk_dev.tag_set.nr_hw_queues = VBLK_HW_QUEUES; 119 | vblk_dev.tag_set.queue_depth = VBLK_MAX_REQUESTS; 120 | vblk_dev.tag_set.numa_node = NUMA_NO_NODE; 121 | vblk_dev.tag_set.cmd_size = 0; 122 | vblk_dev.tag_set.flags = BLK_MQ_F_SHOULD_MERGE; 123 | vblk_dev.tag_set.driver_data = &vblk_dev; 124 | 125 | ret = blk_mq_alloc_tag_set(&vblk_dev.tag_set); 126 | if (ret) { 127 | pr_err("Failed to allocate tag set\n"); 128 | return ret; 129 | } 130 | 131 | // allocate request queue 132 | vblk_dev.queue = blk_mq_init_queue(&vblk_dev.tag_set); 133 | if (IS_ERR(vblk_dev.queue)) { 134 | pr_err("Failed to allocate request queue\n"); 135 | ret = PTR_ERR(vblk_dev.queue); 136 | goto free_tag_set; 137 | } 138 | 139 | // allocate disk 140 | vblk_dev.disk = blk_mq_alloc_disk(&vblk_dev.tag_set, NULL); 141 | if (IS_ERR(vblk_dev.disk)) { 142 | pr_err("Failed to allocate disk\n"); 143 | ret = PTR_ERR(vblk_dev.disk); 144 | goto free_queue; 145 | } 146 | 147 | // set disk parameters 148 | vblk_dev.disk->major = 0; 149 | vblk_dev.disk->first_minor = 0; 150 | vblk_dev.disk->fops = &vblk_fops; 151 | vblk_dev.disk->private_data = &vblk_dev; 152 | vblk_dev.disk->queue = vblk_dev.queue; 153 | vblk_dev.disk->flags = GENHD_FL_EXT_DEVT; 154 | sprintf(vblk_dev.disk->disk_name, VBLK_NAME); 155 | set_capacity(vblk_dev.disk, VBLK_CAPACITY / VBLK_SECTOR_SIZE); 156 | 157 | // allocate data buffer 158 | vblk_dev.data = vzalloc(VBLK_CAPACITY); 159 | if (!vblk_dev.data) { 160 | pr_err("Failed to allocate data buffer\n"); 161 | ret = -ENOMEM; 162 | goto free_disk; 163 | } 164 | 165 | // add disk 166 | add_disk(vblk_dev.disk); 167 | 168 | pr_info("vblk device initialized\n"); 169 | 170 | return 0; 171 | 172 | free_disk: 173 | blk_mq_free_disk(vblk_dev.disk); 174 | free_queue: 175 | blk_cleanup_queue(vblk_dev.queue); 176 | free_tag_set: 177 | blk_mq_free_tag_set(&vblk_dev.tag_set); 178 | return ret; 179 | } 180 | 181 | static void __exit vblk_exit(void) 182 | { 183 | // remove disk 184 | del_gendisk(vblk_dev.disk); 185 | 186 | // free data buffer 187 | vfree(vblk_dev.data); 188 | 189 | // free disk 190 | blk_mq_free_disk(vblk_dev.disk); 191 | 192 | // free request queue 193 | blk_cleanup_queue(vblk_dev.queue); 194 | 195 | // free tag set 196 | blk_mq_free_tag_set(&vblk_dev.tag_set); 197 | 198 | pr_info("vblk device exited\n"); 199 | } 200 | 201 | module_init(vblk_init); 202 | module_exit(vblk_exit); 203 | 204 | MODULE_LICENSE("GPL"); 205 | MODULE_AUTHOR("AAA"); 206 | MODULE_DESCRIPTION("A simple example of using blk-mq"); 207 | ``` 208 | 209 | ## blk-mq的示意图 -------------------------------------------------------------------------------- /docs/Rust for Linux 开发技术分享.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rust-for-linux/a5b3a3cba141e07f4ca57b417380d6a5eb5767d9/docs/Rust for Linux 开发技术分享.pdf -------------------------------------------------------------------------------- /docs/Rust for Linux 驱动模块开发.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rust-for-linux/a5b3a3cba141e07f4ca57b417380d6a5eb5767d9/docs/Rust for Linux 驱动模块开发.pdf -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | ## PPT docs 2 | -------------------------------------------------------------------------------- /docs/网卡框架.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcore-os/rust-for-linux/a5b3a3cba141e07f4ca57b417380d6a5eb5767d9/docs/网卡框架.pdf -------------------------------------------------------------------------------- /exercise/exercise1.md: -------------------------------------------------------------------------------- 1 | # Exercise 1 2 | ## 练习1: Rust for Linux 仓库源码获取,编译环境部署,及Rust内核编译。之后尝试在模拟器Qemu上运行起来。 3 | 4 | 1. Get source code 5 | we have collected all the source code for you in the homework repository(as mentioned before). However, you can also get source code from Rust for Linux official repository: 6 | ``` 7 | git clone https://github.com/Rust-for-Linux/linux -b rust-dev 8 | ``` 9 | OR the rust version source code by fujita from the following repository: 10 | ``` 11 | git clone https://github.com/fujita/linux.git -b rust-e1000 12 | ``` 13 | Enjoy it! 14 | 15 | 2. Preparation 16 | First you need to install some packages(if the warning or error tell you need more packages, just intall them all): 17 | ``` 18 | sudo apt-get -y install \ 19 | binutils build-essential libtool texinfo \ 20 | gzip zip unzip patchutils curl git \ 21 | make cmake ninja-build automake bison flex gperf \ 22 | grep sed gawk bc \ 23 | zlib1g-dev libexpat1-dev libmpc-dev \ 24 | libglib2.0-dev libfdt-dev libpixman-1-dev libelf-dev libssl-dev 25 | ``` 26 | 27 | Besides a Rust environment is necessary, but I think you have done this in stage1. 28 | And install the LLVM: 29 | ``` 30 | apt-get install clang-format clang-tidy clang-tools clang clangd libc++-dev libc++1 libc++abi-dev libc++abi1 libclang-dev libclang1 liblldb-dev libllvm-ocaml-dev libomp-dev libomp5 lld lldb llvm-dev llvm-runtime llvm python3-clang 31 | ``` 32 | 33 | Then try to make the kernel support the Rust language: 34 | ``` 35 | cd linux 36 | sudo apt install clang llvm 37 | 38 | make LLVM=1 rustavailable 39 | rustup override set $(scripts/min-tool-version.sh rustc) 40 | rustup component add rust-src 41 | cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli 42 | ``` 43 | 44 | 3. Build the kernel tree and the image 45 | As we introduced in our lesson, you have to 46 | ``` 47 | make ARCH=arm64 LLVM=1 O=build defconfig 48 | 49 | make ARCH=arm64 LLVM=1 O=build menuconfig 50 | #set the following config to yes 51 | General setup 52 | ---> [*] Rust support 53 | 54 | make ARCH=arm64 LLVM=1 -j8 55 | ``` 56 | After that, under your linux folder you can find a file named vmlinux, which means your building is success. Congratulations! 57 | 58 | Remember, when you building the kernel, some errors might happen, which is very normal and how to fix the bug or just ignore it, is a part of your homework. 59 | For example, some modules might show an error called "modpost \"xxx\" [xxx.ko] underfined!" on my assistant's environment, but that's just an error of modules and have no affection on the kernel image, so just ignore it. 60 | If you have any errors during the process of building the kernel tree, we do not restrict you to use any method to solve the problem, including using google, querying stackoverflow, asking chatgpt, or directly consulting the teaching assistant, etc. 61 | Testcase and Grade 62 | This part accounting for 60% of the score. 63 | 64 | No testcase. 65 | We have no way to test whether you have made a correct environment and built your kernel tree and image successfully on your computer, so you have to submit a report and show the runtime screenshot in your report. 66 | If errors occur during configuration, we want you to be able to document them. If it doesn't work out in any way, you can get 60 points for reporting your mistake. 67 | -------------------------------------------------------------------------------- /exercise/exercise2.md: -------------------------------------------------------------------------------- 1 | # Exercise 2 2 | 3 | ## 练习2: 自定义编写Rust内核驱动模块 4 | 5 | We will only do in-tree compilation. In this part, we need to build a minimal kernel module 6 | 7 | 1. Goto samples/rust directory in Linux folder 8 | 2. Add a rust_helloworld.rs in this directory 9 | 3. Add these content to `rust_helloworld.rs` 10 | 11 | ``` 12 | // SPDX-License-Identifier: GPL-2.0 13 | //! Rust minimal sample. 14 | 15 | use kernel::prelude::*; 16 | 17 | module! { 18 | type: RustHelloWorld, 19 | name: "rust_helloworld", 20 | author: "whocare", 21 | description: "hello world module in rust", 22 | license: "GPL", 23 | } 24 | 25 | struct RustHelloWorld {} 26 | 27 | impl kernel::Module for RustHelloWorld { 28 | fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result { 29 | pr_info!("Hello World from Rust module"); 30 | Ok(RustHelloWorld {}) 31 | } 32 | } 33 | ``` 34 | 4. Edit Makefile and Kconfig within samples/rust directory 35 | 36 | ``` 37 | obj-$(CONFIG_SAMPLE_RUST_FS) += rust_fs.o 38 | obj-$(CONFIG_SAMPLE_RUST_SELFTESTS) += rust_selftests.o 39 | // ++++++++++++++++ add here 40 | obj-$(CONFIG_SAMPLE_RUST_HELLOWORLD) += rust_helloworld.o 41 | // ++++++++++++++++ 42 | subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs 43 | 44 | config SAMPLE_RUST_MINIMAL 45 | tristate "Minimal" 46 | help 47 | This option builds the Rust minimal module sample. 48 | 49 | To compile this as a module, choose M here: 50 | the module will be called rust_minimal. 51 | 52 | If unsure, say N. 53 | // +++++++++++++++ add here 54 | config SAMPLE_RUST_HELLOWORLD 55 | tristate "Print Helloworld in Rust" 56 | help 57 | This option builds the Rust HelloWorld module sample. 58 | 59 | To compile this as a module, choose M here: 60 | the module will be called rust_helloworld. 61 | 62 | If unsure, say N. 63 | // +++++++++++++++ 64 | config SAMPLE_RUST_PRINT 65 | tristate "Printing macros" 66 | help 67 | This option builds the Rust printing macros sample. 68 | 69 | To compile this as a module, choose M here: 70 | the module will be called rust_print. 71 | 72 | If unsure, say N. 73 | ``` 74 | 75 | 5. run make LLVM=1 menuconfig 76 | 6. You can enter `/` and search for the location of the module you want to find. Now make your module compile within kernel binary. 77 | ``` 78 | Kernel hacking 79 | ---> Sample Kernel code 80 | ---> Rust samples 81 | ---> <*>Print Helloworld in Rust (NEW) 82 | ``` 83 | Remember that in the previous option you need to press Y, in the last option you need to press M. 84 | Then exit menuconfig and save your change. 85 | 86 | 7. Compile kernel again make LLVM=1 and you should see no Error if your previous steps are right. 87 | 88 | Testcase and Grade 89 | This part accounting for 10% of the score. 90 | 91 | If everything is ok, you can see a rust_helloworld.ko in the samples/rust directory. 92 | Then copy it to the src_e1000/rootfs and then run build_image.sh 93 | 94 | After you enter the Linux shell, use 'ls' command, you can find the rust_helloworld.ko. 95 | Then use insmod to start this module. 96 | Finally, you can see the "Hello World from Rust module" 97 | 98 | ### A Small TTY Driver from <> 99 | 100 | -------------------------------------------------------------------------------- /exercise/exercise3.md: -------------------------------------------------------------------------------- 1 | # 基于Qemu模拟器上的e1000网卡驱动框架,填充驱动初始化函数 2 | 获取e1000-driver代码 https://github.com/yuoo655/e1000-driver 3 | 4 | 相应的linux仓库 https://github.com/fujita/linux/tree/rust-e1000 5 | 6 | 完成Exercise3 Checkpoint 1-5 7 | 8 | # 自定义一个linux内核函数, 并在rust模块中调用它. 9 | 10 | # step1 11 | 在linux include目录下创建一个头文件xx.h, 在里面写好自定义的一个函数, 打印一行log出来. 12 | 13 | # step2 14 | 在linux rust/bindings/bindings_helper.h中把step1中的头文件引用进来. 在rust/helpers.c中参考其中已有例子,生成rust_helper_为前缀的函数. 15 | 16 | # step3 17 | 在Rust网卡驱动模块中模块中引入kernel::bindings::*; 通过bindings::函数名方式调用. 18 | 19 | 作业解答之e1000驱动完整实现: 20 | Rust e1000网卡驱动模块: https://github.com/elliott10/e1000-driver.git 21 | -------------------------------------------------------------------------------- /exercise/exercise4.md: -------------------------------------------------------------------------------- 1 | # 完成e1000网卡收包和发包函数 2 | 完成Exercise4 Checkpoint 1-2 3 | 4 | [提示](./net.md) -------------------------------------------------------------------------------- /initrd.md: -------------------------------------------------------------------------------- 1 | 使用 busybox 制作内存文件系统 initramfs: 2 | 3 | 1. 下载解压 busybox 并配置环境变量 4 | 5 | ```shell 6 | wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2 7 | tar -xf busybox-1.35.0.tar.bz2 8 | cd busybox-1.35.0 9 | # 配置环境变量 10 | export ARCH=arm64 11 | export CROSS_COMPILE=aarch64-linux-gnu- 12 | ``` 13 | 14 | 2. 配置编译内核的参数 15 | 16 | ```shell 17 | # busybox-1.35.0目录下 18 | make menuconfig 19 | # 修改配置,选中如下项目,静态编译 20 | # Settings -> Build Options -> [*] Build static binary (no share libs) 21 | 22 | 23 | 3. 编译 24 | 25 | ```shell 26 | make -j `nproc` 27 | ``` 28 | 29 | 4. 安装 30 | 31 | 安装前 busybox-1.35.0 目录下的文件如下图所示 32 | 33 | 34 | 35 | 输入如下命令 36 | 37 | ```shell 38 | make install 39 | ``` 40 | 41 | 42 | 安装后目录下生成了\_install 目录 43 | 44 | 5. 在\_install 目录下创建后续所需的文件和目录 45 | 46 | ```shell 47 | cd _install 48 | mkdir proc sys dev tmp 49 | touch init 50 | chmod +x init 51 | ``` 52 | 53 | 6. 用任意的文本编辑器编辑 init 文件内容如下 54 | 55 | ```shell 56 | #!/bin/sh 57 | 58 | # 挂载一些必要的文件系统 59 | mount -t proc none /proc 60 | mount -t sysfs none /sys 61 | mount -t tmpfs none /tmp 62 | mount -t devtmpfs none /dev 63 | 64 | 65 | # 停留在控制台 66 | exec /bin/sh 67 | ``` 68 | 69 | 7. 用 busybox 制作 initramfs 文件 70 | 71 | ```shell 72 | # _install目录 73 | find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz 74 | ``` 75 | 76 | 执行成功后可在 busybox-1.35.0 目录下找到 initramfs.cpio.gz 文件 77 | 78 | 8. 进入 qemu 目录下,执行如下命令 79 | 80 | ```shell 81 | qemu-system-aarch64 -M virt -cpu cortex-a72 -smp 8 -m 128M -kernel (your Image path) -initrd (your initramfs.cpio.gz path) -nographic -append "init=/init console=ttyAMA0" 82 | ``` 83 | 84 | 其中`(your Image path)`为上一个任务中最后的`Image`镜像文件所在的目录,`(your initramfs.cpio.gz)`为步骤 7 中执行成功后得到的 initramfs.cpio.gz 的目录,例如 85 | 86 | ```shell 87 | qemu-system-aarch64 -M virt -cpu cortex-a72 -smp 8 -m 128M -kernel /home/jun/maodou/linux/arch/arm64/boot/Image -initrd /home/jun/maodou/busybox-1.35.0/initramfs.cpio.gz -nographic -append "init=/init console=ttyAMA0" 88 | ``` -------------------------------------------------------------------------------- /net.md: -------------------------------------------------------------------------------- 1 | # linux 2 | netdev_sent_queue() 用于更新已发送的数据包的数据结构的函数,它有三个参数,分别是网络设备指针,已发送的数据包个数和已发送的字节数。它的作用是将网络设备的sent_queue数据结构中的已发送的数据包个数和字节数增加相应的值,以便内核可以在合适的时机释放已发送的数据包占用的内存,例如在收到网卡的传输完成中断时。 3 | 4 | netdev_completed_queue() 用于更新已完成的I/O请求的数据结构的函数,它有三个参数,分别是网络设备指针,已完成的请求个数和已完成的字节数。它的作用是将网络设备的completed_queue数据结构中的已完成的请求个数和字节数增加相应的值,以便内核可以在合适的时机处理已完成的请求,例如释放资源、通知用户空间等。 5 | 6 | skb.eth_type_trans() skb.protocol_set() skb的protocol字段在数据包从链路层向上传递时,会被用于判断数据包的网络层协议,并调用相应的处理函数。在网卡驱动中,收到数据包后,会调用eth_type_trans函数,该函数会根据以太网帧头中的协议类型,调用skb protocol_set函数设置skb的protocol字段,然后将skb传递给netif_receive_skb函数,该函数会根据skb的protocol字段,调用相应的网络层处理函数,如ip_rcv、ipv6_rcv等。 7 | 8 | # NAPI 9 | NAPI是一种提高网络处理效率的技术,它的核心思想是在高流量负载下,不使用中断的方式读取数据,而是使用轮询的方式来检查设备是否有数据包到达。 10 | 11 | napi_complete_done() 用于通知内核NAPI设备轮询完成的函数,表示轮询处理的数据包个数。它的作用是将NAPI结构体从轮询列表中删除,并清除其状态的NAPI_STATE_SCHED位,表示不需要再被调度。它还会根据返回值判断是否需要重新开启设备的中断,以便接收新的数据包。 12 | 13 | napi_schedule() 用于调度一个NAPI设备的轮询,驱动程序应该在收到中断时调用这个函数。一个成功的调用会获取NAPI设备的所有权,然后在合适的时机调用它的poll函数来处理数据包。 14 | 15 | add_weight() weight表示每次调用NAPI的poll函数时,最多可以处理的数据包个数。通常这个值是64. 16 | 17 | NAPI的工作流程大致如下: 18 | 19 | 1.当设备收到数据包时,产生一个硬件中断,通知内核。 20 | 21 | 2.内核调用中断处理函数,关闭设备的中断,将NAPI结构体加入到轮询列表中,触发软中断。 22 | 23 | 3.软中断调用net_rx_action函数,从轮询列表中取出NAPI结构体,调用其poll函数,处理数据包。 24 | 25 | 4.poll函数每处理一个数据包,就调用napi_consume(64)来更新napi_gro_count字段,如果napi_gro_count为0,或者没有更多的GRO数据包可处理,就退出轮询模式,重新开启设备的中断,等待下一次硬件中断。poll函数返回驱动程序本次处理的数据包个数。用来判断是否达到了NAPI_POLL_WEIGHT上限。 26 | 27 | (napi_gro_count是NAPI结构体中的一个字段,它表示当前NAPI设备正在处理的GRO数据包的个数。GRO是一种通用的接收聚合技术,它的作用是将多个相同流的数据包合并成一个大的数据包,从而减少内核协议栈的处理开销。napi_gro_count的值会在每次调用napi_consume函数时减去处理的数据包个数,如果结果为0,就表示NAPI已经处理完所有的GRO数据包,可以退出轮询模式。) 28 | 29 | # FOO 30 | 以太网帧的结构,它由以下几个部分组成: 31 | - 前导码(Preamble):7个字节,用于同步发送端和接收端的时钟信号。 32 | - 起始定界符(Start of Frame Delimiter,SFD):1个字节,用于标识帧的开始。 33 | - 目的地址(Destination Address,DA):6个字节,用于指定接收端的MAC地址。 34 | - 源地址(Source Address,SA):6个字节,用于指定发送端的MAC地址。 35 | - 类型/长度(Type/Length):2个字节,用于指定帧的类型或长度。 36 | - 数据(Data):46~1500个字节,用于存储帧的有效载荷,包括其他协议的报头和数据。 37 | - 帧检验序列(Frame Check Sequence,FCS):4个字节,用于存储CRC校验码,用于检测帧在传输过程中是否出现错误。 38 | 39 | fujita rust-e1000中length -= 4的操作是为了去掉FCS。 -------------------------------------------------------------------------------- /plan.md: -------------------------------------------------------------------------------- 1 | # OS训练营第三阶段选修 2 | 时间:2023-11-04~2023-12-03 3 | 课题:Rust for Linux 驱动模块开发 4 | 导师: XLY, CL 5 | 6 | ## 课题目标 7 | 学生选修该课题,可以学习到如何一步一步地开发一个Rust for Linux内核驱动;逐步积累并形成学习开发文档;进而有机会为rcore-os Rust操作系统社区以及Linux社区的发展,通过编写Rust驱动等方式贡献一份自己的力量。 8 | 9 | ## 考核方式 10 | * 通过课程练习和实习项目,进行成绩打分排名; 11 | * 阶段性练习及每周的引导 60%; 基于物理开发板/模拟器的Rust驱动开发项目 40% 12 | * 将会统计各位学员的姓名及仓库地址; 代码及运行结果截图请提交在各自练习的仓库; 结果截图中需出现自己的名字或Github ID 13 | 14 | 15 | ## 课题及实习的安排表 16 | 17 | | 周 | 日期 | 导师 | 课程 | 练习 | 练习分数 | 18 | |:--:|:--:|:--:|:--|:--|:--:| 19 | |第一周 基础| 2023-11-6 | XLY | Rust for Linux的基本框架介绍 | 练习1: 获取Rust for Linux源码,配置环境并编译通过,并在模拟器中运行。| 15% | 20 | |第一周 基础| 2023-11-9 | CL | Rust内核驱动模块的各个基础组成部分的学习,结合已有rust-for-linux案例驱动的学习 | 练习2: 编写一个简单的"Hello World"的Rust内核模块,并能加载运行输出。| 15% | 21 | |第二周 进阶| 2023-11-13 | CL | 内核驱动的开发框架介绍,以网卡驱动为例 | 练习3: 基于Qemu模拟器上的e1000网卡驱动框架,填充驱动初始化函数,并可以自定义一个linux内核C函数在Rust网卡驱动模块中调用该函数。| 15% | 22 | |第二周 进阶| 2023-11-16 | XLY | Rust网卡驱动的开发 | 练习4: 基于Qemu模拟器上的e1000网卡驱动框架,填充驱动的接收包和发送包函数,并可以加载运行,运行ping的发送与接收响应。| 15% | 23 | |第二周 进阶| | | | 练习3, 4(可选): 分析并复现rust-for-linux社区已实现的驱动案例,可复现运行并做个报告。| 30% | 24 | | | | | | | | 25 | | 第三周 实习准备 | 2023-11-20 | XLY | 实习项目安排介绍; Uart及VirtIO驱动项目讲解 | 对于实习项目,准备Rust for Linux在所选平台上的基本运行所需的工作,如引导启动所需的设备树,内存设备基地址等 | | 26 | | 第三周 实习准备 | 2023-11-23 | CL | 项目问题交流; 精简C语言版本驱动介绍 | 对于所选的驱动,如Uart或VirtIO驱动, 为下一步把最小化C版本驱动转换为Rust版本驱动做准备;精简其C代码, 使其保留最基本的运行功能; | | 27 | | 第四周 项目实习 | 2023-11-27 | XLY | 课程内容回顾,实习项目交流 | 实习项目1 (可选): 在华山派开发板或树莓派模拟器上,适配Rust for Linux 6.6内核,并实现Rust Uart串口驱动,运行起来;| 40% | 28 | | 第四周 项目实习 | 2023-11-30 | CL | 课程内容回顾,实习项目交流 | 实习项目2 (可选): 在Qemu模拟器上尝试把已有的rcore-os/virtio-drivers项目,移植适配于Rust for Linux 6.6内核,驱动运行起来;| 40% | 29 | -------------------------------------------------------------------------------- /优秀文档参考/PuzzleFS-下一代Linux容器文件系统之源码分析报告-邝劲强.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: PuzzleFS-下一代Linux容器文件系统之源码分析报告-邝劲强 3 | Date: 2023-11 4 | Tags: 5 | - Author: 邝劲强 6 | - Reports Repo: https://github.com/lispking/rust-for-linux/tree/main/reports 7 | --- 8 | 9 | # PuzzleFS - 下一代 Linux 容器文件系统之源码分析报告 10 | 11 | PuzzleFS 项目地址:https://github.com/project-machine/puzzlefs 12 | 13 | > PuzzleFS 号称是`下一代 Linux 容器文件系统`,采用 Rust 语言编写,具有`快速镜像构建`、`直接挂载支持`、`内存安全保证`等功能。 14 | 15 | --- 16 | 17 | ## PuzzleFS 项目介绍 18 | 19 | 思科开发者 `Ariel Miculas` 在[内核邮件列表](https://lore.kernel.org/rust-for-linux/20230609063118.24852-1-amiculas@cisco.com/)中,发布了用 Rust 写的 PuzzleFS 文件系统驱动,以征求其他开发者的意见,目前这一驱动程序处于“概念验证”阶段。 20 | 21 | PuzzleFS 是一种`只读`的文件系统,与 `Open Container Initiative (OCI) v2 image specification` 的设计目标相符,它使用内容定义进行分块,并且使用内容寻址的数据存储,旨在解决现有 OCI 格式的限制。 22 | 23 | > OCI 镜像规范的第一个版本存在许多问题,这些问题的根源都是来自依赖 tar 存档来保存文件系统中的层,事实上 `tar 不适合解决容器文件系统问题`。 24 | 25 | PuzzleFS 旨在解决 tar 的问题。文件系统镜像本身由一组放置在底层文件系统上的文件组成。与 `OCI 镜像格式一样`,存在一个顶级 `index.json` 文件,其中包含一组标签,每个标签代表某一个版本的文件系统,并指向一个清单文件(`manifest file`)。清单文件又指向镜像配置和存储在实际镜像层中的数据。其他所有内容都存储为 `blobs/sha256` 目录中的一组 `blob`。 26 | 27 | 文件系统中的大多数数据都被分成可变大小的块(`chunk`),以内容的 `SHA256` 哈希作为文件名来存储为 `blob`。这个分块动作本身是使用 `FastCDC 算法`进行的,该算法查找 "切割点(`cut points`)",把数据流分为不同大小的 `blob`。任何一个特定的流(例如文件的内容)都可以分成`五个`或`五十个块`,这取决于如何确定这些切割点;然后,每个块都以不同的 `blob` 方式落在 `blobs/sha256` 下,并将其哈希添加到清单中。 28 | 29 | --- 30 | 31 | ## 环境准备 32 | 33 | ### 实验环境信息 34 | 35 | * CPU: Apple M1 36 | * 宿主机OS:macOS Sonoma 14.1.1 37 | * 虚拟机:使用 OrbStack 安装 ubuntu 23.10,选用 OrbStack 理由看 这里:https://docs.orbstack.dev/ 38 | 39 | ### Ubuntu 环境准备 40 | 41 | * 设置 Rustup 镜像, 修改配置 ~/.zshrc or ~/.bashrc 42 | 43 | ```bash 44 | export RUSTUP_DIST_SERVER="https://rsproxy.cn" 45 | export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup" 46 | ``` 47 | 48 | * 安装 Rust(请先完成步骤一的环境变量导入并 source rc 文件或重启终端生效) 49 | 50 | ```bash 51 | curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh 52 | ``` 53 | 54 | * 设置 crates.io 镜像, 修改配置 ~/.cargo/config,已支持git协议和sparse协议,>=1.68 版本建议使用 sparse-index,速度更快。 55 | 56 | ```bash 57 | cat < ~/.cargo/config 58 | [source.crates-io] 59 | replace-with = 'rsproxy-sparse' 60 | [source.rsproxy] 61 | registry = "https://rsproxy.cn/crates.io-index" 62 | [source.rsproxy-sparse] 63 | registry = "sparse+https://rsproxy.cn/index/" 64 | [registries.rsproxy] 65 | index = "https://rsproxy.cn/crates.io-index" 66 | [net] 67 | git-fetch-with-cli = true 68 | EOF 69 | ``` 70 | 71 | * 修改 Ubuntu 源,加速依赖包更新速度 72 | 73 | ```bash 74 | sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 75 | sudo sed -i 's/ports.ubuntu.com/mirrors.ustc.edu.cn/' /etc/apt/sources.list 76 | ``` 77 | 78 | * 安装 Linux 内核相关依赖包 79 | 80 | ```bash 81 | ## first Update 82 | sudo apt update 83 | 84 | ## Install dependency packages 85 | sudo apt -y install \ 86 | binutils build-essential libtool texinfo \ 87 | gzip zip unzip patchutils curl git \ 88 | make cmake ninja-build automake bison flex gperf \ 89 | grep sed gawk bc \ 90 | zlib1g-dev libexpat1-dev libmpc-dev \ 91 | libglib2.0-dev libfdt-dev libpixman-1-dev libelf-dev libssl-dev 92 | 93 | ## Install the LLVM 94 | sudo apt -y install clang-format clang-tidy clang-tools clang clangd libc++-dev libc++1 libc++abi-dev libc++abi1 libclang-dev libclang1 liblldb-dev libllvm-ocaml-dev libomp-dev libomp5 lld lldb llvm-dev llvm-runtime llvm python3-clang 95 | ``` 96 | 97 | * 安装 PuzzleFS 需要用到的工具 98 | 99 | ```bash 100 | sudo apt install -y capnproto skopeo umoci tree jq 101 | ``` 102 | 103 | --- 104 | 105 | ## 编译和挂载 PuzzleFS 镜像 106 | 107 | ### 测试文件准备 108 | 109 | ```bash 110 | # 1. 创建相关测试目录 111 | mkdir -p /tmp/example-rootfs/algorithms /tmp/mounted-image 112 | 113 | # 2. 创建测试文件 114 | echo "Binary Search" > /tmp/example-rootfs/algorithms/binary-search.txt 115 | echo "test" > /tmp/example-rootfs/algorithms/test.txt 116 | echo "lorem ipsum" > /tmp/example-rootfs/lorem_ipsum.txt 117 | tree /tmp/example-rootfs 118 | ``` 119 | 120 | ### 编译 PuzzleFS 源码 121 | 122 | ```bash 123 | cargo build --release 124 | ``` 125 | 126 | ### 构建 PuzzleFS 镜像 127 | 128 | ```bash 129 | target/release/puzzlefs build -c /tmp/example-rootfs /tmp/puzzlefs-image puzzlefs_example 130 | ``` 131 | 132 | 运行成功会产生以下信息,`build` 命令内部实际通过 Linux 内核工具模块 `fs-verity` 实现。 133 | 134 | ```bash 135 | puzzlefs image manifest digest: 743b08e98105e637c47adb80a17ba21ed12eb31523a056a0970ff687551904d7 136 | ``` 137 | 138 | ### 挂载 PuzzleFS 镜像 139 | 140 | * 通过 PuzzleFS 工具挂载到系统 141 | 142 | ```bash 143 | target/release/puzzlefs mount /tmp/puzzlefs-image puzzlefs_example /tmp/mounted-image 144 | ``` 145 | 146 | * 验证挂载结果 147 | 148 | ```bash 149 | # 1. 验证挂载结果 150 | mount|grep fuse 151 | 152 | # 若显示结果出现 /dev/fuse on /tmp/mounted-image,则代表镜像挂载成功 153 | /dev/fuse on /tmp/mounted-image type fuse (rw,nosuid,nodev,relatime,user_id=501,group_id=501) 154 | 155 | # 2. 验证挂载目录情况是否与 /tmp/example-rootfs 内容一致 156 | tree /tmp/mounted-image 157 | 158 | # 若显示结果如下,则代表文件挂载成功 159 | /tmp/mounted-image 160 | ├── algorithms 161 | │   ├── binary-search.txt 162 | │   └── test.txt 163 | └── lorem_ipsum.txt 164 | 165 | # 3. 查看 PuzzleFS 文件组织结构 166 | tree /tmp/puzzlefs-image/ 167 | 168 | # 显示内容如文章介绍所述 169 | /tmp/puzzlefs-image/ 170 | ├── blobs 171 | │   └── sha256 172 | │   ├── 04718eb872552abfdf44d563aa09bdf89343950a364d9ff555fc6e3657d383b4 173 | │   ├── 73ee73b991e91697ec02c990de8401d7c316d3392ec394be7162195dc783c9b5 174 | │   └── e10090d2c24f6a299ad6fee3e6f345a784918bc89530a1329c92c40de71a9c47 175 | ├── index.json 176 | └── oci-layout 177 | 178 | # 4. 查看 /tmp/puzzlefs-image/index.json 内容 179 | cat /tmp/puzzlefs-image/index.json | jq . 180 | 181 | # manifests 里的 digest 确实指示到 /tmp/puzzlefs-image/blobs/sha256 下 182 | { 183 | "schemaVersion": -1, 184 | "manifests": [ 185 | { 186 | "digest": "sha256:04718eb872552abfdf44d563aa09bdf89343950a364d9ff555fc6e3657d383b4", 187 | "size": 272, 188 | "media_type": "application/vnd.puzzlefs.image.rootfs.v1", 189 | "annotations": { 190 | "org.opencontainers.image.ref.name": "puzzlefs_example" 191 | } 192 | } 193 | ], 194 | "annotations": {} 195 | } 196 | 197 | # 5. 复制内容验证文件 198 | mkdir -p ~/puzzlefs/format 199 | cp -f puzzlefs-lib/src/format/*.capnp ~/puzzlefs/format 200 | 201 | # 6. 验证 PuzzleFS 文件系统 metadata 内容,注意 Rootfs 文件在 /tmp/puzzlefs-image/index.json 的 digest 字段已提供 202 | capnp convert binary:json ~/puzzlefs/format/manifest.capnp Rootfs < /tmp/puzzlefs-image/blobs/sha256/04718eb872552abfdf44d563aa09bdf89343950a364d9ff555fc6e3657d383b4 203 | 204 | # 这里可以看到文件的压缩开发、偏移以及文件验证信息等元数据均记录在此文件 205 | { "metadatas": [{ "digest": [115, 238, 115, 185, 145, 233, 22, 151, 236, 2, 201, 144, 222, 132, 1, 215, 195, 22, 211, 57, 46, 195, 148, 190, 113, 98, 25, 93, 199, 131, 201, 181], 206 | "offset": "0", 207 | "compressed": false }], 208 | "fsVerityData": [ 209 | { "digest": [115, 238, 115, 185, 145, 233, 22, 151, 236, 2, 201, 144, 222, 132, 1, 215, 195, 22, 211, 57, 46, 195, 148, 190, 113, 98, 25, 93, 199, 131, 201, 181], 210 | "verity": [175, 40, 167, 26, 25, 10, 180, 65, 203, 152, 77, 163, 62, 196, 210, 184, 253, 43, 13, 128, 130, 115, 124, 42, 166, 42, 113, 75, 36, 144, 91, 6] }, 211 | { "digest": [225, 0, 144, 210, 194, 79, 106, 41, 154, 214, 254, 227, 230, 243, 69, 167, 132, 145, 139, 200, 149, 48, 161, 50, 156, 146, 196, 13, 231, 26, 156, 71], 212 | "verity": [224, 250, 182, 10, 67, 174, 109, 47, 213, 23, 121, 57, 246, 130, 220, 70, 235, 42, 198, 50, 201, 130, 116, 91, 120, 12, 232, 61, 237, 227, 113, 94] } ], 213 | "manifestVersion": "2" } 214 | 215 | # 7. 验证 PuzzleFS 文件系统 inode 内容 216 | capnp convert binary:json ~/puzzlefs/format/metadata.capnp InodeVector < /tmp/puzzlefs-image/blobs/sha256/73ee73b991e91697ec02c990de8401d7c316d3392ec394be7162195dc783c9b5 217 | 218 | # 可以看到日常看到的 ls -lat 命令查看的文件 dir、file 及对应的归属用户和权限都保存在这个文件里 219 | {"inodes": [ 220 | { "ino": "1", 221 | "mode": {"dir": { 222 | "entries": [ 223 | { "ino": "2", 224 | "name": [97, 108, 103, 111, 114, 105, 116, 104, 109, 115] }, 225 | { "ino": "3", 226 | "name": [108, 111, 114, 101, 109, 95, 105, 112, 115, 117, 109, 46, 116, 120, 116] } ], 227 | "lookBelow": false }}, 228 | "uid": 501, 229 | "gid": 501, 230 | "permissions": 493 }, 231 | { "ino": "2", 232 | "mode": {"dir": { 233 | "entries": [ 234 | { "ino": "4", 235 | "name": [98, 105, 110, 97, 114, 121, 45, 115, 101, 97, 114, 99, 104, 46, 116, 120, 116] }, 236 | {"ino": "5", "name": [116, 101, 115, 116, 46, 116, 120, 116]} ], 237 | "lookBelow": false }}, 238 | "uid": 501, 239 | "gid": 501, 240 | "permissions": 493 }, 241 | { "ino": "3", 242 | "mode": {"file": [{ "blob": { 243 | "digest": [225, 0, 144, 210, 194, 79, 106, 41, 154, 214, 254, 227, 230, 243, 69, 167, 132, 145, 139, 200, 149, 48, 161, 50, 156, 146, 196, 13, 231, 26, 156, 71], 244 | "offset": "0", 245 | "compressed": false }, 246 | "len": "12" }]}, 247 | "uid": 501, 248 | "gid": 501, 249 | "permissions": 420 }, 250 | { "ino": "4", 251 | "mode": {"file": [{ "blob": { 252 | "digest": [225, 0, 144, 210, 194, 79, 106, 41, 154, 214, 254, 227, 230, 243, 69, 167, 132, 145, 139, 200, 149, 48, 161, 50, 156, 146, 196, 13, 231, 26, 156, 71], 253 | "offset": "12", 254 | "compressed": false }, 255 | "len": "14" }]}, 256 | "uid": 501, 257 | "gid": 501, 258 | "permissions": 420 }, 259 | { "ino": "5", 260 | "mode": {"file": [{ "blob": { 261 | "digest": [225, 0, 144, 210, 194, 79, 106, 41, 154, 214, 254, 227, 230, 243, 69, 167, 132, 145, 139, 200, 149, 48, 161, 50, 156, 146, 196, 13, 231, 26, 156, 71], 262 | "offset": "26", 263 | "compressed": false }, 264 | "len": "5" }]}, 265 | "uid": 501, 266 | "gid": 501, 267 | "permissions": 420 } ]} 268 | ``` 269 | 270 | > 至此,PuzzleFS 镜像则准备完成,接下来,就可以开始从源码级别解刨 PuzzleFS 实现。 271 | 272 | --- 273 | 274 | ## 解刨前,需了解的知识 275 | 276 | ### [Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/spec.md) 277 | 278 | OCI manifest主要由一个或多个文件列表(组成一个完整的可运行的文件系统)、索引和配置等元数据组成。其中,配置文件包含应用参数、环境变量等信息。索引文件主要是文件清单列表描述,不同平台可能会所有差异。 279 | 280 | ![00-oci-spec](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise5/./images/00-oci-spec.png) 281 | 282 | OCI 镜像构建完成后,可通过名称方式发现和下载镜像,以及通过哈希方式进行验证,通过签名进行信任,并解压到 OCI 运行时中。 283 | 284 | ![01-oci-spec](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise5/./images/01-oci-spec.png) 285 | 286 | OCI 规范主要包括下面这些组件: 287 | * Image Manifest - 有关容器镜像文件清单说明 288 | * Image Index - 有关 Manifest 索引列表说明 289 | * Image Layout - 有关镜像中的文件系统布局说明 290 | * Filesystem Layer - 有关容器文件系统的变化说明 291 | * Image Configuration - 有关镜像的配置说明 292 | * Conversion - 有关转换如何发生的说明 293 | * Artifacts Guidance - 除 OCI 镜像之外的打包规范说明 294 | * Descriptor - 有关类型、元数据和内容地址等的说明 295 | 296 | --- 297 | 298 | ## PuzzleFS 源码分析 299 | 300 | 该工程主要包含`puzzlefs-lib`库和`exe`可执行两部分: 301 | 302 | * `puzzlefs-lib`:PuzzleFS 库的实现 303 | - `format`:用于 PuzzleFS 格式的序列化和反序列化 304 | - `builder`:用于构建 PuzzleFS 镜像 305 | - `extractor`:用于解压 PuzzleFS 镜像 306 | - `reader`: 用于将 PuzzleFS 镜像挂载到 `FUSE`(文件系统)的模块。 307 | * `exe/`:可执行文件封装主要是对一些命令和库进行组合,将其整合在一起。这个封装没有什么特别需要解读的地方,因为它只是简单地将相关功能集成在一起,方便用户使用。 308 | 309 | --- 310 | 311 | ### PuzzleFS 格式 312 | 313 | PuzzleFS 主要由两部分组成:一个用于存储 inode 信息的元数据格式,以及由各种分块算法定义的实际文件系统数据块。其中,所有枚举值均编码为 u32;所有编码均为小端。 314 | 315 | 所有 PuzzleFS 块均由以下结构体封装: 316 | 317 | ```rust 318 | enum hash { 319 | sha256, 320 | } 321 | 322 | typedef hash_value byte[32] // 对于 sha256 323 | 324 | enum blob_type { 325 | root, 326 | metadata, 327 | file, 328 | } 329 | 330 | type puzzlefs_blob { 331 | enum hash; 332 | u64 references_len; 333 | hash_value references[]; 334 | blob_type type; 335 | // followed by the actual blob 336 | } 337 | ``` 338 | 339 | > 下面通过一张 UML 图来解读 Puzzle 文件系统格式里相关组件的关系,源码文件位于 `format/types.rs` 340 | 341 | ![02-format-types](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise5/images/02-format-types.png) 342 | 343 | 344 | #### 根文件系统(Rootfs)的结构体定义 345 | 346 | ```rust 347 | pub struct Rootfs { 348 | pub metadatas: Vec, 349 | pub fs_verity_data: VerityData, 350 | pub manifest_version: u64, 351 | } 352 | ``` 353 | 354 | > 用于描述一个 Rootfs 实例,其中包含了文件系统的元数据、完整性数据和清单版本号。这有助于系统和应用程序正确地处理和验证根文件系统的内容。 355 | 356 | 结构体包含以下成员: 357 | 358 | 1. `metadatas`:一个`Vec`类型的成员,表示根文件系统的元数据。`BlobRef`可能是一个指向文件系统元数据的引用或指针。 359 | 2. `fs_verity_data`:一个`VerityData`类型的成员,表示文件系统的完整性数据。`VerityData`可能是一个用于验证文件系统数据完整性的结构体或类。 360 | 3. `manifest_version`:一个`u64`类型的成员,表示清单(manifest)的版本。这个版本号用于识别和区分不同的清单版本。 361 | 362 | 363 | #### Blob 引用(BlobRef)的结构体定义 364 | 365 | ```rust 366 | pub struct BlobRef { 367 | pub digest: [u8; SHA256_BLOCK_SIZE], 368 | pub offset: u64, 369 | pub compressed: bool, 370 | } 371 | ``` 372 | 373 | > 用于文件系统中引用和标识 BLOB 数据。通过存储 BLOB 的哈希值、偏移量和压缩状态,这个结构体为文件系统的管理和操作提供了有关 BLOB 信息的基本概述。 374 | 375 | 结构体包含以下成员: 376 | 377 | 1. `digest`:`[u8; SHA256_BLOCK_SIZE]`类型,用于存储 BLOB 的哈希值。SHA256 是一种加密哈希算法,用于生成一个固定长度的哈希值。这个字节数组的长度为 SHA256 块大小,通常为 64 字节。 378 | 2. `offset`:`u64` 类型,表示 BLOB 在文件系统中的偏移量。这个偏移量用于定位 BLOB 在文件系统中的位置。 379 | 3. `compressed`:`bool` 类型,表示 BLOB 是否已压缩。如果为 `true`,则表示 BLOB 已压缩;如果为 `false`,则表示 BLOB 未压缩。 380 | 381 | #### inode(节点)的结构体定义 382 | 383 | ```rust 384 | pub struct Inode { 385 | pub ino: Ino, 386 | pub mode: InodeMode, 387 | pub uid: u32, 388 | pub gid: u32, 389 | pub permissions: u16, 390 | pub additional: Option, 391 | } 392 | ``` 393 | 394 | > 用于表示文件系统中的 inode,包含了识别和描述 inode 所需的基本信息。这有助于管理和操作文件系统中的文件和目录。 395 | 396 | 结构体包含以下成员: 397 | 1. `ino`:`Ino` 类型,表示 inode 的唯一标识符。inode 是文件系统中的一个基本概念,用于标识文件和目录。 398 | 2. `mode`:`InodeMode` 类型,表示 inode 的模式。`InodeMode` 可能是一个枚举类型,用于表示 inode 是文件还是目录,以及文件或目录的权限设置。 399 | 3. `uid`:`u32` 类型,表示 inode 所属的用户 ID。用户 ID 用于识别文件或目录的所有者。 400 | 4. `gid`:`u32` 类型,表示 inode 所属的组 ID。组 ID 用于识别文件或目录的所有者所属的组。 401 | 5. `permissions`:`u16` 类型,表示 inode 的权限。这些权限通常包括读、写和执行权限,用于控制用户和组对文件或目录的访问。 402 | 6. `additional`:`Option` 类型,表示额外的 inode 信息。`InodeAdditional` 可能是一个包含更多详细信息的结构体或类。这个选项值为 `None` 时,表示没有额外的 inode 信息。 403 | 404 | 405 | #### 元数据 BLOB(MetadataBlob)的结构体定义 406 | 407 | ```rust 408 | pub struct MetadataBlob { 409 | reader: message::TypedReader< 410 | ::capnp::serialize::BufferSegments, 411 | crate::metadata_capnp::inode_vector::Owned, 412 | >, 413 | } 414 | ``` 415 | 416 | > 用于表示一个元数据 BLOB,包含了读取和解析元数据所需的信息。这个结构体通常用于处理和分析文件系统中的元数据,以便更好地管理和操作文件。 417 | 418 | 结构体只有一个成员 `reader`: 419 | 420 | 这是一个 `message::TypedReader` 类型,用于读取和解析元数据 BLOB。`TypedReader` 是 `Cap'n Proto` 序列化库中的一个类型,用于读取和处理特定类型的数据。 421 | 422 | `::capnp::serialize::BufferSegments` 是一个泛型类型,表示用于存储元数据的内存映射(`Mmap`)缓冲区。`Mmap` 是一种内存映射文件的技术,可以将文件内容映射到内存中,以便更快地访问和处理文件数据。 423 | 424 | `crate::metadata_capnp::inode_vector::Owned` 是一个泛型类型,表示元数据 BLOB 中的 inode 向量。inode 向量可能是一个包含 inode 结构体的列表或数组,用于存储文件系统中的元数据。 425 | 426 | --- 427 | 428 | ### PuzzleFS 镜像构建 429 | 430 | > 用于处理文件系统、压缩、加密或构建的工具或库。它提供了一些方法来操作文件系统、处理压缩、进行测试,并且与文件系统的可复现性或安全性有关。 431 | 432 | #### 核心结构体定义 433 | 434 | > 文件位于 `puzzlefs-lib/src/builder/filesystem.rs` 435 | 436 | ```rust 437 | struct ReaderLink { 438 | file: PathBuf, 439 | done: bool, 440 | } 441 | 442 | /// A structure used to chain multiple readers, similar to 443 | /// [chain](https://doc.rust-lang.org/std/io/trait.Read.html#method.chain) 444 | /// and [multi_reader](https://docs.rs/multi_reader/latest/multi_reader/) 445 | pub struct FilesystemStream { 446 | reader_chain: Vec, 447 | current_reader: Option, 448 | } 449 | ``` 450 | 451 | 该部分主要由两个结构体:`ReaderLink` 和 `FilesystemStream` 组成。 452 | 453 | 1. `ReaderLink` 结构体包含两个字段: 454 | - `file`:一个 `PathBuf` 类型的字段,表示文件的路径。 455 | - `done`:一个 `bool` 类型的字段,表示读取操作是否完成。 456 | 457 | 2. `FilesystemStream` 结构体用于链接多个 `Reader`,类似于 `std::io::Read` trait 的 `chain` 方法和 `multi_reader` 库,该做法的目的是方便按顺序读取多个文件。它包含两个字段: 458 | - `reader_chain`:一个 `Vec` 类型的字段,用于存储多个 `ReaderLink`,实现类似于链式文件读取的操作,例如,在读取一个文件之后自动切换到下一个文件。这种设计可以方便地实现流水线式的文件处理任务。 459 | - `current_reader`:一个 `Option` 类型的字段,表示当前正在使用的`reader`。 460 | 461 | 3. 两个结构体关系如下图所述: 462 | 463 | ![03-builder-filesystem](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise5/images/03-builder-filesystem.png) 464 | 465 | 466 | #### 核心函数作用解读 467 | 468 | > 文件位于 `puzzlefs-lib/src/builder.rs` 469 | 470 | * `walker`: 用于遍历文件系统或目录结构。 471 | * `add_entry`, `serialize_manifest`, `serialize_metadata`: 与将文件系统或目录结构的信息添加到某个数据结构或序列化有关。 472 | * `process_chunks`, `build_delta`: 与处理文件或数据块的压缩相关,其中, `C` 代表某种压缩算法。 473 | * `lookup_existing`: 用于查找或查询文件系统中的现有文件或目录。 474 | * `build_initial_rootfs`, `add_rootfs_delta`: 与构建或修改文件系统根目录有关。 475 | * `enable_fs_verity`: 用于启用文件系统 verity 相关的功能。 476 | * `build_test_fs`, `test_fs_generation`, `test_delta_generation`: 与测试文件系统或其生成有关。 477 | * `do_vecs_match`: 用于比较两个序列或向量是否相等的。 478 | * `get_image_blobs`: 用于获取镜像或数据块的。 479 | * `same_dir_reproducible`, `same_dir_contents_reproducible`: 与检查目录或文件内容的可复现性有关。 480 | * `test_reproducibility`: 用于测试或验证文件系统的可复现性。 481 | * `build_dummy_fs`: 用于构建虚拟的文件系统。 482 | 483 | --- 484 | 485 | ### PuzzleFS 镜像解压 486 | 487 | > 用于从容器或镜像中提取文件系统的工具或库。它提供了一些方法来处理文件和目录的创建、所有权和权限。 488 | 489 | > 文件位于 `puzzlefs-lib/src/extractor.rs` 490 | 491 | * `runs_privileged`: 用于检查当前运行是否具有 root 权限。 492 | * `PuzzleFS`, `WalkPuzzleFS``: 用于处理文件系统的类或结构体。 493 | * `makedev`, `mknod`, `Mode`, `SFlag`: 与文件系统或设备创建有关的函数或常量。 494 | * `chown`, `mkfifo`, `symlinkat`, `Gid`, `Uid`: 与文件系统所有权、符号链接或 FIFO 管道创建有关的函数或常量。 495 | * `HashMap`, `OsStr`, `Path`, `PathBuf`: 与文件系统路径或数据结构有关的类型。 496 | * `fs`, `io`: 与文件系统操作或输入/输出操作有关的模块。 497 | 498 | --- 499 | 500 | ### PuzzleFS 镜像挂载 501 | 502 | > 用于实现一个基于 FUSE 的文件系统,并可能与 puzzles 或者 cryptographic hash 有关。 503 | 504 | #### `PuzzleFS`结构体定义 505 | 506 | > 文件位于 `puzzlefs-lib/src/reader/puzzlefs.rs` 507 | 508 | > 用于在Rust中实现镜像挂载功能,并与其他组件(如`Fuse`)协同工作,表示一个包含镜像、层(layer)元数据、验证数据和可选验证映像清单的文件系统。 509 | 510 | ```rust 511 | pub struct PuzzleFS { 512 | pub oci: Arc, 513 | layers: Vec, 514 | pub verity_data: Option, 515 | pub manifest_verity: Option>, 516 | } 517 | ``` 518 | 以下是结构体中各属性的解释: 519 | 1. `oci`:`Arc`类型,`Arc`用于确保镜像在多个引用之间共享,而不会创建不必要的副本。 520 | 2. `layers`:`Vec`类型,表示镜像的各个层。`MetadataBlob`定义参考 `Format 章节`的描述。 521 | 3. `verity_data`:`Option`类型,表示验证数据。验证数据用于确保镜像在传输过程中不被篡改。可选字段,意味着并非所有`PuzzleFS`实例都需要验证数据。 522 | 4. `manifest_verity`:`Option>`类型的,表示镜像清单的验证。可选字段,用于确保镜像的完整性。清单验证通常是通过将清单内容与预期的哈希值进行比较来实现的。 523 | 524 | 525 | #### `Fuse`结构体定义 526 | 527 | > 文件位于 `puzzlefs-lib/src/reader/fuse.rs` 528 | 529 | ```rust 530 | pub enum PipeDescriptor { 531 | UnnamedPipe(PipeWriter), 532 | NamedPipe(PathBuf), 533 | } 534 | 535 | pub struct Fuse { 536 | pfs: PuzzleFS, 537 | sender: Option>, 538 | init_notify: Option, 539 | // TODO: LRU cache inodes or something. I had problems fiddling with the borrow checker for the 540 | // cache, so for now we just do each lookup every time. 541 | } 542 | ``` 543 | 544 | > 用于实现基于 FUSE(Filesystem in Userspace)的文件系统,允许用户空间实现文件系统功能。`PipeDescriptor` 用于在进程间通信,`Fuse` 结构体则用于处理 FUSE 文件系统的相关操作。 545 | 546 | * `PipeDescriptor` 枚举类型有两个成员: 547 | - `UnnamedPipe`:`PipeWriter` 实例,表示未命名管道。 548 | - `NamedPipe`:`PathBuf` 实例,表示已命名管道。 549 | 550 | * `Fuse` 结构体有以下成员: 551 | - `pfs`:`PuzzleFS` 实例,表示 Puzzle 文件系统。 552 | - `sender`:`Option>` 类型,用于发送消息。 553 | - `init_notify`:一个 `Option` 类型,表示一个初始化通知管道。 554 | - 注释中提到了一个 LRU 缓存,但由于借用检查器的问题,暂时每次都进行查找。 555 | 556 | 557 | #### `WalkPuzzleFS`结构体定义 558 | 559 | > 文件位于 `puzzlefs-lib/src/reader/walk.rs` 560 | 561 | ```rust 562 | /// A in iterator over a PuzzleFS filesystem. This iterates breadth first, since file content is 563 | /// stored that way in a puzzlefs image so it'll be faster reading actual content if clients want 564 | /// to do that. 565 | pub struct WalkPuzzleFS<'a> { 566 | pfs: &'a mut PuzzleFS, 567 | q: VecDeque, 568 | } 569 | 570 | pub struct DirEntry { 571 | oci: Arc, 572 | pub path: PathBuf, 573 | pub inode: Inode, 574 | } 575 | ``` 576 | 577 | > 用于遍历 Puzzle 文件系统,以便于查询和操作文件系统中的内容。迭代器 `WalkPuzzleFS` 负责遍历文件系统,而 `DirEntry` 表示每个目录 entry,便于进一步处理。 578 | 579 | * `WalkPuzzleFS` 表示一个遍历 Puzzle 文件系统的迭代器。它采用广度优先遍历策略,因为文件内容在 Puzzle 文件系统镜像中就是以这种方式存储的,这样在实际内容读取时会更快。结构体有以下成员: 580 | - `pfs`:`PuzzleFS` 实例,表示一个 Puzzle 文件系统。 581 | - `q`:`VecDeque` 类型,表示一个待遍历的目录 entry 队列。 582 | 583 | * `DirEntry` 表示一个目录 entry,包含镜像、路径和 inode 编号等信息。结构体有以下成员: 584 | - `oci`:`Arc` 类型,表示一个镜像。 585 | - `path`:`PathBuf` 类型,表示目录 entry 的路径。 586 | - `inode`:`Inode` 类型,表示目录 entry 的 inode 编号。 587 | 588 | #### 核心函数作用解读 589 | 590 | > 文件位于 `puzzlefs-lib/src/reader.rs` 591 | 592 | * `mount_option_from_str` 函数:用于从字符串`s`中解析出对应的 `FUSE` 挂载选项(`fuse_ffi::MountOption`)。函数采用匹配语法 `match` 来根据字符串`s`的值进行匹配,然后返回相应的挂载选项。以下是代码中各挂载选项的描述: 593 | - `auto_unmount`:自动卸载文件系统。 594 | - `allow_other`:允许其他用户访问文件系统。 595 | - `allow_root`:允许root用户访问文件系统。 596 | - `default_permissions`:使用默认权限。 597 | - `dev`:启用设备文件。 598 | - `nodev`:禁用设备文件。 599 | - `suid`:启用 `SUID`(`Set UID`)特权。 600 | - `nosuid`:禁用 `SUID` 特权。 601 | - `ro`:只读挂载。 602 | - `rw`:读写挂载。 603 | - `exec`:启用可执行文件。 604 | - `noexec`:禁用可执行文件。 605 | - `atime`:使用文件访问时间(`ATIME`)更新。 606 | - `noatime`:不使用文件访问时间更新。 607 | - `dirsync`:启用目录同步。 608 | - `sync`:启用同步挂载。 609 | - `async`:启用异步挂载。 610 | - `fsname`:设置文件系统名称。 611 | - `subtype`:设置文件系统子类型。 612 | - 如果`s`的字符串值不匹配以上任何一种挂载选项,则返回`fuse_ffi::MountOption::CUSTOM`(表示自定义挂载选项),并将字符串`s`作为参数传递。 613 | 614 | * `mount`函数:用于将一个镜像(Image)挂载到指定的目录(mountpoint)。以下是代码的主要部分及其功能: 615 | * 参数解读: 616 | - `image`:要挂载的镜像。 617 | - `tag`:镜像的标签。 618 | - `mountpoint`:要挂载镜像到的目录路径。 619 | - `options`:字符串切片,表示挂载选项。 620 | - `init_notify`:可选的管道描述符,用于初始化通知。 621 | - `manifest_verity`:可选的字节切片,表示镜像清单的验证。 622 | * 函数流程: 623 | - 首先,尝试打开一个名为`PuzzleFS`的文件系统,该文件系统包含镜像、标签和可选的验证。 624 | - 然后,创建一个`Fuse`实例,将`PuzzleFS`挂载到`mountpoint`。 625 | - 使用`fuse_ffi::mount2`函数将`Fuse`实例挂载到指定路径,并传递挂载选项。(另一个 `spawn_mount` 函数就不展开说明,差异点是此处调用`fuse_ffi::spawn_mount2`实现) 626 | - 最后,返回成功挂载的`Ok(())`结果。 627 | 628 | --- 629 | 630 | ## [Chunking 文章解读](chunking.md) 631 | 632 | 这篇文章主要讨论了如何将文件系统进行分块的技术。作者提出了两种分块策略:基于`Rabin指纹/滚动哈希`的方法和基于`内容定义`的分块方法。 633 | 634 | 首先,作者建议使用`Rabin指纹/滚动哈希技术`进行分块,并提到了 `casync` 作为相关先例。接下来,作者阐述了如何将文件系统内容序列化为一个流,以便进行内容定义的分块。在这个过程中,作者强调了对目录条目进行广度优先遍历,以便更好地共享文件系统内容。 635 | 636 | 文章还讨论了两种分块策略的优缺点。 637 | * 一方面,让镜像定义自己的参数可以根据特定镜像进行微调,以获得更好的更新结果。 638 | * 另一方面,硬编码参数可以使镜像之间的共享更多,并使不同的镜像构建者使用相同的算法和参数。 639 | 640 | 最后,作者表示将选择留给了读者,例如哈希、参数等。这意味着读者可以根据自己的需求和偏好选择合适的技术和参数。 641 | 642 | 简单来说,这篇文章介绍了如何在不同镜像之间共享文件系统内容的技术和方法,并讨论了两种分块策略的优缺点。作者鼓励读者根据自己的需求选择最适合的分块方法。 643 | 644 | 645 | ## 内核邮件信息说明 646 | 647 | 最后,附上内核邮件的信息来结束 PuzzleFS 源码分析: 648 | 649 | PuzzleFS [1] 是一个容器文件系统,旨在解决现有 OCI 格式的一些局限性。项目的的主要目标是降低重复性,实现可重现的镜像构建,支持直接挂载以及提供内存安全保证,其中一些灵感来自 OCIv2 设计文档 [7]。 650 | 651 | 通过使用内容定义的分块算法 FastCDC,实现了降低重复性。这种实现允许分块在层之间共享。从现有层构建新层时,可以重用大部分分块。 652 | 653 | 项目的另一个目标是实现可重现的镜像构建,通过定义镜像格式的正规表示来实现。 654 | 655 | 直接挂载支持是 PuzzleFS 的关键特性之一,与 fs-verity 一起提供数据完整性。目前,PuzzleFS 作为用户空间文件系统(FUSE)实现。一个只读内核文件系统驱动正在进行中。 656 | 657 | 最后,内存安全性对 PuzzleFS 至关重要,导致决定用 Rust 实现。另一个目标是实现用户空间和内核空间共享相同代码,以提供安全的实现。 658 | 659 | * [1] https://github.com/anuvu/puzzlefs 660 | * [2] https://github.com/wedsonaf/linux/tree/fs 661 | * [3] https://github.com/Rust-for-Linux/linux/issues/1004 662 | * [4] https://github.com/Rust-for-Linux/linux/pull/1007 663 | * [5] https://docs.rs/serde_cbor/latest/serde_cbor/ 664 | * [6] https://docs.rs/hex/0.4.3/hex/ 665 | * [7] https://hackmd.io/@cyphar/ociv2-brainstorm 666 | * [8] https://www.youtube.com/watch?v=tPs1uRqOnlk 667 | * [9] https://github.com/Rust-for-Linux/linux/tree/rust-next 668 | * [10] https://docs.kernel.org/rust/quick-start.html 669 | * [11] https://github.com/ariel-miculas/puzzlefs/tree/support-no-verity-data 670 | * [12] https://archive.fosdem.org/2019/schedule/event/containers_atomfs/ 671 | 672 | ```rust 673 | Ariel Miculas (58): 674 | rust: kernel: add libraries required by the filesystem abstractions 675 | rust: kernel: backport the delay module from the rust branch 676 | rust: kernel: add container_of macro 677 | rust: kernel: add offset_of macro 678 | drop: Add crate::pr_warn declaration 679 | rust: kernel: rename from_kernel_errno to from_errno 680 | rust: kernel: Rename from_pointer to from_foreing and into_pointer to 681 | into_foreign 682 | rust: kernel: add count_paren_items macro, needed by define_fs_params 683 | macro 684 | rust: helpers: add missing rust helper 'alloc_pages' 685 | kernel: configs: add qemu-busybox-min.config 686 | rust: kernel: format the rust code 687 | samples: puzzlefs: add initial puzzlefs sample, copied from rust_fs.rs 688 | kernel: configs: enable rust samples in rust.config 689 | Add SAMPLE_RUST_SERDE in rust.config 690 | rust: kernel: fix compile errors after rebase to rust-next 691 | rust: serde_cbor: import crate 692 | rust: serde_cbor: add SPDX License Identifiers 693 | rust: serde_cbor: add no_fp_fmt_parse support 694 | rust: Kbuild: enable serde_cbor 695 | samples: rust: add cbor serialize/deserialize example 696 | rust: serde_cbor: add support for serde_cbor's from_slice method by 697 | using a custom alloc_kernel feature 698 | rust: serde: add support for deserializing Vec with kernel_alloc 699 | feature 700 | rust: file: Replace UnsafeCell with Opaque for File 701 | rust: kernel: implement fmt::Debug for CString 702 | samples: puzzlefs: rename RustFs to PuzzleFs 703 | samples: puzzlefs: add basic deserializing support for the puzzlefs 704 | metadata 705 | rust: file: present the filesystem context to the open function 706 | rust: kernel: add an abstraction over vfsmount to allow cloning a new 707 | private mount 708 | rust: file: add from_path, from_path_in_root_mnt and read_with_offset 709 | methods to File 710 | samples: puzzlefs: pass the Vfsmount structure from open to read and 711 | return the contents of the data file inside /home/puzzlefs_oci 712 | rust: file: move from_path, from_path_in_root_mnt and read_with_offset 713 | methods to a RegularFile newtype 714 | rust: file: ensure RegularFile can only create regular files 715 | rust: file: add get_pos method to RegularFile 716 | rust: file: add methods read_to_end, get_file_size and update_pos to 717 | RegularFile 718 | rust: file: define a minimal Read trait and implement it for 719 | RegularFile 720 | samples: puzzlefs: add cbor_get_array_size method 721 | samples: puzzlefs: add KernelError to WireFormatError and implement 722 | From conversion 723 | samples: puzzlefs: implement new for MetadataBlob 724 | samples: puzzlefs: build puzzlefs into the kernel, thus avoiding the 725 | need to export rust symbols 726 | rust: alloc: add try_clone for Vec 727 | rust: alloc: add from_iter_fallible for Vec 728 | samples: puzzlefs: implement to_errno and from_errno for 729 | WireFormatError 730 | samples: puzzlefs: add TryReserveError (and from conversion) to 731 | WireFormatError 732 | samples: puzzlefs: add higher level inode related functionality 733 | samples: puzzlefs: populate the directory entries with the inodes from 734 | the puzzlefs metadata file 735 | rust: hex: import crate 736 | rust: hex: add SPDX license identifiers 737 | rust: Kbuild: enable `hex` 738 | rust: hex: implement FromHex trait and hex::decode using a custom 739 | kernel_alloc feature 740 | rust: hex: add encode_hex_iter and encode_hex_upper_iter methods 741 | rust: puzzlefs: add HexError to WireFormatError and implement the From 742 | conversion 743 | rust: puzzlefs: display the error value for 744 | WireFormatError::KernelError 745 | samples: puzzlefs: add Rootfs and Digest structs to types.rs 746 | samples: puzzlefs: implement the conversion from WireFormatError to 747 | kernel::error::Error 748 | rust: puzzlefs: read the puzzlefs image manifest instead of an 749 | individual metadata layer 750 | rust: puzzlefs: rename PuzzleFs to PuzzleFsModule to avoid confusion 751 | with the PuzzleFS struct 752 | rust: puzzlefs: add support for reading files 753 | rust: puzzlefs: add oci_root_dir and image_manifest filesystem 754 | parameters 755 | ​ 756 | Miguel Ojeda (15): 757 | rust: proc-macro2: import crate 758 | rust: proc-macro2: add SPDX License Identifiers 759 | rust: proc-macro2: remove `unicode_ident` dependency 760 | rust: quote: import crate 761 | rust: quote: add SPDX License Identifiers 762 | rust: syn: import crate 763 | rust: syn: add SPDX License Identifiers 764 | rust: syn: remove `unicode-ident` dependency 765 | rust: serde: import crate 766 | rust: serde: add `no_fp_fmt_parse` support 767 | rust: serde: add SPDX License Identifiers 768 | rust: serde_derive: import crate 769 | rust: serde_derive: add SPDX License Identifiers 770 | rust: Kbuild: enable `proc-macro2`, `quote`, `syn`, `serde` and 771 | `serde_derive` 772 | rust: test `serde` support 773 | ​ 774 | Wedson Almeida Filho (7): 775 | rust: add definitions for ref-counted inodes and dentries 776 | rust: add ability to register a file system 777 | rust: define fs context 778 | rust: add support for file system parameters 779 | rust: allow fs driver to initialise new superblocks 780 | rust: add `module_fs` macro 781 | WIP: rust: allow fs to be populated 782 | ​ 783 | Makefile | 14 +- 784 | arch/x86/configs/qemu-busybox-min.config | 11 + 785 | kernel/configs/qemu-busybox-min.config | 56 + 786 | kernel/configs/rust.config | 11 + 787 | rust/.gitignore | 1 + 788 | rust/Makefile | 232 +- 789 | rust/alloc/vec/mod.rs | 48 + 790 | rust/bindings/bindings_helper.h | 14 + 791 | rust/bindings/lib.rs | 5 + 792 | rust/helpers.c | 76 + 793 | rust/hex/error.rs | 78 + 794 | rust/hex/lib.rs | 506 +++ 795 | rust/hex/serde.rs | 104 + 796 | rust/kernel/cred.rs | 46 + 797 | rust/kernel/delay.rs | 104 + 798 | rust/kernel/driver.rs | 28 + 799 | rust/kernel/error.rs | 52 +- 800 | rust/kernel/file.rs | 1117 ++++++ 801 | rust/kernel/fs.rs | 1478 ++++++++ 802 | rust/kernel/fs/param.rs | 558 +++ 803 | rust/kernel/io_buffer.rs | 153 + 804 | rust/kernel/iov_iter.rs | 81 + 805 | rust/kernel/lib.rs | 83 + 806 | rust/kernel/mm.rs | 149 + 807 | rust/kernel/mount.rs | 66 + 808 | rust/kernel/pages.rs | 144 + 809 | rust/kernel/str.rs | 6 + 810 | rust/kernel/test_serde.rs | 26 + 811 | rust/kernel/test_serde/de.rs | 439 +++ 812 | rust/kernel/test_serde/error.rs | 73 + 813 | rust/kernel/test_serde/ser.rs | 466 +++ 814 | rust/kernel/user_ptr.rs | 175 + 815 | rust/proc-macro2/detection.rs | 77 + 816 | rust/proc-macro2/fallback.rs | 1004 ++++++ 817 | rust/proc-macro2/lib.rs | 1341 ++++++++ 818 | rust/proc-macro2/marker.rs | 20 + 819 | rust/proc-macro2/parse.rs | 874 +++++ 820 | rust/proc-macro2/rcvec.rs | 144 + 821 | rust/proc-macro2/wrapper.rs | 996 ++++++ 822 | rust/quote/ext.rs | 112 + 823 | rust/quote/format.rs | 170 + 824 | rust/quote/ident_fragment.rs | 88 + 825 | rust/quote/lib.rs | 1436 ++++++++ 826 | rust/quote/runtime.rs | 440 +++ 827 | rust/quote/spanned.rs | 45 + 828 | rust/quote/to_tokens.rs | 211 ++ 829 | rust/serde/de/format.rs | 32 + 830 | rust/serde/de/ignored_any.rs | 246 ++ 831 | rust/serde/de/impls.rs | 2755 +++++++++++++++ 832 | rust/serde/de/mod.rs | 2313 +++++++++++++ 833 | rust/serde/de/seed.rs | 21 + 834 | rust/serde/de/utf8.rs | 48 + 835 | rust/serde/de/value.rs | 1718 ++++++++++ 836 | rust/serde/integer128.rs | 84 + 837 | rust/serde/lib.rs | 351 ++ 838 | rust/serde/macros.rs | 238 ++ 839 | rust/serde/private/de.rs | 2997 ++++++++++++++++ 840 | rust/serde/private/doc.rs | 161 + 841 | rust/serde/private/mod.rs | 52 + 842 | rust/serde/private/ser.rs | 1316 +++++++ 843 | rust/serde/private/size_hint.rs | 23 + 844 | rust/serde/ser/fmt.rs | 180 + 845 | rust/serde/ser/impls.rs | 987 ++++++ 846 | rust/serde/ser/impossible.rs | 218 ++ 847 | rust/serde/ser/mod.rs | 1992 +++++++++++ 848 | rust/serde/std_error.rs | 50 + 849 | rust/serde_cbor/de.rs | 1370 ++++++++ 850 | rust/serde_cbor/error.rs | 320 ++ 851 | rust/serde_cbor/lib.rs | 371 ++ 852 | rust/serde_cbor/read.rs | 647 ++++ 853 | rust/serde_cbor/ser.rs | 748 ++++ 854 | rust/serde_cbor/tags.rs | 224 ++ 855 | rust/serde_cbor/value/de.rs | 168 + 856 | rust/serde_cbor/value/mod.rs | 158 + 857 | rust/serde_cbor/value/ser.rs | 447 +++ 858 | rust/serde_cbor/write.rs | 177 + 859 | rust/serde_derive/bound.rs | 408 +++ 860 | rust/serde_derive/de.rs | 3148 +++++++++++++++++ 861 | rust/serde_derive/dummy.rs | 46 + 862 | rust/serde_derive/fragment.rs | 76 + 863 | rust/serde_derive/internals/ast.rs | 204 ++ 864 | rust/serde_derive/internals/attr.rs | 1908 +++++++++++ 865 | rust/serde_derive/internals/case.rs | 199 ++ 866 | rust/serde_derive/internals/check.rs | 445 +++ 867 | rust/serde_derive/internals/ctxt.rs | 64 + 868 | rust/serde_derive/internals/mod.rs | 30 + 869 | rust/serde_derive/internals/receiver.rs | 287 ++ 870 | rust/serde_derive/internals/respan.rs | 18 + 871 | rust/serde_derive/internals/symbol.rs | 71 + 872 | rust/serde_derive/lib.rs | 112 + 873 | rust/serde_derive/pretend.rs | 203 ++ 874 | rust/serde_derive/ser.rs | 1342 ++++++++ 875 | rust/serde_derive/this.rs | 34 + 876 | rust/serde_derive/try.rs | 26 + 877 | rust/syn/attr.rs | 664 ++++ 878 | rust/syn/await.rs | 4 + 879 | rust/syn/bigint.rs | 68 + 880 | rust/syn/buffer.rs | 400 +++ 881 | rust/syn/custom_keyword.rs | 255 ++ 882 | rust/syn/custom_punctuation.rs | 302 ++ 883 | rust/syn/data.rs | 495 +++ 884 | rust/syn/derive.rs | 276 ++ 885 | rust/syn/discouraged.rs | 196 ++ 886 | rust/syn/error.rs | 430 +++ 887 | rust/syn/export.rs | 41 + 888 | rust/syn/expr.rs | 3560 +++++++++++++++++++ 889 | rust/syn/ext.rs | 141 + 890 | rust/syn/file.rs | 127 + 891 | rust/syn/gen/clone.rs | 2243 ++++++++++++ 892 | rust/syn/gen/debug.rs | 3044 +++++++++++++++++ 893 | rust/syn/gen/eq.rs | 2197 ++++++++++++ 894 | rust/syn/gen/fold.rs | 3343 ++++++++++++++++++ 895 | rust/syn/gen/hash.rs | 2871 ++++++++++++++++ 896 | rust/syn/gen/visit.rs | 3788 +++++++++++++++++++++ 897 | rust/syn/gen/visit_mut.rs | 3788 +++++++++++++++++++++ 898 | rust/syn/gen_helper.rs | 156 + 899 | rust/syn/generics.rs | 1339 ++++++++ 900 | rust/syn/group.rs | 284 ++ 901 | rust/syn/ident.rs | 103 + 902 | rust/syn/item.rs | 3315 ++++++++++++++++++ 903 | rust/syn/lib.rs | 985 ++++++ 904 | rust/syn/lifetime.rs | 156 + 905 | rust/syn/lit.rs | 1602 +++++++++ 906 | rust/syn/lookahead.rs | 171 + 907 | rust/syn/mac.rs | 221 ++ 908 | rust/syn/macros.rs | 179 + 909 | rust/syn/op.rs | 236 ++ 910 | rust/syn/parse.rs | 1316 +++++++ 911 | rust/syn/parse_macro_input.rs | 181 + 912 | rust/syn/parse_quote.rs | 169 + 913 | rust/syn/pat.rs | 929 +++++ 914 | rust/syn/path.rs | 856 +++++ 915 | rust/syn/print.rs | 18 + 916 | rust/syn/punctuated.rs | 1070 ++++++ 917 | rust/syn/reserved.rs | 46 + 918 | rust/syn/sealed.rs | 6 + 919 | rust/syn/span.rs | 69 + 920 | rust/syn/spanned.rs | 116 + 921 | rust/syn/stmt.rs | 351 ++ 922 | rust/syn/thread.rs | 43 + 923 | rust/syn/token.rs | 1015 ++++++ 924 | rust/syn/tt.rs | 109 + 925 | rust/syn/ty.rs | 1288 +++++++ 926 | rust/syn/verbatim.rs | 35 + 927 | rust/syn/whitespace.rs | 67 + 928 | samples/rust/Kconfig | 28 + 929 | samples/rust/Makefile | 3 + 930 | samples/rust/local_data_format/de.rs | 422 +++ 931 | samples/rust/local_data_format/error.rs | 73 + 932 | samples/rust/local_data_format/ser.rs | 443 +++ 933 | samples/rust/puzzle.rs | 4 + 934 | samples/rust/puzzle/error.rs | 91 + 935 | samples/rust/puzzle/inode.rs | 150 + 936 | samples/rust/puzzle/oci.rs | 71 + 937 | samples/rust/puzzle/types.rs | 389 +++ 938 | samples/rust/puzzle/types/cbor_helpers.rs | 50 + 939 | samples/rust/puzzlefs.rs | 220 ++ 940 | samples/rust/rust_fs.rs | 105 + 941 | samples/rust/rust_serde.rs | 125 + 942 | scripts/Makefile.build | 4 +- 943 | 160 files changed, 89204 insertions(+), 29 deletions(-) 944 | create mode 100644 arch/x86/configs/qemu-busybox-min.config 945 | create mode 100644 kernel/configs/qemu-busybox-min.config 946 | create mode 100644 rust/hex/error.rs 947 | create mode 100644 rust/hex/lib.rs 948 | create mode 100644 rust/hex/serde.rs 949 | create mode 100644 rust/kernel/cred.rs 950 | create mode 100644 rust/kernel/delay.rs 951 | create mode 100644 rust/kernel/driver.rs 952 | create mode 100644 rust/kernel/file.rs 953 | create mode 100644 rust/kernel/fs.rs 954 | create mode 100644 rust/kernel/fs/param.rs 955 | create mode 100644 rust/kernel/io_buffer.rs 956 | create mode 100644 rust/kernel/iov_iter.rs 957 | create mode 100644 rust/kernel/mm.rs 958 | create mode 100644 rust/kernel/mount.rs 959 | create mode 100644 rust/kernel/pages.rs 960 | create mode 100644 rust/kernel/test_serde.rs 961 | create mode 100644 rust/kernel/test_serde/de.rs 962 | create mode 100644 rust/kernel/test_serde/error.rs 963 | create mode 100644 rust/kernel/test_serde/ser.rs 964 | create mode 100644 rust/kernel/user_ptr.rs 965 | create mode 100644 rust/proc-macro2/detection.rs 966 | create mode 100644 rust/proc-macro2/fallback.rs 967 | create mode 100644 rust/proc-macro2/lib.rs 968 | create mode 100644 rust/proc-macro2/marker.rs 969 | create mode 100644 rust/proc-macro2/parse.rs 970 | create mode 100644 rust/proc-macro2/rcvec.rs 971 | create mode 100644 rust/proc-macro2/wrapper.rs 972 | create mode 100644 rust/quote/ext.rs 973 | create mode 100644 rust/quote/format.rs 974 | create mode 100644 rust/quote/ident_fragment.rs 975 | create mode 100644 rust/quote/lib.rs 976 | create mode 100644 rust/quote/runtime.rs 977 | create mode 100644 rust/quote/spanned.rs 978 | create mode 100644 rust/quote/to_tokens.rs 979 | create mode 100644 rust/serde/de/format.rs 980 | create mode 100644 rust/serde/de/ignored_any.rs 981 | create mode 100644 rust/serde/de/impls.rs 982 | create mode 100644 rust/serde/de/mod.rs 983 | create mode 100644 rust/serde/de/seed.rs 984 | create mode 100644 rust/serde/de/utf8.rs 985 | create mode 100644 rust/serde/de/value.rs 986 | create mode 100644 rust/serde/integer128.rs 987 | create mode 100644 rust/serde/lib.rs 988 | create mode 100644 rust/serde/macros.rs 989 | create mode 100644 rust/serde/private/de.rs 990 | create mode 100644 rust/serde/private/doc.rs 991 | create mode 100644 rust/serde/private/mod.rs 992 | create mode 100644 rust/serde/private/ser.rs 993 | create mode 100644 rust/serde/private/size_hint.rs 994 | create mode 100644 rust/serde/ser/fmt.rs 995 | create mode 100644 rust/serde/ser/impls.rs 996 | create mode 100644 rust/serde/ser/impossible.rs 997 | create mode 100644 rust/serde/ser/mod.rs 998 | create mode 100644 rust/serde/std_error.rs 999 | create mode 100644 rust/serde_cbor/de.rs 1000 | create mode 100644 rust/serde_cbor/error.rs 1001 | create mode 100644 rust/serde_cbor/lib.rs 1002 | create mode 100644 rust/serde_cbor/read.rs 1003 | create mode 100644 rust/serde_cbor/ser.rs 1004 | create mode 100644 rust/serde_cbor/tags.rs 1005 | create mode 100644 rust/serde_cbor/value/de.rs 1006 | create mode 100644 rust/serde_cbor/value/mod.rs 1007 | create mode 100644 rust/serde_cbor/value/ser.rs 1008 | create mode 100644 rust/serde_cbor/write.rs 1009 | create mode 100644 rust/serde_derive/bound.rs 1010 | create mode 100644 rust/serde_derive/de.rs 1011 | create mode 100644 rust/serde_derive/dummy.rs 1012 | create mode 100644 rust/serde_derive/fragment.rs 1013 | create mode 100644 rust/serde_derive/internals/ast.rs 1014 | create mode 100644 rust/serde_derive/internals/attr.rs 1015 | create mode 100644 rust/serde_derive/internals/case.rs 1016 | create mode 100644 rust/serde_derive/internals/check.rs 1017 | create mode 100644 rust/serde_derive/internals/ctxt.rs 1018 | create mode 100644 rust/serde_derive/internals/mod.rs 1019 | create mode 100644 rust/serde_derive/internals/receiver.rs 1020 | create mode 100644 rust/serde_derive/internals/respan.rs 1021 | create mode 100644 rust/serde_derive/internals/symbol.rs 1022 | create mode 100644 rust/serde_derive/lib.rs 1023 | create mode 100644 rust/serde_derive/pretend.rs 1024 | create mode 100644 rust/serde_derive/ser.rs 1025 | create mode 100644 rust/serde_derive/this.rs 1026 | create mode 100644 rust/serde_derive/try.rs 1027 | create mode 100644 rust/syn/attr.rs 1028 | create mode 100644 rust/syn/await.rs 1029 | create mode 100644 rust/syn/bigint.rs 1030 | create mode 100644 rust/syn/buffer.rs 1031 | create mode 100644 rust/syn/custom_keyword.rs 1032 | create mode 100644 rust/syn/custom_punctuation.rs 1033 | create mode 100644 rust/syn/data.rs 1034 | create mode 100644 rust/syn/derive.rs 1035 | create mode 100644 rust/syn/discouraged.rs 1036 | create mode 100644 rust/syn/error.rs 1037 | create mode 100644 rust/syn/export.rs 1038 | create mode 100644 rust/syn/expr.rs 1039 | create mode 100644 rust/syn/ext.rs 1040 | create mode 100644 rust/syn/file.rs 1041 | create mode 100644 rust/syn/gen/clone.rs 1042 | create mode 100644 rust/syn/gen/debug.rs 1043 | create mode 100644 rust/syn/gen/eq.rs 1044 | create mode 100644 rust/syn/gen/fold.rs 1045 | create mode 100644 rust/syn/gen/hash.rs 1046 | create mode 100644 rust/syn/gen/visit.rs 1047 | create mode 100644 rust/syn/gen/visit_mut.rs 1048 | create mode 100644 rust/syn/gen_helper.rs 1049 | create mode 100644 rust/syn/generics.rs 1050 | create mode 100644 rust/syn/group.rs 1051 | create mode 100644 rust/syn/ident.rs 1052 | create mode 100644 rust/syn/item.rs 1053 | create mode 100644 rust/syn/lib.rs 1054 | create mode 100644 rust/syn/lifetime.rs 1055 | create mode 100644 rust/syn/lit.rs 1056 | create mode 100644 rust/syn/lookahead.rs 1057 | create mode 100644 rust/syn/mac.rs 1058 | create mode 100644 rust/syn/macros.rs 1059 | create mode 100644 rust/syn/op.rs 1060 | create mode 100644 rust/syn/parse.rs 1061 | create mode 100644 rust/syn/parse_macro_input.rs 1062 | create mode 100644 rust/syn/parse_quote.rs 1063 | create mode 100644 rust/syn/pat.rs 1064 | create mode 100644 rust/syn/path.rs 1065 | create mode 100644 rust/syn/print.rs 1066 | create mode 100644 rust/syn/punctuated.rs 1067 | create mode 100644 rust/syn/reserved.rs 1068 | create mode 100644 rust/syn/sealed.rs 1069 | create mode 100644 rust/syn/span.rs 1070 | create mode 100644 rust/syn/spanned.rs 1071 | create mode 100644 rust/syn/stmt.rs 1072 | create mode 100644 rust/syn/thread.rs 1073 | create mode 100644 rust/syn/token.rs 1074 | create mode 100644 rust/syn/tt.rs 1075 | create mode 100644 rust/syn/ty.rs 1076 | create mode 100644 rust/syn/verbatim.rs 1077 | create mode 100644 rust/syn/whitespace.rs 1078 | create mode 100644 samples/rust/local_data_format/de.rs 1079 | create mode 100644 samples/rust/local_data_format/error.rs 1080 | create mode 100644 samples/rust/local_data_format/ser.rs 1081 | create mode 100644 samples/rust/puzzle.rs 1082 | create mode 100644 samples/rust/puzzle/error.rs 1083 | create mode 100644 samples/rust/puzzle/inode.rs 1084 | create mode 100644 samples/rust/puzzle/oci.rs 1085 | create mode 100644 samples/rust/puzzle/types.rs 1086 | create mode 100644 samples/rust/puzzle/types/cbor_helpers.rs 1087 | create mode 100644 samples/rust/puzzlefs.rs 1088 | create mode 100644 samples/rust/rust_fs.rs 1089 | create mode 100644 samples/rust/rust_serde.rs 1090 | ``` 1091 | -------------------------------------------------------------------------------- /优秀文档参考/Rust_for_Linux驱动模块开发报告-周睿.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust for Linux驱动模块开发报告-周睿 3 | Date: 2023-11 4 | Tags: 5 | - Author: 周睿 6 | - Reports Repo: https://github.com/ZR233/learn_rust_for_linux 7 | --- 8 | 9 | # Learn Rust-for-Linux 10 | 11 | ## Day 1 12 | 13 | 今天的目标是编译并在`Qemu`上运行`kernel`。 14 | 源码相当大,有4.5G,还需要给生成留出空间,感觉至少得给20G。 15 | 编译`Qemu` 16 | 17 | ```shell 18 | sudo apt-get install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev libaio-dev libbluetooth-dev libbrlapi-dev libbz2-dev libnfs-dev libiscsi-dev ninja-build python3 python3-pip python3-venv 19 | 20 | pip install sphinx sphinx-rtd-theme 21 | 22 | 23 | git clone git@github.com:qemu/qemu.git 24 | cd qemu 25 | mkdir build 26 | cd build 27 | ../configure --target-list=riscv64-softmmu,riscv64-linux-user,aarch64-softmmu,aarch64-linux-user,x86_64-softmmu,x86_64-linux-user 28 | make -j4 29 | sudo make install 30 | ``` 31 | 32 | 编译`BusyBox`: 33 | 34 | ```shell 35 | wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 36 | tar xvf busybox-1.36.1.tar.bz2 37 | cd busybox-1.36.1/ 38 | make ARCH=arm64 menuconfig -j4 39 | make ARCH=arm64 install -j4 40 | 41 | ``` 42 | 43 | 基于BusyBox制作initrd 44 | 制作`initrd`步骤略多。这里`make_initrd.sh`就是用来自动制作`initrd`的脚本,内容: 45 | ```shell 46 | #!/bin/bash 47 | 48 | MOUNT_DIR=mnt 49 | CURR_DIR=`pwd` 50 | 51 | rm initrd.ext4 52 | dd if=/dev/zero of=initrd.ext4 bs=1M count=32 53 | mkfs.ext4 initrd.ext4 54 | 55 | mkdir -p $MOUNT_DIR 56 | mount initrd.ext4 $MOUNT_DIR 57 | cp -arf busybox-1.35.0/_install/* $MOUNT_DIR 58 | 59 | cd $MOUNT_DIR 60 | mkdir -p etc dev mnt proc sys tmp mnt etc/init.d/ 61 | 62 | echo "proc /proc proc defaults 0 0" > etc/fstab 63 | echo "tmpfs /tmp tmpfs defaults 0 0" >> etc/fstab 64 | echo "sysfs /sys sysfs defaults 0 0" >> etc/fstab 65 | 66 | echo "#!/bin/sh" > etc/init.d/rcS 67 | echo "mount -a" >> etc/init.d/rcS 68 | echo "mount -o remount,rw /" >> etc/init.d/rcS 69 | echo "echo -e \"Welcome to ARM64 Linux\"" >> etc/init.d/rcS 70 | chmod 755 etc/init.d/rcS 71 | 72 | echo "::sysinit:/etc/init.d/rcS" > etc/inittab 73 | echo "::respawn:-/bin/sh" >> etc/inittab 74 | echo "::askfirst:-/bin/sh" >> etc/inittab 75 | chmod 755 etc/inittab 76 | 77 | cd dev 78 | mknod console c 5 1 79 | mknod null c 1 3 80 | mknod tty1 c 4 1 81 | 82 | cd $CURR_DIR 83 | umount $MOUNT_DIR 84 | echo "make initrd ok!" 85 | 86 | ``` 87 | 88 | 然后必须 `sudo` 执行 `sudo ./make_inird.sh` 89 | 90 | 接着,`Qemu`启动! 91 | 92 | ```shell 93 | qemu-system-aarch64 \ 94 | -nographic \ 95 | -M virt \ 96 | -cpu cortex-a57 \ 97 | -smp 2 \ 98 | -m 4G \ 99 | -kernel /mnt/sdb/dev/linux/build/arch/arm64/boot \ 100 | -append "nokaslr root=/dev/ram init=/linuxrc console=ttyS0" \ 101 | -initrd disk.img 102 | ``` 103 | 104 | ![image](https://github.com/ZR233/learn_rust_for_linux/blob/main/image/qemu1.png) 105 | 106 | 果然第一次就失败了。似乎是文件系统没做对 107 | 108 | ## Day2 109 | 110 | 仔细查看`qemu-system-aarch64 -h`,发现指令 `-initrd file use 'file' as initial ram disk` 将文件挂载为ram,而默认`kernel`未添加`ram`支持,应该改为挂载`-hda/-hdb file use 'file' as hard disk 0/1 image`,所以运行命令修改为 111 | ```shell 112 | #!/bin/bash 113 | 114 | qemu-system-aarch64 \ 115 | -nographic \ 116 | -M virt \ 117 | -cpu cortex-a57 \ 118 | -smp 2 \ 119 | -m 4G \ 120 | -kernel /mnt/sdb/dev/linux/build/arch/arm64/boot/Image \ 121 | -append "nokaslr root=/dev/sda init=/linuxrc console=ttyAMA0 console=ttyS0" \ 122 | -hda disk.ext3 123 | ``` 124 | 125 | 运行,依然报错: 126 | 127 | ```log 128 | [ 0.927382] uart-pl011 9000000.pl011: no DMA platform data 129 | [ 0.932510] /dev/root: Can't open blockdev 130 | [ 0.933619] VFS: Cannot open root device "/dev/sda" or unknown-block(0,0): error -6 131 | [ 0.933919] Please append a correct "root=" boot option; here are the available partitions: 132 | [ 0.934481] fe00 32768 vda 133 | [ 0.934649] driver: virtio_blk 134 | [ 0.934939] 1f00 131072 mtdblock0 135 | [ 0.934963] (driver?) 136 | ``` 137 | 138 | 可以看出是root不对,应该改成vda: 139 | 140 | ```shell 141 | #!/bin/bash 142 | 143 | qemu-system-aarch64 \ 144 | -nographic \ 145 | -M virt \ 146 | -cpu cortex-a57 \ 147 | -smp 2 \ 148 | -m 4G \ 149 | -kernel /mnt/sdb/dev/linux/build/arch/arm64/boot/Image \ 150 | -append "nokaslr root=/dev/vda init=/linuxrc console=ttyAMA0 console=ttyS0" \ 151 | -hda disk.ext3 152 | ``` 153 | 154 | 再次运行: 155 | 156 | ![image](https://github.com/ZR233/learn_rust_for_linux/blob/main/image/qemu_ok.png) 157 | 158 | 成功! 159 | 160 | 161 | ## Day 3 162 | 163 | 今天写一个驱动的`helloworld`。 164 | 首先为`kernel`添加`rust-analyzer`支持。在源码根目录执行: 165 | ```shell 166 | make ARCH=arm64 LLVM=1 rust-analyzer 167 | ``` 168 | 会在`build`目录生成`rust-project.json`,在`.vscode`的`settings.json`添加如下: 169 | ```json5 170 | { 171 | "rust-analyzer.linkedProjects": [ 172 | "./build/rust-project.json" 173 | ] 174 | } 175 | ``` 176 | 就可以为源码添加提示啦。 177 | 之后如教程所讲,添加`rust_helloworld`模块,编译后挂载文件系统,找个文件夹放进去,启动`qemu`,日志等级调成`info`: `dmesg -n 7`,加载驱动,成功。 178 | 179 | ## Day4 180 | 181 | 休息一天。 182 | 183 | ## Day5 184 | 185 | 开始做e1000驱动,首先尝试使用`fujita`的`kernel fork`,发现他的版本较低,用的`rust`版本为`1.66`,本着学习就学最新的原则,准备直接用官方最新版本修改使用。 186 | `fork`了官方版本,发现`rust`只支持`x86`,对比了`rust-dev`分支,将修改支持`arm64`的部分拷贝过来,编译并运行成功。 187 | 188 | ## Day6 189 | 190 | 对比`fujita`的分支,补全官方没有的`API`。 191 | 补了一天,发现需要改动的太多,6.6 的api也有部分变动,后面的实验也不用这个项目,所以还是老实用`fujita`的吧。 192 | 193 | ## Day7 194 | 195 | 经过前两天对内核源码的捣鼓,有了些许了解,编译`kernel`,修改代码,练习3轻松完成。 196 | 197 | 198 | ## Day8 199 | 200 | 使用`busybox`需要手动添加`/etc/network/interfaces`文件: 201 | 202 | ``` 203 | auto lo 204 | iface lo inet loopback 205 | auto eth0 206 | iface eth0 inet dhcp 207 | ``` 208 | 209 | 参考`e1000`驱动源码,填写练习4内容,始终无法获取网络,编译了原版`e1000`驱动测试,也不成功,看来是`qemu`配置问题。 210 | 211 | ## Day9 212 | 213 | 到处搜索资料并测试不同配置,发现问题出在没有配置本地回环地址,执行以下语句: 214 | 215 | ```shell 216 | # 添加本地回环 217 | ifconfig lo 127.0.0.1 218 | # 启动网卡 219 | ifconfig eth0 up 220 | ``` 221 | 222 | 成功`ping`通。 223 | 224 | 225 | ## Day 17 226 | 227 | riscv环境配置 228 | 229 | busybox 编译 230 | 231 | ```shell 232 | make O=build_riscv ARCH=riscv64 CROSS_COMPILE=riscv64-linux-gnu- menuconfig -j4 233 | 234 | # setting 中打开 build static 235 | 236 | make O=build_riscv ARCH=riscv64 CROSS_COMPILE=riscv64-linux-gnu- install -j4 237 | ``` 238 | 239 | linux 编译 240 | 241 | ```shell 242 | make LLVM=1 ARCH=riscv defconfig -j4 243 | make LLVM=1 ARCH=riscv menuconfig -j4 244 | make LLVM=1 ARCH=riscv rust-analyzer 245 | bear -- make LLVM=1 ARCH=riscv -j4 246 | ``` 247 | 248 | ## Day 18 249 | 250 | 给 rust-dev 添加riscv支持: 251 | ```makefile 252 | # scripts/Makefile 253 | 254 | 255 | # 22行添加,使其生成target 配置 256 | ifdef CONFIG_RISCV 257 | always-$(CONFIG_RUST) += target.json 258 | filechk_rust_target = $< < include/config/auto.conf 259 | 260 | $(obj)/target.json: scripts/generate_rust_target include/config/auto.conf FORCE 261 | $(call filechk,rust_target) 262 | endif 263 | ``` 264 | 265 | ```rust 266 | # scripts/generate_rust_target.rs 267 | 268 | else if cfg.has("RISCV") { 269 | if cfg.has("64BIT") { 270 | ts.push("arch", "riscv64"); 271 | ts.push("data-layout", "e-m:e-p:64:64-i64:64-i128:128-n64-S128"); 272 | ts.push("llvm-target", "riscv64-linux-gnu"); 273 | ts.push("target-pointer-width", "64"); 274 | } else { 275 | ts.push("arch", "riscv32"); 276 | ts.push("data-layout", "e-m:e-p:32:32-i64:64-n32-S128"); 277 | ts.push("llvm-target", "riscv32-linux-gnu"); 278 | ts.push("target-pointer-width", "32"); 279 | } 280 | ts.push("code-model", "medium"); 281 | ts.push("disable-redzone", true); 282 | let mut features = "+m,+a".to_string(); 283 | if cfg.has("RISCV_ISA_C") { 284 | features += ",+c"; 285 | } 286 | ts.push("features", features); 287 | } 288 | 289 | ``` 290 | 291 | 292 | ```Makefile 293 | # arch/riscv/Makefile 294 | 295 | KBUILD_RUSTFLAGS += --target=$(objtree)/scripts/target.json 296 | 297 | ``` 298 | 299 | 尝试编译,成功。 300 | -------------------------------------------------------------------------------- /优秀文档参考/Rust_for_Linux驱动模块开发报告-杨凯.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust for Linux驱动模块开发报告-杨凯 3 | Date: 2023-11 4 | Tags: 5 | - Author: 杨凯 6 | - Reports Repo: https://github.com/guevaraya/rust-for-linux/ 7 | --- 8 | 9 | # 学习记录 10 | 11 | ### 问题1 12 | 代码下载,由于rust-for-linux的官方代码全镜像约为4G,国内网络想下载下来是很难的,因此需要添加--depth=1 并指定分支rust-dev,但是太慢 13 | 最终还是用下面命令完成下载: 14 | 15 | ``` 16 | git clone https://github.com/fujita/linux.git -b rust-e1000 17 | 18 | ``` 19 | 20 | 由于问题3,最后还是从官网下 21 | git clone -b rust-dev --depth=1 22 | 23 | ### 问题2 24 | 25 | 根据脚本 scripts/min-tool-version.sh分析 26 | rustup override set $(scripts/min-tool-version.sh rustc) 相当于 rustup override set 1.62.0 27 | cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli相当于 28 | cargo install --locked --version 0.56.0 bindgen-cli 安装 29 | 30 | ### 问题3 31 | 32 | menuconfig搞了半天发现没有找到所谓的rust support选项,经过对比config.in发现 33 | 34 | ``` 35 | rust_is_available=n, 36 | cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli 37 | Blocking waiting for file lock on package cache 38 | Updating `ustc` index 39 | 40 | vierror: could not find `bindgen-cli` in registry `crates-io` with version `=0.56.0` 41 | 42 | ``` 43 | 44 | 根因还是bindgen没有安装好,在crates-io官网搜索bindgen,确实最新的版本是0.65.1,但是fujita下载的版本太低,适应官网地址编译,解决了问题 45 | 46 | ``` 47 | cat ~/.cargo/config 48 | [source.crates-io] 49 | registry = "https://github.com/rust-lang/crates.io-index" 50 | replace-with = 'ustc' 51 | [source.ustc] 52 | # registry = "https://mirrors.ustc.edu.cn/crates.io-index" 53 | registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git" 54 | [http] 55 | check-revoke = false 56 | 57 | ``` 58 | 59 | 配置完成结果 60 | ![image](https://github.com/guevaraya/rust-for-linux/assets/446973/e9c21606-476f-4ca1-9b77-63b2f87fe9e7) 61 | 62 | ## 练习2 自定义编写Rust内核驱动模块 63 | 64 | * 1. linux目录下samples/rust,新建rust_helloworld.rs 并添加对应的Kconfig 65 | **rust_helloworld.rs** 66 | 67 | 68 | ``` 69 | // SPDX-License-Identifier: GPL-2.0 70 | //! Rust minimal sample. 71 | 72 | use kernel::prelude::*; 73 | 74 | module! { 75 | type: RustHelloWorld, 76 | name: "rust_helloworld", 77 | author: "whocare", 78 | description: "hello world module in rust", 79 | license: "GPL", 80 | } 81 | 82 | struct RustHelloWorld {} 83 | 84 | impl kernel::Module for RustHelloWorld { 85 | fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result { 86 | pr_info!("Hello World from Rust module"); 87 | Ok(RustHelloWorld {}) 88 | } 89 | } 90 | 91 | ``` 92 | 93 | * 1. samples/rust/Kconfig 配置修改如下: 94 | 95 | 96 | ``` 97 | obj-$(CONFIG_SAMPLE_RUST_FS) += rust_fs.o 98 | obj-$(CONFIG_SAMPLE_RUST_SELFTESTS) += rust_selftests.o 99 | // ++++++++++++++++ add here 100 | obj-$(CONFIG_SAMPLE_RUST_HELLOWORLD) += rust_helloworld.o 101 | // ++++++++++++++++ 102 | subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs 103 | 104 | config SAMPLE_RUST_MINIMAL 105 | tristate "Minimal" 106 | help 107 | This option builds the Rust minimal module sample. 108 | 109 | To compile this as a module, choose M here: 110 | the module will be called rust_minimal. 111 | 112 | If unsure, say N. 113 | // +++++++++++++++ add here 114 | config SAMPLE_RUST_HELLOWORLD 115 | tristate "Print Helloworld in Rust" 116 | help 117 | This option builds the Rust HelloWorld module sample. 118 | 119 | To compile this as a module, choose M here: 120 | the module will be called rust_helloworld. 121 | 122 | If unsure, say N. 123 | // +++++++++++++++ 124 | config SAMPLE_RUST_PRINT 125 | tristate "Printing macros" 126 | help 127 | This option builds the Rust printing macros sample. 128 | 129 | To compile this as a module, choose M here: 130 | the module will be called rust_print. 131 | 132 | If unsure, say N. 133 | 134 | ``` 135 | 136 | 3,配置使能 run make LLVM=1 menuconfig 137 | 138 | ``` 139 | Kernel hacking 140 | ---> Sample Kernel code 141 | ---> Rust samples 142 | ---> <*>Print Helloworld in Rust (NEW) 143 | 144 | ``` 145 | 146 | 4,编译内核 run make LLVM=1 147 | 148 | 5, 启动QEMU, 149 | 150 | 为了方便直接在 这里下载根文件系统,解压后参照readme.txt,更换自己的内核后,运行QEMU目录: 151 | 152 | ``` 153 | qemu-system-aarch64 -machine 'virt' -cpu 'cortex-a57' -m 1G -device virtio-blk-device,drive=hd -drive file=image.qcow2,if=none,id=hd -device virtio-net-device,netdev=net -netdev user,id=net,hostfwd=tcp::2222-:22 -kernel ../linux/build/arch/arm64/boot/Image -initrd initrd -nographic -append "root=LABEL=rootfs console=ttyAMA0" 154 | 155 | ``` 156 | 157 | ### 使用 busybox 制作内存文件系统 initramfs: 158 | 159 | 1. 下载解压 busybox 并配置环境变量 160 | 161 | ```shell 162 | wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 163 | tar -xf busybox-1.35.0.tar.bz2 164 | cd busybox-1.35.0 165 | # 配置环境变量 166 | export ARCH=arm64 167 | export CROSS_COMPILE=aarch64-linux-gnu- 168 | ``` 169 | 170 | 如果教程工具没有安装,请用下面命令 171 | 172 | 173 | ``` 174 | sudo apt-get install gcc-aarch64-linux-gnu 175 | 176 | ``` 177 | 178 | 1. 配置编译内核的参数 179 | 180 | ```shell 181 | # busybox-1.35.0目录下 182 | make menuconfig 183 | # 修改配置,选中如下项目,静态编译 184 | # Settings -> Build Options -> [*] Build static binary (no share libs) 185 | 186 | ``` 187 | 188 | 189 | 1. 编译 190 | 191 | ```shell 192 | make -j `nproc` 193 | 194 | ``` 195 | 196 | 197 | 编译头文件出错,错误如下: 198 | ``` 199 | make -j `nproc` 200 | SPLIT include/autoconf.h -> include/config/* 201 | GEN include/bbconfigopts.h 202 | GEN include/common_bufsiz.h 203 | GEN include/embedded_scripts.h 204 | HOSTCC applets/usage 205 | HOSTCC applets/applet_tables 206 | GEN include/usage_compressed.h 207 | GEN include/applet_tables.h include/NUM_APPLETS.h 208 | GEN include/applet_tables.h include/NUM_APPLETS.h 209 | HOSTCC applets/usage_pod 210 | CC applets/applets.o 211 | In file included from /usr/lib/gcc-cross/aarch64-linux-gnu/10/include/limits.h:195, 212 | from /usr/lib/gcc-cross/aarch64-linux-gnu/10/include/syslimits.h:7, 213 | from /usr/lib/gcc-cross/aarch64-linux-gnu/10/include/limits.h:34, 214 | from include/platform.h:157, 215 | from include/libbb.h:13, 216 | from include/busybox.h:8, 217 | from applets/applets.c:9: 218 | /usr/include/limits.h:26:10: fatal error: bits/libc-header-start.h: 没有那个文件或目录 219 | 26 | #include 220 | | ^~~~~~~~~~~~~~~~~~~~~~~~~~ 221 | compilation terminated. 222 | make[1]: *** [scripts/Makefile.build:198:applets/applets.o] 错误 1 223 | make: *** [Makefile:372:applets_dir] 错误 2 224 | 225 | ``` 226 | 227 | 需要安装apt install gcc-multilib 228 | 229 | ``` 230 | make 231 | CC applets/applets.o 232 | In file included from /usr/include/bits/errno.h:26, 233 | from /usr/include/errno.h:28, 234 | from include/libbb.h:17, 235 | from include/busybox.h:8, 236 | from applets/applets.c:9: 237 | /usr/include/linux/errno.h:1:10: fatal error: asm/errno.h: 没有那个文件或目录 238 | 1 | #include 239 | | ^~~~~~~~~~~~~ 240 | compilation terminated. 241 | make[1]: *** [scripts/Makefile.build:198:applets/applets.o] 错误 1 242 | make: *** [Makefile:372:applets_dir] 错误 2 243 | 244 | ``` 245 | 246 | 解决方法:sudo ln -s /usr/i686-linux-gnu/include/asm /usr/include/asm 247 | 248 | 1. 安装 249 | 250 | 安装前 busybox-1.35.0 目录下的文件如下图所示 251 | 252 | 输入如下命令 253 | 254 | 255 | ```shell 256 | make install 257 | ``` 258 | 259 | 260 | 安装后目录下生成了\_install 目录 261 | 262 | 263 | 1. 在\_install 目录下创建后续所需的文件和目录 264 | 265 | ```shell 266 | cd _install 267 | mkdir proc sys dev tmp 268 | touch init 269 | chmod +x init 270 | 271 | ``` 272 | 273 | 2. 用任意的文本编辑器编辑 init 文件内容如下 274 | 275 | ```shell 276 | #!/bin/sh 277 | 278 | # 挂载一些必要的文件系统 279 | mount -t proc none /proc 280 | mount -t sysfs none /sys 281 | mount -t tmpfs none /tmp 282 | mount -t devtmpfs none /dev 283 | 284 | ``` 285 | 286 | 287 | ``` 288 | # 停留在控制台 289 | exec /bin/sh 290 | ``` 291 | 292 | 293 | 294 | 1. 用 busybox 制作 initramfs 文件 295 | 296 | ```shell 297 | # _install目录 298 | find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz 299 | 300 | ``` 301 | 302 | 执行成功后可在 busybox-1.35.0 目录下找到 initramfs.cpio.gz 文件 303 | 304 | 2. 进入 qemu 目录下,执行如下命令 305 | 306 | 307 | ```shell 308 | qemu-system-aarch64 -M virt -cpu cortex-a72 -smp 8 -m 128M -kernel (your Image path) -initrd (your initramfs.cpio.gz path) -nographic -append "init=/init console=ttyAMA0" 309 | ``` 310 | 311 | 其中`(your Image path)`为上一个任务中最后的`Image`镜像文件所在的目录,`(your initramfs.cpio.gz)`为步骤 7 中执行成功后得到的 initramfs.cpio.gz 的目录,例如 312 | 313 | ```shell 314 | qemu-system-aarch64 -M virt -cpu cortex-a72 -smp 8 -m 128M -kernel /home/jun/maodou/linux/arch/arm64/boot/Image -initrd /home/jun/maodou/busybox-1.35.0/initramfs.cpio.gz -nographic -append "init=/init console=ttyAMA0" 315 | ``` 316 | 317 | 318 | 319 | * 问题:failed to find romfile "efi-virtio.rom" 320 | 解决:apt-get install ipxe-qemu 321 | 322 | ![image](https://github.com/guevaraya/rust-for-linux/assets/446973/c2fc4220-df5f-4353-a07d-79381223d87d) 323 | 324 | [\[1\] QEMU安装启动实例](http://iric.tpddns.cn:9955/#/Rust/2023%E7%A7%8B%E5%86%AC%E8%AE%AD%E7%BB%83%E8%90%A5/%E7%AC%AC%E4%B8%89%E9%98%B6%E6%AE%B5/rust-for-linux/Qemu%E6%A8%A1%E6%8B%9F%E5%90%AF%E5%8A%A8/README) 325 | 326 | [\[2\] 编译 linux for rust 并制作 initramfs 最后编写 rust_helloworld 内核驱动 并在 qemu 环境加载](https://blog.csbxd.fun/archives/1699538198511) 327 | [通过 QEMU 打开学习 Linux kernel 的新世界](https://www.jianshu.com/p/9b68e9ea5849) 328 | 329 | 330 | ## 练习3& 4 编写bindling 和填充checkpoint编写E1000驱动 331 | 332 | 作业描述: 333 | 334 | 参考链接: https://github.com/yuoo655/e1000-driver.git 335 | ### bingding填写 336 | 在linux 的rust驱动中需要调用内核API,需要借助bindgen是一个将C 语言的头文件转化为rust源码的工具,才能调用。https://rust-lang.github.io/rust-bindgen/ 337 | 下面是一个C的头文件 338 | 339 | linux/include/r4l_sample.h 340 | ``` 341 | int echo_for_rust(const unsigned char * str) 342 | { 343 | printk("[r4l test]:%s\n", str); 344 | return 0; 345 | } 346 | EXPORT_SYMBOL_GPL(echo_for_rust); 347 | ``` 348 | linux/rust/helpers.c 349 | ```` 350 | int rust_helper_echo(const unsigned char * str) 351 | { 352 | return echo_for_rust(str); 353 | } 354 | EXPORT_SYMBOL_GPL(rust_helper_echo); 355 | ```` 356 | 这样就可以在rust里面通过bingling::echo调用C代码了 357 | 358 | ### 驱动编译和文件系统制作的Makefile脚本 359 | ``` 360 | all: run 361 | 362 | menuconfig: 363 | #make -C linux ARCH=arm64 LLVM=1 O=build menuconfig 364 | make -C linux-fujita ARCH=arm64 LLVM=1 O=build menuconfig 365 | build: 366 | #make -C linux/build ARCH=arm64 LLVM=1 -j4 367 | make -C linux-fujita/build ARCH=arm64 LLVM=1 -j4 368 | run: 369 | #qemu-system-aarch64 -M virt -cpu cortex-a72 -smp 8 -m 128M -kernel linux/build/arch/arm64/boot/Image -initrd busybox-1.33.2/initramfs.cpio.gz -nographic -append "init=/linuxrc console=ttyAMA0" 370 | qemu-system-aarch64 -M virt \ 371 | -cpu cortex-a72 \ 372 | -smp 8 \ 373 | -m 128M \ 374 | -device virtio-net-device,netdev=net \ 375 | -netdev user,id=net,hostfwd=tcp::2222-:22 \ 376 | -kernel linux-fujita/build/arch/arm64/boot/Image \ 377 | -initrd busybox-1.33.2/initramfs.cpio.gz \ 378 | -nographic \ 379 | -device e1000,netdev=net0,bus=pcie.0 \ 380 | -netdev user,id=net0 \ 381 | -append "init=/linuxrc console=ttyAMA0" 382 | run_initrd: 383 | qemu-system-aarch64 -machine 'virt' \ 384 | -cpu 'cortex-a57' \ 385 | -m 1G -device virtio-blk-device,drive=hd \ 386 | -drive file=dqib_arm64-virt/image.qcow2,if=none,id=hd \ 387 | -device virtio-net-device,netdev=net \ 388 | -netdev user,id=net,hostfwd=tcp::2222-:22 \ 389 | -kernel linux/build/arch/arm64/boot/Image \ 390 | -initrd dqib_arm64-virt/initrd \ 391 | -nographic \ 392 | -append "root=LABEL=rootfs console=ttyAMA0" 393 | busybox: 394 | export ARCH=arm64 395 | export CROSS_COMPILE=aarch64-linux-gnu- 396 | make -C busybox-1.33.2 397 | e1000: 398 | make -C e1000-driver/src/linux 399 | setup: e1000 400 | rm busybox-1.33.2/_install/e1000_for_linux.ko 401 | cp e1000-driver/src/e1000_for_linux.ko busybox-1.33.2/_install/ 402 | cd busybox-1.33.2/; ./setup.sh; cd - 403 | 404 | ``` 405 | ### 进入状态 406 | 407 | 刚开始老师讲解e1000驱动过程,云里雾里,驱动作业无从下手的感觉,前期内核编译和文件系统制作原本很简单,但是各种安装包不兼容问题,并且rust-for-linux的驱动和内核接口变化太快,导致驱动调试,e1000-driver最好还是要基于fujita-linux编译会更好一点,这块我遇到的问题是 408 | 409 | 先用官网的 https\://github.com/yuoo655/e1000-driver.git驱动的时候发现其与 410 | 411 | 随转向 编译 1.62.0 替换1.56.0 编译过去,修改了内核部分代码,花费了三四天才真正开始调驱动,看起其他同学都已经调试完成了,心里还是有点焦急的。但没办法,硬着头皮调试,不能放弃。 412 | 413 | ### 驱动调试网络原理困惑 414 | 415 | 由于之前没有接触过网卡驱动,对网卡配置有点手足无措,最后经过调查E1000有fujita的日本人已经调试好的代码参考 416 | 417 | ![image](https://github.com/guevaraya/rust-for-linux/assets/446973/c112bccc-2c47-4b8c-abfc-a0d59e5684de) 418 | 419 | 驱动OSI 7层协议,应用层,表示层,会话层,传输层,网络层,链路层和物理层 420 | 物理层和链路层对应网络的驱动,应用层,表示层,会话层对应ftp,http,ssh等,而传输层就是UDP,TCP,以及最热门的QUIC协议,而链路层典型的是MAC层,物理层是Phy层,IDH等。 421 | 422 | 代码经过一系列的折腾和编译,也参考了 这位同学的代码,启动之后结果发现没有probe打印, 423 | 424 | 425 | 经过仔细对吧,我们的开机启动qemu没有加入e1000网卡参数 426 | 处理中断的时候没有考虑None的情况,导致panic 427 | 428 | ``` 429 | -device e1000,netdev=net0,bus=pcie.0 \ 430 | -netdev user,id=net0 \ 431 | 432 | ``` 433 | 434 | ### 网卡数据发送: 435 | 436 | HTTP发送网络报过程 437 | 438 | 套接字缓冲区查询命令ss -ntt 439 | 440 | 441 | 442 | 看来start_xmit从网络层项链路层发送skb_buffer数据到tx_ring 443 | 444 | skb_buffer 数据结构 445 | 446 | Ring buffer结构如下: 447 | 448 | ![Uploading image.png…]() 449 | 450 | e1000 收发参考原理: 451 | 452 | \[内核源码] Linux 网络数据接收流程(TCP)- NAPI : 453 | 454 | ### 渐入佳境-驱动debug 455 | 456 | * 问题: eth1 up的时候内核崩溃 457 | 458 | ``` 459 | / # ifconfig eth1 up 460 | [ 31.261677] rust_e1000dev: Ethernet E1000 open 461 | [ 31.262660] rust_e1000dev: New E1000 device @ 0xffff800008480000 462 | [ 31.268236] rust_e1000dev: Allocated vaddr: 0xffff559803193000, paddr: 0x43193000 463 | [ 31.271569] rust_e1000dev: Allocated vaddr: 0xffff5598033d0000, paddr: 0x433d0000 464 | [ 31.282618] rust_e1000dev: Allocated vaddr: 0xffff559805c80000, paddr: 0x45c80000 465 | [ 31.289265] rust_e1000dev: Allocated vaddr: 0xffff559805d00000, paddr: 0x45d00000 466 | [ 31.292126] rust_e1000dev: e1000 CTL: 0x140240, Status: 0x80080783 467 | [ 31.293155] rust_e1000dev: e1000_init has been completed 468 | [ 31.294449] rust_e1000dev: e1000 device is initialized 469 | [ 31.302014] rust_e1000dev: handle_irq 470 | [ 31.303574] rust_e1000dev: irq::Handler E1000_ICR = 0x4 471 | [ 31.307389] rust_e1000dev: NapiPoller poll 472 | [ 31.307501] rust_e1000dev: get stats64 473 | [ 31.309985] rust_e1000dev: e1000_recv 474 | [ 31.309985] 475 | [ 31.310547] rust_kernel: panicked at 'called `Option::unwrap()` on a `None` value', /srv/dev-disk-by-uuid-acaca851-c5c0-4f99-8522-91fec8bd0740/users/yangkai/work/rcore_2023a/rust-for_linux/e1000-driver/src/linux/../e1000_for_linux.rs:86:54 476 | [ 31.321218] ------------[ cut here ]------------ 477 | [ 31.322559] rust_e1000dev: get stats64 478 | [ 31.324359] kernel BUG at rust/helpers.c:48! 479 | [ 31.326392] Internal error: Oops - BUG: 00000000f2000800 [#1] PREEMPT SMP 480 | [ 31.329246] Modules linked in: e1000_for_linux(O) 481 | [ 31.335888] CPU: 0 PID: 0 Comm: swapper/0 Tainted: G O 6.1.0-rc1-g5fc95830739f-dirty #2 482 | [ 31.340642] Hardware name: linux,dummy-virt (DT) 483 | [ 31.343687] pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) 484 | [ 31.346427] pc : rust_helper_BUG+0x4/0x8 485 | [ 31.350033] lr : rust_begin_unwind+0x5c/0x60 486 | [ 31.352394] sp : ffff800008003ca0 487 | [ 31.354242] x29: ffff800008003cf0 x28: ffff5598033d0000 x27: ffff800008003de0 488 | [ 31.358153] x26: ffff800008003ef0 x25: ffff800008003f00 x24: ffffb7c2d17ca5fd 489 | [ 31.361537] x23: ffffb7c31aea4634 x22: 0000000000000001 x21: 0000000000000100 490 | [ 31.364296] x20: 0000000000000000 x19: ffff800008003e00 x18: ffffffffffffffff 491 | [ 31.366870] x17: 0000000000000041 x16: ffffb7c31adfd5b8 x15: 0000000000000004 492 | [ 31.369318] x14: 0000000000000fff x13: ffffb7c31b95b090 x12: 0000000000000003 493 | [ 31.372159] x11: 00000000ffffefff x10: c0000000ffffefff x9 : 306c1c7bb7800c00 494 | / # [ 31.374389] x8 : 306c1c7bb7800c00 x7 : 205d373435303133 x6 : 332e31332020205b 495 | [ 31.378287] x5 : ffffb7c31bcf7eaf x4 : 0000000000000001 x3 : 0000000000000000 496 | [ 31.380756] x2 : 0000000000000000 x1 : ffff800008003a60 x0 : 00000000000000e3 497 | [ 31.383739] Call trace: 498 | [ 31.384726] rust_helper_BUG+0x4/0x8 499 | [ 31.386219] _RNvNtCs3yuwAp0waWO_4core9panicking9panic_fmt+0x34/0x38 500 | [ 31.389057] _RNvNtCs3yuwAp0waWO_4core9panicking5panic+0x38/0x3c 501 | [ 31.390407] _RNvXs2_CsejK4Ne9WL8c_15e1000_for_linuxNtB5_6PollerNtNtCsfATHBUcknU9_6kernel3net10NapiPoller4poll+0x560/0x564 [e1000_for_linux] 502 | [ 31.396475] _RNvMs7_NtCsfATHBUcknU9_6kernel3netINtB5_11NapiAdapterNtCsejK4Ne9WL8c_15e1000_for_linux6PollerE13poll_callbackBR_+0x44/0x58 [e1000_for_linux] 503 | [ 31.400059] __napi_poll+0x48/0x1cc 504 | [ 31.401478] net_rx_action+0x134/0x2d4 505 | [ 31.402281] __do_softirq+0xdc/0x25c 506 | [ 31.403612] ____do_softirq+0x10/0x1c 507 | [ 31.404441] call_on_irq_stack+0x2c/0x54 508 | [ 31.405413] do_softirq_own_stack+0x1c/0x28 509 | [ 31.406320] __irq_exit_rcu+0x98/0xec 510 | [ 31.407628] irq_exit_rcu+0x10/0x1c 511 | [ 31.408937] el1_interrupt+0x8c/0xbc 512 | [ 31.409942] el1h_64_irq_handler+0x18/0x24 513 | [ 31.411241] el1h_64_irq+0x64/0x68 514 | [ 31.412316] arch_cpu_idle+0x18/0x28 515 | [ 31.413007] cpuidle_idle_call+0x6c/0x1e4 516 | [ 31.413789] do_idle+0xcc/0xf4 517 | [ 31.414408] cpu_startup_entry+0x24/0x28 518 | [ 31.416156] kernel_init+0x0/0x1a0 519 | [ 31.417162] start_kernel+0x0/0x41c 520 | [ 31.418206] start_kernel+0x328/0x41c 521 | [ 31.419322] __primary_switched+0xbc/0xc4 522 | [ 31.421651] Code: a8c17bfd d50323bf d65f03c0 d503233f (d4210000) 523 | [ 31.425128] ---[ end trace 0000000000000000 ]--- 524 | [ 31.427231] Kernel panic - not syncing: Oops - BUG: Fatal exception in interrupt 525 | [ 31.428962] SMP: stopping secondary CPUs 526 | [ 31.432119] Kernel Offset: 0x37c311e00000 from 0xffff800008000000 527 | [ 31.433208] PHYS_OFFSET: 0xffffaa6840000000 528 | [ 31.433966] CPU features: 0x20000,2013c080,0000421b 529 | [ 31.435508] Memory Limit: none 530 | [ 31.437214] ---[ end Kernel panic - not syncing: Oops - BUG: Fatal exception in interrupt ]--- 531 | 532 | ``` 533 | 534 | 边看老师的视频,参考fujita和查找网络资料,终于ping通了 535 | 536 | ``` 537 | qemu-system-aarch64 -M virt -cpu cortex-a72 -smp 8 -m 128M -kernel linux/build/arch/arm64/boot/Image -initrd busybox-1.33.2/initramfs.cpio.gz -nographic -append "init=/linuxrc console=ttyAMA0" 538 | \-device virtio-net-device,netdev=net \\ 539 | \-netdev user,id=net,hostfwd=tcp::2222-:22 \\ 540 | \-device e1000,netdev=net0,bus=pcie.0 \\ 541 | \-netdev user,id=net0 \\ 542 | 543 | \[ 0.000000] Booting Linux on physical CPU 0x0000000000 \[0x410fd083] 544 | 545 | \[ 0.000000] Linux version 6.1.0-rc1-g5fc95830739f-dirty (root@openmediavault) (Debian clang version 11.0.1-2, LLD 11.0.1) #4 SMP PREEMPT Sat Nov 18 17:07:27 CST 2023 546 | 547 | \[ 0.000000] random: crng init done 548 | 549 | \[ 0.000000] Machine model: linux,dummy-virt 550 | 551 | ... 552 | 553 | / # ping 10.0.2.2 554 | 555 | PING 10.0.2.2 (10.0.2.2): 56 data bytes 556 | 557 | \[ 18.279362] \[r4l test]:binding start xmit 558 | 559 | \[ 18.281663] rust_e1000dev: Read E1000_TDT = 0x0 560 | 561 | \[ 18.282078] rust_e1000dev: >>>>>>>>> TX PKT 60 562 | 563 | \[ 18.282844] rust_e1000dev: 564 | 565 | \[ 18.282844] 566 | 567 | \[ 18.284086] rust_e1000dev: handle_irq 568 | 569 | \[ 18.291407] rust_e1000dev: irq::Handler E1000_ICR = 0x83 570 | 571 | \[ 18.293517] rust_e1000dev: NapiPoller poll 572 | 573 | \[ 18.295755] rust_e1000dev: Read E1000_RDT + 1 = 0x0 574 | 575 | \[ 18.296219] rust_e1000dev: RX PKT 64 \<\<\<\<\<\<\<\<\< 576 | 577 | \[ 18.299517] rust_e1000dev: e1000_recv 578 | 579 | \[ 18.299517] 580 | 581 | \[ 18.301622] rust_e1000dev: irq::Handler recv = 0x1 582 | 583 | \[ 18.316187] rust_e1000dev: irq::Handler recv = packets_len:0x1 datalen:0x40 584 | 585 | \[ 18.330359] \[r4l test]:binding start xmit 586 | 587 | \[ 18.332177] rust_e1000dev: Read E1000_TDT = 0x1 588 | 589 | \[ 18.332326] rust_e1000dev: >>>>>>>>> TX PKT 98 590 | 591 | \[ 18.334101] rust_e1000dev: 592 | 593 | \[ 18.334101] 594 | 595 | \[ 18.338608] rust_e1000dev: handle_irq 596 | 597 | \[ 18.340693] rust_e1000dev: irq::Handler E1000_ICR = 0x83 598 | 599 | \[ 18.346517] rust_e1000dev: NapiPoller poll 600 | 601 | \[ 18.347330] rust_e1000dev: Read E1000_RDT + 1 = 0x1 602 | 603 | \[ 18.347421] rust_e1000dev: RX PKT 98 \<\<\<\<\<\<\<\<\< 604 | 605 | \[ 18.348343] rust_e1000dev: e1000_recv 606 | 607 | \[ 18.348343] 608 | 609 | \[ 18.350169] rust_e1000dev: irq::Handler recv = 0x1 610 | 611 | \[ 18.353030] rust_e1000dev: irq::Handler recv = packets_len:0x1 datalen:0x62 612 | 613 | 64 bytes from 10.0.2.2: seq=0 ttl=255 time=122.904 ms 614 | 615 | ``` 616 | -------------------------------------------------------------------------------- /优秀文档参考/Rust_for_Linux驱动模块开发报告-江昊.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust for Linux驱动模块开发报告-江昊 3 | Date: 2023-11 4 | Tags: 5 | - Author: 江昊 6 | - Reports Repo: https://github.com/xxkeming/rust/blob/main/rust-for-linux.md 7 | --- 8 | 9 | ## 练习 1,2 作业 10 | 11 | ### 源码下载 12 | ``` 13 | git clone https://github.com/Rust-for-Linux/linux -b rust-dev --depth 1 14 | git clone https://github.com/fujita/linux.git -b rust-e1000 --depth 1 15 | ``` 16 | 17 | ### 环境搭建 18 | ``` 19 | ## Install dependency packages 20 | sudo apt-get -y install \ 21 | binutils build-essential libtool texinfo \ 22 | gzip zip unzip patchutils curl git \ 23 | make cmake ninja-build automake bison flex gperf \ 24 | grep sed gawk bc \ 25 | zlib1g-dev libexpat1-dev libmpc-dev \ 26 | libglib2.0-dev libfdt-dev libpixman-1-dev libelf-dev libssl-dev 27 | 28 | ## Install the LLVM 29 | sudo apt-get install clang-format clang-tidy clang-tools clang clangd libc++-dev libc++1 libc++abi-dev libc++abi1 libclang-dev libclang1 liblldb-dev libllvm-ocaml-dev libomp-dev libomp5 lld lldb llvm-dev llvm-runtime llvm python3-clang 30 | 31 | ## Add Rust environment 32 | cd linux 33 | rustup override set $(scripts/min-tool-version.sh rustc) 34 | rustup component add rust-src 35 | cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli 36 | make LLVM=1 rustavailable 37 | ``` 38 | 39 | ### 编译时遇到的问题 bindgen版本过高,修改了部分增对bindgen参数的Makefile配置 40 | image 41 | 42 | ### 编译流程,配置开启rust,并把练习2的代码及配置加入 43 | ``` 44 | make ARCH=arm64 LLVM=1 O=build defconfig 45 | 46 | make ARCH=arm64 LLVM=1 O=build menuconfig 47 | 48 | cd build 49 | make ARCH=arm64 LLVM=1 -j4 50 | ``` 51 | 52 | ### 启动方式 53 | ``` 54 | qemu-system-aarch64 -machine 'virt' -cpu 'cortex-a57' -m 1G -device virtio-blk-device,drive=hd -drive file=image.qcow2,if=none,id=hd -device virtio-net-device,netdev=net -netdev user,id=net,hostfwd=tcp::2222-:22 -kernel /home/parallels/linux/build/arch/arm64/boot/Image.gz -initrd initrd -nographic -append "root=LABEL=rootfs console=ttyAMA0" 55 | 56 | ``` 57 | 58 | ### 运行结果 59 | image 60 | 61 | 62 | ## 练习 3,4 作业 63 | ### 网络环境配置 64 | ``` 65 | 主机配置: 66 | sudo ip link add br0 type bridge 67 | sudo ip addr add 192.168.100.50/24 brd 192.168.100.255 dev br0 68 | sudo ip tuntap add mode tap user $(whoami) 69 | ip tuntap show 70 | sudo ip link set tap0 master br0 71 | sudo ip link set dev br0 up 72 | sudo ip link set dev tap0 up 73 | 74 | 虚拟机配置: 75 | ifconfig lo 127.0.0.1 netmask 255.0.0.0 up 76 | ifconfig eth0 192.168.100.224 netmask 255.255.255.0 broadcast 192.168.100.255 up 77 | 78 | qemu启动方式: 79 | qemu-system-aarch64 -M virt -cpu cortex-a57 -smp 1 -m 256M -kernel build/arch/arm64/boot/Image.gz -initrd ../busybox-1.36.1/initramfs.cpio.gz -nographic -vga none -no-reboot -append "init=/init console=ttyAMA0" -netdev tap,ifname=tap0,id=tap0,script=no,downscript=no -device e1000,netdev=tap0 80 | ``` 81 | ### 添加自定义函数 82 | ``` 83 | 1.直接在rust/helpers.c添加自定义函数void rust_helper_test(void) 84 | 2.在rust/bindings/lib.rs对bindings_helper添加pub 85 | 3.调用方式:kernel::bindings::bindings_helper::test() 86 | ``` 87 | ### e100网卡驱动作业仓库地址及运行结果 88 | ``` 89 | https://github.com/xxkeming/e1000-driver 90 | ``` 91 | image 92 | 93 | ## 补充: 自定义基本模块 94 | ### test.rs 95 | ``` 96 | use kernel::prelude::*; 97 | module! { 98 | type: RustTest, 99 | name: "rust_test", 100 | author: "keming", 101 | description: "test module in rust", 102 | license: "GPL", 103 | } 104 | struct RustTest {} 105 | impl kernel::Module for RustTest { 106 | fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result { 107 | pr_info!("test from Rust module"); 108 | Ok(RustTest {}) 109 | } 110 | } 111 | ``` 112 | ### makefile 113 | ``` 114 | obj-m := test.o 115 | PWD := $(shell pwd) 116 | ARCH ?= arm64 117 | KDIR ?= /lib/modules/$(shell uname -r)/build 118 | default: 119 | $(MAKE) ARCH=$(ARCH) LLVM=1 -C $(KDIR) M=$(PWD)/ modules 120 | clean: 121 | $(MAKE) ARCH=$(ARCH) LLVM=1 -C $(KDIR) M=$(PWD)/ clean 122 | ``` 123 | -------------------------------------------------------------------------------- /优秀文档参考/Rust_for_Linux驱动模块开发报告-童浩轩.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust for Linux驱动模块开发报告-童浩轩 3 | Date: 2023-11 4 | Tags: 5 | - Author: 童浩轩 6 | - Reports Repo: https://github.com/Tunghohin/rust-for-linux/tree/main/reports 7 | --- 8 | 9 | ## 编译 linux 10 | 11 | #### 1. 获取源码 12 | 13 | ```sh 14 | git clone https://github.com/Rust-for-Linux/linux -b rust-dev --depth=1 15 | ``` 16 | 17 | #### 2. 安装依赖 18 | 19 | - 获取依赖包 20 | 21 | ```sh 22 | sudo apt-get -y install \ 23 | binutils build-essential libtool texinfo \ 24 | gzip zip unzip patchutils curl git \ 25 | make cmake ninja-build automake bison flex gperf \ 26 | grep sed gawk bc \ 27 | zlib1g-dev libexpat1-dev libmpc-dev \ 28 | libglib2.0-dev libfdt-dev libpixman-1-dev libelf-dev libssl-dev 29 | ``` 30 | 31 | - 获取 clang 和 llvm 32 | 33 | ```sh 34 | sudo apt install clang llvm 35 | ``` 36 | 37 | #### 3. 编译 linux 内核 38 | 39 | ```sh 40 | make ARCH=arm64 LLVM=1 defconfig 41 | make ARCH=arm64 LLVM=1 menuconfig # general setup 里打开 rust support 42 | cd ./build 43 | bear -- make ARCH=arm64 LLVM=1 -j12 # 顺便生成 compile_commands.json 44 | ``` 45 | 46 | #### 4. 获取 dqib 系统镜像 47 | 48 | ```sh 49 | https://people.debian.org/~gio/dqib/ 50 | ``` 51 | 52 | 使用脚本运行 qemu 53 | 54 | ```sh 55 | #!/bin/bash 56 | 57 | qemu-system-aarch64 \ 58 | -machine 'virt' \ 59 | -cpu 'cortex-a57' \ 60 | -m 1G \ 61 | -device virtio-blk-device,drive=hd \ 62 | -drive file=image.qcow2,if=none,id=hd \ 63 | -device virtio-net-device,netdev=net \ 64 | -netdev user,id=net,hostfwd=tcp::2222-:22 \ 65 | -kernel /home/notaroo/kernel_dev/linux-rust/build/arch/arm64/boot/Image.gz \ 66 | -initrd initrd \ 67 | -append "root=LABEL=rootfs console=ttyAMA0" 68 | ``` 69 | 70 | ## 运行结果 71 | 72 | 73 | 74 | ## 加载模块 75 | 76 | 将 .ko 拷贝到制作好的 rootfs 里 77 | 运行 78 | 79 | ```sh 80 | insmod rust_hello.ko 81 | ``` 82 | 83 | ## 碰到的问题 84 | 85 | #### 1. 根文件系统构建 86 | 87 | 安装 debootstrap 和 qemu-utils 88 | 89 | ```sh 90 | sudo apt-get install debootstrap qemu-utils 91 | ``` 92 | 93 | 创建磁盘镜像文件格式化并挂载 94 | 95 | ```sh 96 | qemu-img create -f raw rootfs.img 4G 97 | mkfs.ext4 rootfs.img 98 | sudo mkdir /mnt/rootfs 99 | sudo mount -o loop rootfs.img /mnt/rootfs 100 | ``` 101 | 102 | 安装 debian 镜像 103 | 104 | ```sh 105 | sudo debootstrap --arch arm64 stable /mnt/rootfs https://mirrors.tuna.tsinghua.edu.cn/debian/ 106 | ``` 107 | 108 | 运行 `run.sh` 109 | 110 | #### 2. rust-analyzer 111 | 112 | ```sh 113 | make LLVM=1 ARCH=arm64 rust-analyzer 114 | ``` 115 | 116 | #### 3. deboostrap 下载慢 117 | 118 | 加上清华的源 119 | 120 | ``` 121 | https://mirrors.tuna.tsinghua.edu.cn/debian/ 122 | ``` 123 | 124 | #### 4. 根文件系统挂载不上 125 | 126 | 在根文件创建 /dev/vda,在 qemu 中指定 root=/dev/vda rw 127 | 128 | #### 5. qemu 连不上网 129 | 130 | 配置 `/etc/network/interfaces` 131 | 132 | ``` 133 | auto eth0 134 | allow-hotplug eth0 135 | iface eth0 inet dhcp 136 | ``` 137 | 138 | 同时在 qemu 里根文件指定路径后添加 rw 139 | 140 | ## 运行结果 141 | 142 | 143 | 144 | ## binding 145 | 146 | #### 1.在 include/linux 中创建文件 147 | 148 | 149 | 150 | #### 2.在 rust/bindings/bindings_helper.h 中 include 头文件,并将其于 /rust/helpers.c 中 export 151 | 152 | 153 | 154 | 155 | #### 3.通过 rust 模块调用 156 | 157 | 158 | 159 | ## 要点 160 | 161 | #### dma 162 | 163 | DMA(Direct Memory Access)可以使得外部设备可以不用 CPU 干预,直接把数据传输到内存,这样可以解放 CPU,提高系统性能 164 | 165 | 使用 dma_alloc_coherent/dma_free_coherent,分配/释放网卡 tx_ring 和 rx_ring 的 dma 空间 166 | 167 | #### napi 168 | 169 | 由于当网络包首发密集时,频繁地触发中断会严重影响 cpu 的执行效率,napi 是 linux 上采用的以提高网络处理效率的技术。 170 | 网卡接收到数据,通过硬中断通知 CPU 进行处理,但是当网卡有大量数据涌入时,频繁中断消耗大量的 CPU 处理时间在中断自身处理上,使得网卡和 CPU 工作效率低下,所以系统采用了硬中断进入轮询队列 + 软中断退出轮询队列技术,提升数据接收处理效率 171 | 172 | #### ring buffer 173 | 174 | e1000 网卡环形缓冲区,通过 DMA 映射到内存 175 | 176 | #### sk_buff 177 | 178 | 网络是分层的,对于应用层而言不用关心具体的底层是如何工作的,只需要按照协议将要发送或接收的数据打包成 packet 即可。 179 | 180 | 要使用 sk_buff 必须先分配 181 | 使用 alloc_skb_ip_align 分配一个 sk_buff 182 | 183 | 接收数据时,将网卡 rx_ring 受到的 packet 写入到 sk_buff 184 | 发送数据时,将 sk_buff 的数据使用 e1000_transmit 发送到网卡的 tx_ring 185 | 186 | #### 参考资料 187 | 188 | https://www.zhihu.com/people/wenfh2020/posts?page=2 189 | 190 | https://stackoverflow.com/questions/47450231/what-is-the-relationship-of-dma-ring-buffer-and-tx-rx-ring-for-a-network-card?answertab=votes#tab-top 191 | 192 | https://github.com/fujita/rust-e1000 193 | 194 | https://elixir.bootlin.com/linux/v4.19.121/source/drivers/net/ethernet/intel/e1000 195 | -------------------------------------------------------------------------------- /优秀文档参考/Rust_for_Linux驱动模块开发报告-邝劲强.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust for Linux驱动模块开发报告-邝劲强 3 | Date: 2023-11 4 | Tags: 5 | - Author: 邝劲强 6 | - Reports Repo: https://github.com/lispking/rust-for-linux/tree/main/reports 7 | --- 8 | 9 | # Exercise 1 10 | ## 练习1: Rust for Linux 仓库源码获取,编译环境部署,及Rust内核编译。之后尝试在模拟器Qemu上运行起来。 11 | 12 | ### 以下是整个练习过程使用到的命令及相关截图结果: 13 | 14 | 因为 `Rust-for-Linux/linux` 仓库下载确实太慢,所以,这次实验使用仓库是 `fujita/linux`: 15 | 16 | ```shell 17 | git clone git@github.com:fujita/linux.git -b rust-e1000 --depth 1 linux-rust-e1000 18 | ``` 19 | 20 | > 注:这里用了两个小技巧: 21 | > 1. 使用 `git` 协议下载,可以非常稳定将代码下载到本地 22 | > 2. 不需要关注历史 commit 信息,加上 `--depth 1`命令 23 | 24 | 25 | ### 0. 环境准备 26 | 27 | #### 环境说明 28 | 29 | * 机器配置: `CPU:M1;内存:16G;磁盘:256G` 30 | * 主机OS:`macOS Sonoma 14.0` 31 | * 虚拟机:`Ubuntu 23.04` 32 | 33 | #### 环境依赖 34 | 35 | ```shell 36 | ## first Update 37 | sudo apt-get update 38 | 39 | ## Install dependency packages 40 | sudo apt-get -y install \ 41 | binutils build-essential libtool texinfo \ 42 | gzip zip unzip patchutils curl git \ 43 | make cmake ninja-build automake bison flex gperf \ 44 | grep sed gawk bc \ 45 | zlib1g-dev libexpat1-dev libmpc-dev \ 46 | libglib2.0-dev libfdt-dev libpixman-1-dev libelf-dev libssl-dev 47 | 48 | ## Install the LLVM 49 | sudo apt-get install clang-format clang-tidy clang-tools clang clangd libc++-dev libc++1 libc++abi-dev libc++abi1 libclang-dev libclang1 liblldb-dev libllvm-ocaml-dev libomp-dev libomp5 lld lldb llvm-dev llvm-runtime llvm python3-clang 50 | 51 | ## Add Rust environment 52 | cd linux 53 | make LLVM=1 rustavailable 54 | rustup override set $(scripts/min-tool-version.sh rustc) 55 | rustup component add rust-src 56 | 57 | ## Rust-for-Linux/linux 官方 bindgen 版本较新,需将命令改成 bindgen-cli 58 | cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen 59 | ``` 60 | 61 | > 小技巧:若本地已经安装过 `bindgen`,在执行 `make LLVM=1 rustavailable` 会提示版本有差异,可以使用 `--force` 命令强制安装当前 `linux` 源码需要用到的版本。 62 | 63 | ### 1. 生成默认配置 64 | 65 | ```shell 66 | make ARCH=arm64 LLVM=1 O=build defconfig 67 | ``` 68 | 69 | ![make-defconfig-result](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise1/images/make-defconfig-result.png) 70 | 71 | 72 | ### 2. 支持 Rust 选项开启 73 | ```shell 74 | make ARCH=arm64 LLVM=1 O=build menuconfig 75 | ``` 76 | 77 | #### 2.1 进入配置页面,按回车选择 `General setup` 78 | 79 | ![General-setup](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise1/images/General-setup.png) 80 | 81 | #### 2.2 滚动到最下方,按空格选择 `Rust support`,然后,按 `ESC` 返回上一层 82 | 83 | ![make-menuconfig-result](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise1/images/make-menuconfig-result.png) 84 | 85 | #### 2.3 再按一次 `ESC` 弹窗,按回车确认 `Yes` 即可保存配置 86 | ![Yes-confirm](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise1/images/Yes-confirm.png) 87 | 88 | #### 2.4 配置保存结果如下图所示: 89 | 90 | ![save-config](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise1/images/save-config.png) 91 | 92 | ### 3. 编译内核 93 | 94 | ```shell 95 | cd build && time make ARCH=arm64 LLVM=1 -j8 96 | ``` 97 | 98 | > 编译结果如下图所示,代表编译成功(整体时间花费约 `半小时`),若编译出错,最后会出现 `Error` 字眼 99 | 100 | ![compile-result](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise1/images/compile-result.png) 101 | 102 | 103 | ### 4. 进入 `Qemu` 环境验证 104 | 105 | #### 4.1 准备 `busybox` 工具 106 | 107 | ```shell 108 | ## 下载,写作时,最新版本是 busybox-1.36.1 109 | wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 110 | ## 解压 111 | tar xjf busybox-1.36.1.tar.bz2 112 | ## 进入目录 113 | cd busybox-1.36.1 114 | ## 开启静态二进制,架构用 arm64 115 | make menuconfig ARCH=arm64 116 | ``` 117 | 118 | > 进入 `busybox` 菜单选择页,在 `Settings` 按回车进入下一个页面 119 | 120 | ![busybox-setting](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise1/images/busybox-setting.png) 121 | 122 | > 找到 `Build static binary (no shared libs)` 选项,并按空格选中后,按 `ESC` 返回上层,再按一次进入提示保存页面,回车选择 `Yes` 保存即完成静态二进制编译支持。 123 | 124 | ![busybox-setting-build-staice-binary](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise1/images/busybox-setting-build-static-binary.png) 125 | 126 | 127 | > 编译 busybox 二进制文件,并将该执行文件 copy 到 linux 源码目录 128 | 129 | ```shell 130 | ## 编译 busybox,编译成功后,会生成 busybox 可执行文件在 busybox-1.36.1 目录下 131 | make -j8 132 | ## 拷贝文件到 rust-for-linux 工程项目根目录 133 | cp /tmp/busybox-1.36.1/busybox $HOME/linux/ 134 | ``` 135 | 136 | #### 4.2 开启 `Rust Samples` 137 | 138 | * 按 [2. 支持 Rust 选项开启](#2-支持-rust-选项开启) 步骤,重新进入 `menuconfig`,开启所有 `Rust Samples` 139 | 140 | * 增加 `rust_module_parameters` 模块,并将文件名及源码内容作如下修改 141 | 142 | ```shell 143 | cd $HOME/linux 144 | 145 | cp samples/rust/rust_module_parameters.rs samples/rust/rust_module_parameters_builtin_default.rs 146 | cp samples/rust/rust_module_parameters.rs samples/rust/rust_module_parameters_builtin_custom.rs 147 | cp samples/rust/rust_module_parameters.rs samples/rust/rust_module_parameters_loadable_default.rs 148 | cp samples/rust/rust_module_parameters.rs samples/rust/rust_module_parameters_loadable_custom.rs 149 | 150 | sed -i 's:rust_module_parameters:rust_module_parameters_builtin_default:g' samples/rust/rust_module_parameters_builtin_default.rs 151 | sed -i 's:rust_module_parameters:rust_module_parameters_builtin_custom:g' samples/rust/rust_module_parameters_builtin_custom.rs 152 | sed -i 's:rust_module_parameters:rust_module_parameters_loadable_default:g' samples/rust/rust_module_parameters_loadable_default.rs 153 | sed -i 's:rust_module_parameters:rust_module_parameters_loadable_custom:g' samples/rust/rust_module_parameters_loadable_custom.rs 154 | 155 | echo 'obj-y += rust_module_parameters_builtin_default.o' >> samples/rust/Makefile 156 | echo 'obj-y += rust_module_parameters_builtin_custom.o' >> samples/rust/Makefile 157 | echo 'obj-m += rust_module_parameters_loadable_default.o' >> samples/rust/Makefile 158 | echo 'obj-m += rust_module_parameters_loadable_custom.o' >> samples/rust/Makefile 159 | ``` 160 | * 按 [3. 编译内核](#3-编译内核) 命令,将 `Rust Samples` 源码编译成 `.ko` 文件 161 | 162 | #### 4.3 生成 `qemu-initramfs.img` 镜像 163 | 164 | ```shell 165 | sed -i 's:samples/rust/:build/samples/rust/:' .github/workflows/qemu-initramfs.desc 166 | 167 | build/usr/gen_init_cpio .github/workflows/qemu-initramfs.desc > qemu-initramfs.img 168 | ``` 169 | 170 | #### 4.4 执行 `qemu` 命令验证结果 171 | 172 | ```shell 173 | qemu-system-aarch64 \ 174 | -kernel build/arch/arm64/boot/Image.gz \ 175 | -initrd qemu-initramfs.img \ 176 | -M virt \ 177 | -cpu cortex-a72 \ 178 | -smp 2 \ 179 | -nographic \ 180 | -vga none \ 181 | -no-reboot \ 182 | -append ' \ 183 | rust_module_parameters_builtin_custom.my_bool=n \ 184 | rust_module_parameters_builtin_custom.my_i32=345543 \ 185 | rust_module_parameters_builtin_custom.my_str=🦀mod \ 186 | rust_module_parameters_builtin_custom.my_usize=84 \ 187 | rust_module_parameters_builtin_custom.my_array=1,2,3 \ 188 | ' \ 189 | | sed 's:\r$::' 190 | ``` 191 | 192 | > 若执行结果如下所示,则代表内核准备一切就绪,然后,就可以愉快地开始编写内核驱动了。 193 | 194 | ```shell 195 | [ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd083] 196 | [ 0.000000] Linux version 6.1.0-rc1-gbb59bfc550bc-dirty (lispking@ubuntu) (Ubuntu clang version 15.0.7, Ubuntu LLD 15.0.7) #4 SMP PREEMPT Tue Nov 7 00:39:03 CST 2023 197 | [ 0.000000] random: crng init done 198 | [ 0.000000] Machine model: linux,dummy-virt 199 | [ 0.000000] efi: UEFI not found. 200 | [ 0.000000] NUMA: No NUMA configuration found 201 | [ 0.000000] NUMA: Faking a node at [mem 0x0000000040000000-0x0000000047ffffff] 202 | [ 0.000000] NUMA: NODE_DATA [mem 0x47fb0a00-0x47fb2fff] 203 | [ 0.000000] Zone ranges: 204 | [ 0.000000] DMA [mem 0x0000000040000000-0x0000000047ffffff] 205 | [ 0.000000] DMA32 empty 206 | [ 0.000000] Normal empty 207 | [ 0.000000] Movable zone start for each node 208 | [ 0.000000] Early memory node ranges 209 | [ 0.000000] node 0: [mem 0x0000000040000000-0x0000000047ffffff] 210 | [ 0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x0000000047ffffff] 211 | [ 0.000000] cma: Reserved 32 MiB at 0x0000000045c00000 212 | [ 0.000000] psci: probing for conduit method from DT. 213 | [ 0.000000] psci: PSCIv1.1 detected in firmware. 214 | [ 0.000000] psci: Using standard PSCI v0.2 function IDs 215 | [ 0.000000] psci: Trusted OS migration not required 216 | [ 0.000000] psci: SMC Calling Convention v1.0 217 | [ 0.000000] percpu: Embedded 20 pages/cpu s44840 r8192 d28888 u81920 218 | [ 0.000000] Detected PIPT I-cache on CPU0 219 | [ 0.000000] CPU features: detected: Spectre-v2 220 | [ 0.000000] CPU features: detected: Spectre-v3a 221 | [ 0.000000] CPU features: detected: Spectre-v4 222 | [ 0.000000] CPU features: detected: Spectre-BHB 223 | [ 0.000000] CPU features: kernel page table isolation forced ON by KASLR 224 | [ 0.000000] CPU features: detected: Kernel page table isolation (KPTI) 225 | [ 0.000000] CPU features: detected: ARM erratum 1742098 226 | [ 0.000000] CPU features: detected: ARM errata 1165522, 1319367, or 1530923 227 | [ 0.000000] alternatives: applying boot alternatives 228 | [ 0.000000] Fallback order for Node 0: 0 229 | [ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 32256 230 | [ 0.000000] Policy zone: DMA 231 | [ 0.000000] Kernel command line: \ 232 | [ 0.000000] rust_module_parameters_builtin_custom.my_bool=n \ 233 | [ 0.000000] rust_module_parameters_builtin_custom.my_i32=345543 \ 234 | [ 0.000000] rust_module_parameters_builtin_custom.my_str=🦀mod \ 235 | [ 0.000000] rust_module_parameters_builtin_custom.my_usize=84 \ 236 | [ 0.000000] rust_module_parameters_builtin_custom.my_array=1,2,3 \ 237 | [ 0.000000] 238 | [ 0.000000] Unknown kernel command line parameters "\ \ \ \ \ \", will be passed to user space. 239 | [ 0.000000] Dentry cache hash table entries: 16384 (order: 5, 131072 bytes, linear) 240 | [ 0.000000] Inode-cache hash table entries: 8192 (order: 4, 65536 bytes, linear) 241 | [ 0.000000] mem auto-init: stack:all(zero), heap alloc:off, heap free:off 242 | [ 0.000000] Memory: 58068K/131072K available (16640K kernel code, 3724K rwdata, 9340K rodata, 1920K init, 610K bss, 40236K reserved, 32768K cma-reserved) 243 | [ 0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=2, Nodes=1 244 | [ 0.000000] rcu: Preemptible hierarchical RCU implementation. 245 | [ 0.000000] rcu: RCU event tracing is enabled. 246 | [ 0.000000] rcu: RCU restricting CPUs from NR_CPUS=256 to nr_cpu_ids=2. 247 | [ 0.000000] Trampoline variant of Tasks RCU enabled. 248 | [ 0.000000] Tracing variant of Tasks RCU enabled. 249 | [ 0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies. 250 | [ 0.000000] rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=2 251 | [ 0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0 252 | [ 0.000000] Root IRQ handler: gic_handle_irq 253 | [ 0.000000] GICv2m: range[mem 0x08020000-0x08020fff], SPI[80:143] 254 | [ 0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention. 255 | [ 0.000000] arch_timer: cp15 timer(s) running at 62.50MHz (virt). 256 | [ 0.000000] clocksource: arch_sys_counter: mask: 0x1ffffffffffffff max_cycles: 0x1cd42e208c, max_idle_ns: 881590405314 ns 257 | [ 0.000034] sched_clock: 57 bits at 63MHz, resolution 16ns, wraps every 4398046511096ns 258 | [ 0.003104] Console: colour dummy device 80x25 259 | [ 0.006498] printk: console [tty0] enabled 260 | [ 0.007580] Calibrating delay loop (skipped), value calculated using timer frequency.. 125.00 BogoMIPS (lpj=250000) 261 | [ 0.007696] pid_max: default: 32768 minimum: 301 262 | [ 0.008201] LSM: Security Framework initializing 263 | [ 0.009621] Mount-cache hash table entries: 512 (order: 0, 4096 bytes, linear) 264 | [ 0.009667] Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes, linear) 265 | [ 0.027385] cacheinfo: Unable to detect cache hierarchy for CPU 0 266 | [ 0.030707] cblist_init_generic: Setting adjustable number of callback queues. 267 | [ 0.030813] cblist_init_generic: Setting shift to 1 and lim to 1. 268 | [ 0.031032] cblist_init_generic: Setting shift to 1 and lim to 1. 269 | [ 0.031887] rcu: Hierarchical SRCU implementation. 270 | [ 0.031927] rcu: Max phase no-delay instances is 1000. 271 | [ 0.034754] EFI services will not be available. 272 | [ 0.035301] smp: Bringing up secondary CPUs ... 273 | [ 0.038000] Detected PIPT I-cache on CPU1 274 | [ 0.038422] cacheinfo: Unable to detect cache hierarchy for CPU 1 275 | [ 0.038656] CPU1: Booted secondary processor 0x0000000001 [0x410fd083] 276 | [ 0.040629] smp: Brought up 1 node, 2 CPUs 277 | [ 0.041139] SMP: Total of 2 processors activated. 278 | [ 0.041197] CPU features: detected: 32-bit EL0 Support 279 | [ 0.041224] CPU features: detected: 32-bit EL1 Support 280 | [ 0.041273] CPU features: detected: CRC32 instructions 281 | [ 0.042832] CPU: All CPU(s) started at EL1 282 | [ 0.042955] alternatives: applying system-wide alternatives 283 | [ 0.053178] devtmpfs: initialized 284 | [ 0.060754] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns 285 | [ 0.060882] futex hash table entries: 512 (order: 3, 32768 bytes, linear) 286 | [ 0.063074] pinctrl core: initialized pinctrl subsystem 287 | [ 0.069593] DMI not present or invalid. 288 | [ 0.097328] NET: Registered PF_NETLINK/PF_ROUTE protocol family 289 | [ 0.103269] DMA: preallocated 128 KiB GFP_KERNEL pool for atomic allocations 290 | [ 0.103577] DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA pool for atomic allocations 291 | [ 0.103741] DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations 292 | [ 0.103955] audit: initializing netlink subsys (disabled) 293 | [ 0.104990] audit: type=2000 audit(0.064:1): state=initialized audit_enabled=0 res=1 294 | [ 0.107444] thermal_sys: Registered thermal governor 'step_wise' 295 | [ 0.107475] thermal_sys: Registered thermal governor 'power_allocator' 296 | [ 0.107695] cpuidle: using governor menu 297 | [ 0.108455] hw-breakpoint: found 6 breakpoint and 4 watchpoint registers. 298 | [ 0.108858] ASID allocator initialised with 32768 entries 299 | [ 0.111764] Serial: AMBA PL011 UART driver 300 | [ 0.133623] 9000000.pl011: ttyAMA0 at MMIO 0x9000000 (irq = 13, base_baud = 0) is a PL011 rev1 301 | [ 0.138161] printk: console [ttyAMA0] enabled 302 | [ 0.144714] KASLR enabled 303 | [ 0.172047] HugeTLB: registered 1.00 GiB page size, pre-allocated 0 pages 304 | [ 0.172209] HugeTLB: 16380 KiB vmemmap can be freed for a 1.00 GiB page 305 | [ 0.172317] HugeTLB: registered 32.0 MiB page size, pre-allocated 0 pages 306 | [ 0.172405] HugeTLB: 508 KiB vmemmap can be freed for a 32.0 MiB page 307 | [ 0.172490] HugeTLB: registered 2.00 MiB page size, pre-allocated 0 pages 308 | [ 0.172588] HugeTLB: 28 KiB vmemmap can be freed for a 2.00 MiB page 309 | [ 0.172694] HugeTLB: registered 64.0 KiB page size, pre-allocated 0 pages 310 | [ 0.172794] HugeTLB: 0 KiB vmemmap can be freed for a 64.0 KiB page 311 | [ 0.178560] ACPI: Interpreter disabled. 312 | [ 0.182045] iommu: Default domain type: Translated 313 | [ 0.182153] iommu: DMA domain TLB invalidation policy: strict mode 314 | [ 0.183245] SCSI subsystem initialized 315 | [ 0.184691] usbcore: registered new interface driver usbfs 316 | [ 0.184938] usbcore: registered new interface driver hub 317 | [ 0.185193] usbcore: registered new device driver usb 318 | [ 0.186663] pps_core: LinuxPPS API ver. 1 registered 319 | [ 0.186741] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti 320 | [ 0.186899] PTP clock support registered 321 | [ 0.187237] EDAC MC: Ver: 3.0.0 322 | [ 0.191035] FPGA manager framework 323 | [ 0.191539] Advanced Linux Sound Architecture Driver Initialized. 324 | [ 0.197068] vgaarb: loaded 325 | [ 0.201654] clocksource: Switched to clocksource arch_sys_counter 326 | [ 0.203078] VFS: Disk quotas dquot_6.6.0 327 | [ 0.203335] VFS: Dquot-cache hash table entries: 512 (order 0, 4096 bytes) 328 | [ 0.204321] pnp: PnP ACPI: disabled 329 | [ 0.233969] NET: Registered PF_INET protocol family 330 | [ 0.235788] IP idents hash table entries: 2048 (order: 2, 16384 bytes, linear) 331 | [ 0.240230] tcp_listen_portaddr_hash hash table entries: 256 (order: 0, 4096 bytes, linear) 332 | [ 0.240647] Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear) 333 | [ 0.240810] TCP established hash table entries: 1024 (order: 1, 8192 bytes, linear) 334 | [ 0.241319] TCP bind hash table entries: 1024 (order: 3, 32768 bytes, linear) 335 | [ 0.241642] TCP: Hash tables configured (established 1024 bind 1024) 336 | [ 0.242672] UDP hash table entries: 256 (order: 1, 8192 bytes, linear) 337 | [ 0.243137] UDP-Lite hash table entries: 256 (order: 1, 8192 bytes, linear) 338 | [ 0.244082] NET: Registered PF_UNIX/PF_LOCAL protocol family 339 | [ 0.246729] RPC: Registered named UNIX socket transport module. 340 | [ 0.246978] RPC: Registered udp transport module. 341 | [ 0.247087] RPC: Registered tcp transport module. 342 | [ 0.247207] RPC: Registered tcp NFSv4.1 backchannel transport module. 343 | [ 0.247457] PCI: CLS 0 bytes, default 64 344 | [ 0.250194] Unpacking initramfs... 345 | [ 0.252089] hw perfevents: enabled with armv8_pmuv3 PMU driver, 7 counters available 346 | [ 0.252719] kvm [1]: HYP mode not available 347 | [ 0.255075] Initialise system trusted keyrings 348 | [ 0.256216] workingset: timestamp_bits=42 max_order=15 bucket_order=0 349 | [ 0.264587] Freeing initrd memory: 4048K 350 | [ 0.264813] squashfs: version 4.0 (2009/01/31) Phillip Lougher 351 | [ 0.266781] NFS: Registering the id_resolver key type 352 | [ 0.267102] Key type id_resolver registered 353 | [ 0.267177] Key type id_legacy registered 354 | [ 0.267604] nfs4filelayout_init: NFSv4 File Layout Driver Registering... 355 | [ 0.267759] nfs4flexfilelayout_init: NFSv4 Flexfile Layout Driver Registering... 356 | [ 0.268362] 9p: Installing v9fs 9p2000 file system support 357 | [ 0.296720] Key type asymmetric registered 358 | [ 0.296866] Asymmetric key parser 'x509' registered 359 | [ 0.297224] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 245) 360 | [ 0.297383] io scheduler mq-deadline registered 361 | [ 0.297517] io scheduler kyber registered 362 | [ 0.313985] pl061_gpio 9030000.pl061: PL061 GPIO chip registered 363 | [ 0.335794] pci-host-generic 4010000000.pcie: host bridge /pcie@10000000 ranges: 364 | [ 0.336496] pci-host-generic 4010000000.pcie: IO 0x003eff0000..0x003effffff -> 0x0000000000 365 | [ 0.336995] pci-host-generic 4010000000.pcie: MEM 0x0010000000..0x003efeffff -> 0x0010000000 366 | [ 0.337139] pci-host-generic 4010000000.pcie: MEM 0x8000000000..0xffffffffff -> 0x8000000000 367 | [ 0.337729] pci-host-generic 4010000000.pcie: Memory resource size exceeds max for 32 bits 368 | [ 0.338221] pci-host-generic 4010000000.pcie: ECAM at [mem 0x4010000000-0x401fffffff] for [bus 00-ff] 369 | [ 0.339179] pci-host-generic 4010000000.pcie: PCI host bridge to bus 0000:00 370 | [ 0.339454] pci_bus 0000:00: root bus resource [bus 00-ff] 371 | [ 0.339581] pci_bus 0000:00: root bus resource [io 0x0000-0xffff] 372 | [ 0.339685] pci_bus 0000:00: root bus resource [mem 0x10000000-0x3efeffff] 373 | [ 0.339829] pci_bus 0000:00: root bus resource [mem 0x8000000000-0xffffffffff] 374 | [ 0.341081] pci 0000:00:00.0: [1b36:0008] type 00 class 0x060000 375 | [ 0.343668] pci 0000:00:01.0: [1af4:1000] type 00 class 0x020000 376 | [ 0.343989] pci 0000:00:01.0: reg 0x10: [io 0x0000-0x001f] 377 | [ 0.344113] pci 0000:00:01.0: reg 0x14: [mem 0x00000000-0x00000fff] 378 | [ 0.344331] pci 0000:00:01.0: reg 0x20: [mem 0x00000000-0x00003fff 64bit pref] 379 | [ 0.344512] pci 0000:00:01.0: reg 0x30: [mem 0x00000000-0x0007ffff pref] 380 | [ 0.346415] pci 0000:00:01.0: BAR 6: assigned [mem 0x10000000-0x1007ffff pref] 381 | [ 0.346725] pci 0000:00:01.0: BAR 4: assigned [mem 0x8000000000-0x8000003fff 64bit pref] 382 | [ 0.346953] pci 0000:00:01.0: BAR 1: assigned [mem 0x10080000-0x10080fff] 383 | [ 0.347074] pci 0000:00:01.0: BAR 0: assigned [io 0x1000-0x101f] 384 | [ 0.351329] EINJ: ACPI disabled. 385 | [ 0.374286] virtio-pci 0000:00:01.0: enabling device (0000 -> 0003) 386 | [ 0.385972] Serial: 8250/16550 driver, 4 ports, IRQ sharing enabled 387 | [ 0.390186] SuperH (H)SCI(F) driver initialized 388 | [ 0.391020] msm_serial: driver initialized 389 | [ 0.393221] cacheinfo: Unable to detect cache hierarchy for CPU 0 390 | [ 0.416646] loop: module loaded 391 | [ 0.418396] megasas: 07.719.03.00-rc1 392 | [ 0.422165] physmap-flash 0.flash: physmap platform flash device: [mem 0x00000000-0x03ffffff] 393 | [ 0.423432] 0.flash: Found 2 x16 devices at 0x0 in 32-bit bank. Manufacturer ID 0x000000 Chip ID 0x000000 394 | [ 0.423866] Intel/Sharp Extended Query Table at 0x0031 395 | [ 0.424794] Using buffer write method 396 | [ 0.425221] physmap-flash 0.flash: physmap platform flash device: [mem 0x04000000-0x07ffffff] 397 | [ 0.426214] 0.flash: Found 2 x16 devices at 0x0 in 32-bit bank. Manufacturer ID 0x000000 Chip ID 0x000000 398 | [ 0.426376] Intel/Sharp Extended Query Table at 0x0031 399 | [ 0.426803] Using buffer write method 400 | [ 0.426954] Concatenating MTD devices: 401 | [ 0.427018] (0): "0.flash" 402 | [ 0.427070] (1): "0.flash" 403 | [ 0.427111] into device "0.flash" 404 | [ 0.447101] tun: Universal TUN/TAP device driver, 1.6 405 | [ 0.455641] thunder_xcv, ver 1.0 406 | [ 0.455774] thunder_bgx, ver 1.0 407 | [ 0.455875] nicpf, ver 1.0 408 | [ 0.457608] hns3: Hisilicon Ethernet Network Driver for Hip08 Family - version 409 | [ 0.457692] hns3: Copyright (c) 2017 Huawei Corporation. 410 | [ 0.458149] hclge is initializing 411 | [ 0.458287] e1000: Intel(R) PRO/1000 Network Driver 412 | [ 0.458358] e1000: Copyright (c) 1999-2006 Intel Corporation. 413 | [ 0.458499] e1000e: Intel(R) PRO/1000 Network Driver 414 | [ 0.458558] e1000e: Copyright(c) 1999 - 2015 Intel Corporation. 415 | [ 0.458704] igb: Intel(R) Gigabit Ethernet Network Driver 416 | [ 0.458766] igb: Copyright (c) 2007-2014 Intel Corporation. 417 | [ 0.459007] igbvf: Intel(R) Gigabit Virtual Function Network Driver 418 | [ 0.459083] igbvf: Copyright (c) 2009 - 2012 Intel Corporation. 419 | [ 0.459626] sky2: driver version 1.30 420 | [ 0.461268] VFIO - User Level meta-driver version: 0.3 421 | [ 0.466328] usbcore: registered new interface driver usb-storage 422 | [ 0.471491] rtc-pl031 9010000.pl031: registered as rtc0 423 | [ 0.472031] rtc-pl031 9010000.pl031: setting system clock to 2023-11-07T06:02:08 UTC (1699336928) 424 | [ 0.473346] i2c_dev: i2c /dev entries driver 425 | [ 0.482225] sdhci: Secure Digital Host Controller Interface driver 426 | [ 0.482316] sdhci: Copyright(c) Pierre Ossman 427 | [ 0.483338] Synopsys Designware Multimedia Card Interface Driver 428 | [ 0.484559] sdhci-pltfm: SDHCI platform and OF driver helper 429 | [ 0.487468] ledtrig-cpu: registered to indicate activity on CPUs 430 | [ 0.490437] usbcore: registered new interface driver usbhid 431 | [ 0.490514] usbhid: USB HID core driver 432 | [ 0.499497] NET: Registered PF_PACKET protocol family 433 | [ 0.500241] 9pnet: Installing 9P2000 support 434 | [ 0.500450] Key type dns_resolver registered 435 | [ 0.501267] registered taskstats version 1 436 | [ 0.501467] Loading compiled-in X.509 certificates 437 | [ 0.520843] input: gpio-keys as /devices/platform/gpio-keys/input/input0 438 | [ 0.543124] ALSA device list: 439 | [ 0.543312] No soundcards found. 440 | [ 0.546543] uart-pl011 9000000.pl011: no DMA platform data 441 | [ 0.612888] Freeing unused kernel memory: 1920K 442 | [ 0.614508] Run /init as init process 443 | [ 0.666661] rust_minimal: Rust minimal sample (init) 444 | [ 0.666979] rust_minimal: Am I built-in? false 445 | [ 0.675964] rust_minimal: My numbers are [72, 108, 200] 446 | [ 0.676445] rust_minimal: Rust minimal sample (exit) 447 | [ 0.720870] rust_print: Rust printing macros sample (init) 448 | [ 0.721035] rust_print: Emergency message (level 0) without args 449 | [ 0.721234] rust_print: Alert message (level 1) without args 450 | [ 0.721483] rust_print: Critical message (level 2) without args 451 | [ 0.721705] rust_print: Error message (level 3) without args 452 | [ 0.721774] rust_print: Warning message (level 4) without args 453 | [ 0.721836] rust_print: Notice message (level 5) without args 454 | [ 0.721913] rust_print: Info message (level 6) without args 455 | [ 0.721982] rust_print: A line that is continued without args 456 | [ 0.722114] rust_print: Emergency message (level 0) with args 457 | [ 0.722220] rust_print: Alert message (level 1) with args 458 | [ 0.722285] rust_print: Critical message (level 2) with args 459 | [ 0.722362] rust_print: Error message (level 3) with args 460 | [ 0.722426] rust_print: Warning message (level 4) with args 461 | [ 0.722496] rust_print: Notice message (level 5) with args 462 | [ 0.722589] rust_print: Info message (level 6) with args 463 | [ 0.722715] rust_print: A line that is continued with args 464 | [ 0.727927] rust_print: Rust printing macros sample (exit) 465 | [ 0.749354] rust_module_parameters: Rust module parameters sample (init) 466 | [ 0.749642] rust_module_parameters: Parameters: 467 | [ 0.750058] rust_module_parameters: my_bool: true 468 | [ 0.750170] rust_module_parameters: my_i32: 42 469 | [ 0.750430] rust_module_parameters: my_str: default str val 470 | [ 0.750545] rust_module_parameters: my_usize: 42 471 | [ 0.750700] rust_module_parameters: my_array: [0, 1] 472 | [ 0.755527] rust_module_parameters: Rust module parameters sample (exit) 473 | [ 0.780268] rust_sync: Rust synchronisation primitives sample (init) 474 | [ 0.780446] rust_sync: Value: 10 475 | [ 0.780799] rust_sync: Value: 10 476 | [ 0.785505] rust_sync: Rust synchronisation primitives sample (exit) 477 | [ 0.813859] rust_chrdev: Rust character device sample (init) 478 | [ 0.819166] rust_chrdev: Rust character device sample (exit) 479 | [ 0.841260] rust_miscdev: Rust miscellaneous device sample (init) 480 | [ 0.847056] rust_miscdev: Rust miscellaneous device sample (exit) 481 | [ 0.872530] rust_stack_probing: Rust stack probing sample (init) 482 | [ 0.872732] rust_stack_probing: Large array has length: 514 483 | [ 0.877872] rust_stack_probing: Rust stack probing sample (exit) 484 | [ 0.906001] rust_semaphore: Rust semaphore sample (init) 485 | [ 0.911501] rust_semaphore: Rust semaphore sample (exit) 486 | [ 0.936540] rust_semaphore_c: Rust semaphore sample (in C, for comparison) (init) 487 | [ 0.941912] rust_semaphore_c: Rust semaphore sample (in C, for comparison) (exit) 488 | [ 0.964929] rust_selftests: Rust self tests (init) 489 | [ 0.965048] rust_selftests: test_example passed! 490 | [ 0.965115] rust_selftests: 1 tests run, 1 passed, 0 failed, 0 hit errors 491 | [ 0.965453] rust_selftests: All tests passed. Congratulations! 492 | [ 0.971363] rust_selftests: Rust self tests (exit) 493 | [ 0.997060] rust_module_parameters_loadable_default: Rust module parameters sample (init) 494 | [ 0.997242] rust_module_parameters_loadable_default: Parameters: 495 | [ 0.997390] rust_module_parameters_loadable_default: my_bool: true 496 | [ 0.997531] rust_module_parameters_loadable_default: my_i32: 42 497 | [ 0.997992] rust_module_parameters_loadable_default: my_str: default str val 498 | [ 0.998271] rust_module_parameters_loadable_default: my_usize: 42 499 | [ 0.998512] rust_module_parameters_loadable_default: my_array: [0, 1] 500 | [ 1.007882] rust_module_parameters_loadable_custom: Rust module parameters sample (init) 501 | [ 1.008054] rust_module_parameters_loadable_custom: Parameters: 502 | [ 1.008180] rust_module_parameters_loadable_custom: my_bool: false 503 | [ 1.008445] rust_module_parameters_loadable_custom: my_i32: 345543 504 | [ 1.008939] rust_module_parameters_loadable_custom: my_str: 🦀mod 505 | [ 1.009066] rust_module_parameters_loadable_custom: my_usize: 84 506 | [ 1.009352] rust_module_parameters_loadable_custom: my_array: [1, 2, 3] 507 | [ 1.016364] rust_module_parameters_loadable_default: Rust module parameters sample (exit) 508 | [ 1.035957] rust_module_parameters_loadable_custom: Rust module parameters sample (exit) 509 | [ 1.068096] Flash device refused suspend due to active operation (state 20) 510 | [ 1.068362] Flash device refused suspend due to active operation (state 20) 511 | [ 1.068791] reboot: Restarting system 512 | ``` 513 | 514 | # Exercise 2 515 | 516 | ## 练习2: 自定义编写Rust内核驱动模块 517 | 518 | We will only do in-tree compilation. In this part, we need to build a minimal kernel module 519 | 520 | * 编写 `rust_helloworld` 模块 521 | 522 | ```shell 523 | ## 进入练习1所在源码目录 524 | cd $HOME/linux 525 | 526 | ## 添加 RustHelloWorld 模块源码 527 | cat < samples/rust/rust_helloworld.rs 528 | // SPDX-License-Identifier: GPL-2.0 529 | //! Rust minimal sample. 530 | 531 | use kernel::prelude::*; 532 | 533 | module! { 534 | type: RustHelloWorld, 535 | name: "rust_helloworld", 536 | author: "whocare", 537 | description: "hello world module in rust", 538 | license: "GPL", 539 | } 540 | 541 | struct RustHelloWorld {} 542 | 543 | impl kernel::Module for RustHelloWorld { 544 | fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result { 545 | pr_info!("Hello World from Rust module"); 546 | Ok(RustHelloWorld {}) 547 | } 548 | } 549 | EOF 550 | ``` 551 | 552 | * 添加编译相关配置 553 | 554 | ```shell 555 | ## 在 samples/rust/Makefile 文件添加以下内容: 556 | obj-$(CONFIG_SAMPLE_RUST_HELLOWORLD) += rust_helloworld.o 557 | 558 | ## 在 samples/rust/Kconfig 文件添加以下内容: 559 | config SAMPLE_RUST_HELLOWORLD 560 | tristate "Print Hello World in Rust" 561 | help 562 | This option builds the Rust HelloWorld module sample. 563 | 564 | To compile this as a module, choose M here: 565 | the module will be called rust_helloworld. 566 | 567 | If unsure, say N. 568 | 569 | ## 在练习1基础上,添加 rust_helloworld 模块配置 570 | make ARCH=arm64 LLVM=1 O=build menuconfig 571 | ``` 572 | 573 | ![hello-world-config](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise2/images/hello-world-config.png) 574 | 575 | * 编译 `rust_helloworld` 模块 576 | 577 | ```shell 578 | ## 将 rust_helloworld 模块编译,编译成功后,会生成 build/samples/rust/rust_helloworld.ko 文件 579 | cd build && time make ARCH=arm64 LLVM=1 -j8 580 | ``` 581 | 582 | * 修改 `qemu-init.sh` 脚本,增加 `rust_helloworld` 模块 583 | 584 | ```shell 585 | busybox insmod rust_helloworld.ko 586 | busybox rmmod rust_helloworld.ko 587 | ``` 588 | 589 | * 修改镜像描述文件 `qemu-initramfs.desc`,增加 `rust_helloworld` 模块 590 | 591 | ```shell 592 | file /rust_helloworld.ko samples/rust/rust_helloworld.ko 0755 0 0 593 | ``` 594 | 595 | * 启动 `qemu` 验证 596 | 597 | ```shell 598 | qemu-system-aarch64 \ 599 | -kernel build/arch/arm64/boot/Image.gz \ 600 | -initrd qemu-initramfs.img \ 601 | -M virt \ 602 | -cpu cortex-a72 \ 603 | -smp 2 \ 604 | -nographic \ 605 | -vga none \ 606 | -no-reboot \ 607 | -append ' \ 608 | rust_module_parameters_builtin_custom.my_bool=n \ 609 | rust_module_parameters_builtin_custom.my_i32=345543 \ 610 | rust_module_parameters_builtin_custom.my_str=🦀mod \ 611 | rust_module_parameters_builtin_custom.my_usize=84 \ 612 | rust_module_parameters_builtin_custom.my_array=1,2,3 \ 613 | ' \ 614 | | sed 's:\r$::' 615 | ``` 616 | 617 | * 验证结果显示如下面红色框,则表示`rust_helloworld` 模块加载成功。 618 | 619 | ![hello-world-result](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise2/images/hello-world-result.png) 620 | 621 | * 代码已上传至:https://github.com/lispking/rust-for-linux-e1000/commit/fe8497d45e5cf719a4c1816c4183c80a55816899 622 | 623 | ## 基于Qemu模拟器上的e1000网卡驱动框架,填充驱动初始化函数 624 | 625 | 1. 由于在练习1里面是用的.github/workflow里的脚本生成的镜像,那个镜像的问题是不会停留在内核里,所以需要重新做一个 626 | 627 | ```shell 628 | cd busybox-1.36.1 629 | 630 | # 上一次练习没有执行这一步 631 | make install 632 | ``` 633 | 634 | 2. 由于实验用的是 `fujita/linux` 源码,需要将自带的 `e1000` 驱动先去掉,再重新编译内核 635 | 636 | 3. 启动脚本 `qemu-aarch64-test.sh` 改成下面: 637 | 638 | ```shell 639 | #!/bin/bash 640 | 641 | BUSYBOX=../busybox-1.36.1 642 | INITRD=${PWD}/initramfs.cpio.gz 643 | BUSYBOX_INSTALL_DIR=$BUSYBOX/_install 644 | MODULE_DIR=/lib/modules 645 | 646 | cat < $BUSYBOX_INSTALL_DIR/init 647 | #!/bin/busybox sh 648 | 649 | /bin/busybox mkdir -p /proc && /bin/busybox mount -t proc none /proc 650 | 651 | /bin/busybox echo -e "\033[33m[$(date)] Hello, Welcome to Rust for Linux! \033[0m" 652 | 653 | /bin/busybox ls $MODULE_DIR 654 | /bin/busybox insmod $MODULE_DIR/e1000_for_linux.ko 655 | 656 | /bin/busybox ip addr add 127.0.0.1/32 dev lo 657 | /bin/busybox ip link set lo up 658 | 659 | /bin/busybox ip addr add 192.168.100.223/24 dev eth0 660 | /bin/busybox ip link set eth0 up 661 | 662 | export 'PS1=(kernel) >' 663 | /bin/busybox sh 664 | EOF 665 | 666 | # 参考这里: https://www.jianshu.com/p/9b68e9ea5849 667 | # Set host-only network 668 | if [ "$1" == "init-host-only" ]; then 669 | sudo ip link add br0 type bridge 670 | sudo ip addr add 192.168.100.50/24 brd 192.168.100.255 dev br0 671 | sudo ip tuntap add mode tap user $(whoami) 672 | ip tuntap show 673 | sudo ip link set tap0 master br0 674 | sudo ip link set dev br0 up 675 | sudo ip link set dev tap0 up 676 | fi 677 | 678 | chmod +x $BUSYBOX_INSTALL_DIR/init 679 | 680 | mkdir -p $BUSYBOX_INSTALL_DIR/$MODULE_DIR/ 681 | 682 | ## 网卡驱动 ko 文件 683 | cp ../rust-e1000-driver/src/e1000_for_linux.ko $BUSYBOX_INSTALL_DIR/$MODULE_DIR/ 684 | 685 | cd $BUSYBOX_INSTALL_DIR && find . -print0 | cpio --null -ov --format=newc | gzip -9 > ${INITRD} && cd - 686 | 687 | qemu-system-aarch64 \ 688 | -kernel ./build/arch/arm64/boot/Image.gz \ 689 | -initrd ${INITRD} \ 690 | -M virt \ 691 | -cpu cortex-a72 \ 692 | -smp 2 \ 693 | -m 128M \ 694 | -nographic \ 695 | -netdev tap,ifname=tap0,id=tap0,script=no,downscript=no -device e1000,netdev=tap0 \ 696 | -append 'init=/init console=ttyAMA0' 697 | ``` 698 | 699 | 4. 左边窗口执行 `./qemu-aarch64-test.sh`,网卡驱动和 `ip` 地址在 `init` 脚本里已实现自动加载,待 `qemu` 启动后,在右边窗口执行 `ping` 命令,运行成功,结果将如下所示: 700 | 701 | ![ping-pong](https://github.com/lispking/rust-for-linux/blob/main/reports/exercise3/images/ping-pong.png) 702 | -------------------------------------------------------------------------------- /优秀文档参考/Rust实现内存块ramdisk设备驱动-江昊.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust实现内存块ramdisk设备驱动-江昊 3 | Date: 2023-11 4 | Tags: 5 | - Author: 江昊 6 | - Reports Repo: https://github.com/xxkeming/rust/blob/main/ramdisk.md 7 | --- 8 | 9 | ## 源码下载 10 | ``` 11 | git clone https://github.com/Rust-for-Linux/linux -b rust-dev --depth 1 12 | ``` 13 | 14 | ## 项目目标 15 | ``` 16 | rust实现基于内存的块设备模块,有部分内存读写的宏太复杂,还是c实现 17 | ``` 18 | 19 | ## blk-mq的binding添加,部分结构的封装 20 | ### rust实现基于内存的块设备模块 21 | ### bindings_helper.h 添加需要引用的头文件 22 | ``` 23 | #include 24 | #include 25 | ``` 26 | ### rust/kernel目录添加blkmq.rs,并重新编译内核 27 | ``` 28 | use crate::bindings; 29 | use crate::sync::LockClassKey; 30 | use alloc::boxed::Box; 31 | 32 | /// gendisk 33 | pub struct GenDisk(*mut bindings::gendisk); 34 | 35 | unsafe impl Sync for GenDisk {} 36 | unsafe impl Send for GenDisk {} 37 | 38 | impl core::ops::Deref for GenDisk { 39 | type Target = bindings::gendisk; 40 | 41 | fn deref(&self) -> &Self::Target { 42 | unsafe { &*self.0 } 43 | } 44 | } 45 | 46 | impl core::ops::DerefMut for GenDisk { 47 | 48 | fn deref_mut(&mut self) -> &mut Self::Target { 49 | unsafe { &mut *self.0 } 50 | } 51 | } 52 | 53 | impl GenDisk { 54 | 55 | pub fn set_name(&mut self, name: &[u8]) { 56 | 57 | unsafe { 58 | core::ptr::copy_nonoverlapping(name.as_ptr(), self.disk_name.as_mut_ptr() as *mut u8, name.len()); 59 | } 60 | } 61 | 62 | pub fn set_capacity(&mut self, sector: u64) { 63 | 64 | unsafe { 65 | bindings::set_capacity(&mut **self, sector) 66 | } 67 | } 68 | 69 | pub fn add(&mut self) -> isize { 70 | 71 | unsafe { 72 | bindings::device_add_disk(core::ptr::null_mut(), &mut **self, core::ptr::null_mut()) as isize 73 | } 74 | } 75 | } 76 | 77 | impl Drop for GenDisk { 78 | 79 | fn drop(&mut self) { 80 | unsafe { 81 | bindings::put_disk(&mut **self) 82 | } 83 | } 84 | } 85 | 86 | /// blk mq tag set 87 | pub struct Tagset(Box); 88 | 89 | unsafe impl Sync for Tagset {} 90 | unsafe impl Send for Tagset {} 91 | 92 | impl Default for Tagset { 93 | 94 | fn default() -> Self { 95 | Self ( Box::try_new(bindings::blk_mq_tag_set::default()).unwrap() ) 96 | } 97 | } 98 | 99 | impl Tagset { 100 | 101 | pub fn alloc(&mut self) -> isize { 102 | 103 | unsafe { bindings::blk_mq_alloc_tag_set(&mut **self) as isize } 104 | } 105 | 106 | pub fn alloc_disk(&mut self) -> Option { 107 | 108 | static KEY: LockClassKey = LockClassKey::new(); 109 | 110 | unsafe 111 | { 112 | let disk = crate::error::from_err_ptr::(bindings::__blk_mq_alloc_disk(&mut **self, core::ptr::null_mut(), KEY.as_ptr())); 113 | if let Ok(disk) = disk { 114 | Some(GenDisk(disk)) 115 | } else { 116 | None 117 | } 118 | } 119 | } 120 | } 121 | 122 | impl Drop for Tagset { 123 | 124 | fn drop(&mut self) { 125 | 126 | unsafe { 127 | /*if self.driver_data != core::ptr::null() { 128 | bindings::vfree(self.driver_data); 129 | }*/ 130 | bindings::blk_mq_free_tag_set(&mut **self) 131 | } 132 | } 133 | } 134 | 135 | impl core::ops::Deref for Tagset { 136 | type Target = bindings::blk_mq_tag_set; 137 | 138 | fn deref(&self) -> &Self::Target { 139 | &self.0 140 | } 141 | } 142 | 143 | impl core::ops::DerefMut for Tagset { 144 | 145 | fn deref_mut(&mut self) -> &mut Self::Target { 146 | &mut self.0 147 | } 148 | } 149 | 150 | /// blk mq ops 151 | pub struct Ops(Box); 152 | 153 | unsafe impl Sync for Ops {} 154 | unsafe impl Send for Ops {} 155 | 156 | impl Ops { 157 | 158 | pub fn new() -> Self { 159 | Self ( Box::try_new(bindings::blk_mq_ops::default()).unwrap() ) 160 | } 161 | } 162 | 163 | impl core::ops::Deref for Ops { 164 | type Target = bindings::blk_mq_ops; 165 | 166 | fn deref(&self) -> &Self::Target { 167 | &self.0 168 | } 169 | } 170 | 171 | impl core::ops::DerefMut for Ops { 172 | 173 | fn deref_mut(&mut self) -> &mut Self::Target { 174 | &mut self.0 175 | } 176 | } 177 | 178 | 179 | /// blk mq ops 180 | pub struct BdOps(Box); 181 | 182 | unsafe impl Sync for BdOps {} 183 | unsafe impl Send for BdOps {} 184 | 185 | impl BdOps { 186 | 187 | pub fn new(module: &mut bindings::module ) -> Self { 188 | 189 | let mut ops = Box::try_new(bindings::block_device_operations::default()).unwrap(); 190 | ops.owner = module; 191 | Self ( ops ) 192 | } 193 | } 194 | 195 | impl core::ops::Deref for BdOps { 196 | type Target = bindings::block_device_operations; 197 | 198 | fn deref(&self) -> &Self::Target { 199 | &self.0 200 | } 201 | } 202 | 203 | impl core::ops::DerefMut for BdOps { 204 | 205 | fn deref_mut(&mut self) -> &mut Self::Target { 206 | &mut self.0 207 | } 208 | } 209 | ``` 210 | 211 | ### 以动态的方式构造ramdisk模块 212 | ### Makefile文件 213 | ``` 214 | ramdisk-objs := vlk_queue.o rustblk.o 215 | obj-m := ramdisk.o 216 | #obj-m := blk.o 217 | 218 | PWD := $(shell pwd) 219 | ARCH = x86_64 220 | #KDIR = /lib/modules/$(shell uname -r)/build 221 | KDIR = /home/parallels/linux/build 222 | 223 | default: 224 | $(MAKE) ARCH=$(ARCH) LLVM=1 -C $(KDIR) M=$(PWD)/ modules 225 | clean: 226 | $(MAKE) ARCH=$(ARCH) LLVM=1 -C $(KDIR) M=$(PWD)/ clean 227 | ``` 228 | 229 | ### vlk_queue.c 部分函数太复杂,还是c实现方便 230 | ``` 231 | #include 232 | #include 233 | #include 234 | 235 | blk_status_t vblk_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd) 236 | { 237 | struct request *rq = bd->rq; 238 | struct bio_vec bvec; 239 | struct req_iterator iter; 240 | u64 offset; 241 | u32 len; 242 | u8 *buf; 243 | 244 | //pr_info("vblk_queue_rq\n"); 245 | 246 | blk_mq_start_request(rq); 247 | 248 | offset = blk_rq_pos(rq) * 512; 249 | 250 | rq_for_each_segment(bvec, rq, iter) { 251 | len = bvec.bv_len; 252 | buf = page_address(bvec.bv_page) + bvec.bv_offset; 253 | if (rq_data_dir(rq) == READ) { 254 | memcpy(buf, hctx->driver_data + offset, len); 255 | } else { 256 | memcpy(hctx->driver_data + offset, buf, len); 257 | } 258 | offset += len; 259 | } 260 | 261 | blk_mq_complete_request(rq); 262 | 263 | return BLK_STS_OK; 264 | } 265 | ``` 266 | 267 | ### rustblk.rs 268 | ``` 269 | #![allow(missing_docs)] 270 | #![allow(improper_ctypes)] 271 | 272 | use kernel::prelude::*; 273 | use kernel::blkmq; 274 | use kernel::bindings; 275 | use kernel::error; 276 | 277 | module! { 278 | type: RustBlock, 279 | name: "rust_block", 280 | author: "keming", 281 | description: "block module in rust", 282 | license: "GPL", 283 | } 284 | 285 | unsafe extern "C" fn vblk_open(_disk: *mut bindings::gendisk, _mode: bindings::blk_mode_t) -> i32 286 | { 287 | pr_info!("vblk_open\n"); 288 | return 0; 289 | } 290 | 291 | unsafe extern "C" fn vblk_release(_disk: *mut bindings::gendisk) 292 | { 293 | pr_info!("vblk_release\n"); 294 | } 295 | 296 | 297 | unsafe extern "C" fn vblk_init(hctx: *mut bindings::blk_mq_hw_ctx, data: *mut core::ffi::c_void, _arg3: u32) -> i32 { 298 | 299 | unsafe { 300 | pr_info!("vblk_init {:#x}\n", data as usize); 301 | (*hctx).driver_data = data; 302 | } 303 | 304 | 0 305 | } 306 | 307 | //* 308 | extern "C" { 309 | fn vblk_queue_rq(hctx: *mut bindings::blk_mq_hw_ctx, bd: *const bindings::blk_mq_queue_data) -> bindings::blk_status_t; 310 | } 311 | //*/ 312 | 313 | /* 314 | unsafe extern "C" fn vblk_queue_rq(hctx: *mut bindings::blk_mq_hw_ctx, bd: *const bindings::blk_mq_queue_data) -> bindings::blk_status_t { 315 | 316 | unsafe { 317 | pr_info!("queue_rq data: {:#x}\n", (*hctx).driver_data as usize); 318 | 319 | let rq = (*bd).rq; 320 | let mut iter = bindings::req_iterator::default(); 321 | 322 | bindings::blk_mq_start_request(rq); 323 | let offset = (*rq).__sector * 512; 324 | 325 | iter.bio = (*rq).bio; 326 | 327 | loop { 328 | if iter.bio == core::ptr::null_mut() { 329 | break; 330 | } 331 | 332 | iter.iter = (*iter.bio).bi_iter; 333 | loop { 334 | if iter.iter.bi_size < 1 { 335 | break; 336 | } 337 | 338 | let mut bv = bindings::bio_vec::default(); 339 | bv.bv_page = 340 | iter.iter.bio.bi_io_vec 341 | iter.iter 342 | 343 | #define bvec_iter_bvec(bvec, iter) \ 344 | ((struct bio_vec) { \ 345 | .bv_page = bvec_iter_page((bvec), (iter)), \ 346 | .bv_len = bvec_iter_len((bvec), (iter)), \ 347 | .bv_offset = bvec_iter_offset((bvec), (iter)), \ 348 | }) 349 | 350 | } 351 | 352 | iter.bio = (*iter.bio).bi_next; 353 | } 354 | 355 | bindings::blk_mq_complete_request(rq); 356 | } 357 | bindings::BLK_STS_OK as bindings::blk_status_t 358 | } 359 | */ 360 | 361 | unsafe extern "C" fn vblk_complete(rq: *mut bindings::request) 362 | { 363 | unsafe { 364 | //pr_info!("complete {:#x} {:#x}", rq as usize, core::mem::size_of::()); 365 | //pr_info!("complete {:#x}", rq.add(1) as usize); 366 | 367 | let data = *(rq.add(1) as *mut usize).add(1); 368 | //pr_info!("complete kfree: {:#x}", data); 369 | 370 | if data != 0 { 371 | bindings::kfree(data as *const _); 372 | } 373 | 374 | bindings::blk_mq_end_request(rq, bindings::BLK_STS_OK as u8) 375 | } 376 | } 377 | 378 | struct RustBlock { 379 | ops: blkmq::Ops, 380 | bdops: blkmq::BdOps, 381 | tagset: blkmq::Tagset, 382 | disk: blkmq::GenDisk 383 | } 384 | 385 | impl kernel::Module for RustBlock { 386 | 387 | fn init(_module: &'static ThisModule) -> Result { 388 | 389 | let mut ops = blkmq::Ops::new(); 390 | ops.init_hctx = Some(vblk_init); 391 | ops.queue_rq = Some(vblk_queue_rq); 392 | ops.complete = Some(vblk_complete); 393 | 394 | let mut tagset = blkmq::Tagset::default(); 395 | tagset.ops = &*ops; 396 | tagset.nr_hw_queues = 4; 397 | tagset.queue_depth = 64; 398 | tagset.numa_node = bindings::NUMA_NO_NODE; 399 | tagset.cmd_size = 0; 400 | tagset.flags = bindings::BLK_MQ_F_SHOULD_MERGE; 401 | tagset.driver_data = unsafe { bindings::vzalloc(1024 * 1024 * 32) }; 402 | 403 | if tagset.alloc() != 0 { 404 | unsafe { bindings::vfree(tagset.driver_data) }; 405 | 406 | pr_err!("Failed to allocate tag set\n"); 407 | return Err(error::code::EPERM); 408 | } 409 | 410 | let mut disk = match tagset.alloc_disk() { 411 | Some(r) => r, 412 | _ => { 413 | unsafe { bindings::vfree(tagset.driver_data) }; 414 | 415 | pr_err!("Failed to add disk\n"); 416 | return Err(error::code::EPERM); 417 | } 418 | }; 419 | let mut bdops = unsafe { blkmq::BdOps::new(&mut bindings::__this_module) }; 420 | bdops.open = Some(vblk_open); 421 | bdops.release = Some(vblk_release); 422 | 423 | disk.major = 0; 424 | disk.first_minor = 0; 425 | disk.fops = &*bdops; 426 | disk.set_name(b"vblk"); 427 | disk.set_capacity((1024 * 1024 * 32) / 512); 428 | 429 | if disk.add() != 0 { 430 | 431 | unsafe { bindings::vfree(tagset.driver_data) }; 432 | 433 | pr_err!("Failed to add disk\n"); 434 | return Err(error::code::EPERM); 435 | } 436 | 437 | pr_info!("Rust module init {:p}\n", &*ops); 438 | 439 | Ok(RustBlock {ops, bdops, tagset, disk}) 440 | } 441 | } 442 | 443 | impl Drop for RustBlock { 444 | 445 | fn drop(&mut self) { 446 | 447 | unsafe { bindings::vfree(self.tagset.driver_data) }; 448 | 449 | pr_info!("Rust module drop {} {:#x} {:p} {:p} {:p}\n", self.tagset.nr_maps, self.tagset.ops as usize, &*self.ops, &*self.bdops, &self.disk); 450 | } 451 | } 452 | ``` 453 | 454 | ### 编译,打包到busybox,加载效果结果 455 | image 456 | 457 | 458 | ## 练习 1,2 作业 459 | 460 | ### 源码下载 461 | ``` 462 | git clone https://github.com/Rust-for-Linux/linux -b rust-dev --depth 1 463 | git clone https://github.com/fujita/linux.git -b rust-e1000 --depth 1 464 | ``` 465 | 466 | ### 环境搭建 467 | ``` 468 | ## Install dependency packages 469 | sudo apt-get -y install \ 470 | binutils build-essential libtool texinfo \ 471 | gzip zip unzip patchutils curl git \ 472 | make cmake ninja-build automake bison flex gperf \ 473 | grep sed gawk bc \ 474 | zlib1g-dev libexpat1-dev libmpc-dev \ 475 | libglib2.0-dev libfdt-dev libpixman-1-dev libelf-dev libssl-dev 476 | 477 | ## Install the LLVM 478 | sudo apt-get install clang-format clang-tidy clang-tools clang clangd libc++-dev libc++1 libc++abi-dev libc++abi1 libclang-dev libclang1 liblldb-dev libllvm-ocaml-dev libomp-dev libomp5 lld lldb llvm-dev llvm-runtime llvm python3-clang 479 | 480 | ## Add Rust environment 481 | cd linux 482 | rustup override set $(scripts/min-tool-version.sh rustc) 483 | rustup component add rust-src 484 | cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli 485 | make LLVM=1 rustavailable 486 | ``` 487 | 488 | ### 编译时遇到的问题 bindgen版本过高,修改了部分增对bindgen参数的Makefile配置 489 | image 490 | 491 | ### 编译流程,配置开启rust,并把练习2的代码及配置加入 492 | ``` 493 | make ARCH=arm64 LLVM=1 O=build defconfig 494 | 495 | make ARCH=arm64 LLVM=1 O=build menuconfig 496 | 497 | cd build 498 | make ARCH=arm64 LLVM=1 -j4 499 | ``` 500 | 501 | ### 启动方式 502 | ``` 503 | qemu-system-aarch64 -machine 'virt' -cpu 'cortex-a57' -m 1G -device virtio-blk-device,drive=hd -drive file=image.qcow2,if=none,id=hd -device virtio-net-device,netdev=net -netdev user,id=net,hostfwd=tcp::2222-:22 -kernel /home/parallels/linux/build/arch/arm64/boot/Image.gz -initrd initrd -nographic -append "root=LABEL=rootfs console=ttyAMA0" 504 | 505 | ``` 506 | 507 | ### 运行结果 508 | image 509 | 510 | 511 | ## 练习 3,4 作业 512 | ### 网络环境配置 513 | ``` 514 | 主机配置: 515 | sudo ip link add br0 type bridge 516 | sudo ip addr add 192.168.100.50/24 brd 192.168.100.255 dev br0 517 | sudo ip tuntap add mode tap user $(whoami) 518 | ip tuntap show 519 | sudo ip link set tap0 master br0 520 | sudo ip link set dev br0 up 521 | sudo ip link set dev tap0 up 522 | 523 | 虚拟机配置: 524 | ifconfig lo 127.0.0.1 netmask 255.0.0.0 up 525 | ifconfig eth0 192.168.100.224 netmask 255.255.255.0 broadcast 192.168.100.255 up 526 | 527 | qemu启动方式: 528 | qemu-system-aarch64 -M virt -cpu cortex-a57 -smp 1 -m 256M -kernel build/arch/arm64/boot/Image.gz -initrd ../busybox-1.36.1/initramfs.cpio.gz -nographic -vga none -no-reboot -append "init=/init console=ttyAMA0" -netdev tap,ifname=tap0,id=tap0,script=no,downscript=no -device e1000,netdev=tap0 529 | ``` 530 | ### 添加自定义函数 531 | ``` 532 | 1.直接在rust/helpers.c添加自定义函数void rust_helper_test(void) 533 | 2.在rust/bindings/lib.rs对bindings_helper添加pub 534 | 3.调用方式:kernel::bindings::bindings_helper::test() 535 | ``` 536 | ### e100网卡驱动作业仓库地址及运行结果 537 | ``` 538 | https://github.com/xxkeming/e1000-driver 539 | ``` 540 | image 541 | 542 | ## 补充: 自定义基本模块 543 | ### test.rs 544 | ``` 545 | use kernel::prelude::*; 546 | module! { 547 | type: RustTest, 548 | name: "rust_test", 549 | author: "keming", 550 | description: "test module in rust", 551 | license: "GPL", 552 | } 553 | struct RustTest {} 554 | impl kernel::Module for RustTest { 555 | fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result { 556 | pr_info!("test from Rust module"); 557 | Ok(RustTest {}) 558 | } 559 | } 560 | ``` 561 | ### makefile 562 | ``` 563 | obj-m := test.o 564 | PWD := $(shell pwd) 565 | ARCH ?= arm64 566 | KDIR ?= /lib/modules/$(shell uname -r)/build 567 | default: 568 | $(MAKE) ARCH=$(ARCH) LLVM=1 -C $(KDIR) M=$(PWD)/ modules 569 | clean: 570 | $(MAKE) ARCH=$(ARCH) LLVM=1 -C $(KDIR) M=$(PWD)/ clean 571 | ``` 572 | -------------------------------------------------------------------------------- /优秀文档参考/lcx-Rust-for-Linux-Virtio驱动模块开发.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust for Linux Virtio驱动模块开发-lcx 3 | Date: 2023-11 4 | Tags: 5 | - Author: lcx 6 | - Reports Repo: https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/rust-for-linux%E2%80%94Virtio%E7%9A%84%E5%AE%9E%E7%8E%B0.md 7 | --- 8 | 9 | # 实验virtio 10 | 11 | 这个就是真的由于时间关系还有驱动不完善,各自有各自的实现,兼容这些问题导致浪费了大量的时间 12 | 13 | 还有要理解一下bus、device、device_driver之间的关系 14 | 15 | 如果实现virtio_blk或者别的,一定要去看一下c实现,还有linux里这3个源码文件的结构体定义,看了以后才能理解一些nvme驱动的封装,还有内存的分配在实验的rust e1000里分配内存使用了dma分配,内核里大部分是kmalloc 16 | 17 | 这里发现还有很多知识的欠缺和断链比如rust的内存分配是如何分配的,在rust-for-linux中都有答案,所以想要用rust写好驱动这些基础模块实现和linux中的差别以及如何封装的一定要了解 18 | 19 | 在写的过程中最需要操作的就是如何把c代码转换为rust的抽象,c的实现其实个人觉得已经很优秀,转换为rust就需要把c面向过程语言是如何实现类似trait功能的理解清楚,之后再根据自己的理解去抽象成rust代码 20 | 21 | 时间关系,最后一个实验实现的东西并不多。。。环境问题和理解比较花时间。。后续也会继续抽时间玩这个东西 22 | 23 | 总体来说,学到了很多,也玩到了rust-for-linux,并且体验到了驱动的编写,通过e1000也了解了网卡那块的处理,包括通过virtio的学习也理解了自己在使用虚拟机读取本机文件的时候是怎么操作的 24 | 25 | ## rust-for-linux—Virtio 的实现 26 | 27 | > https://docs.oasis-open.org/virtio/virtio/v1.2/virtio-v1.2.html 28 | > 29 | > https://www.openeuler.org/zh/blog/yorifang/virtio-spec-overview.html 30 | > 31 | > https://rcore-os.cn/rCore-Tutorial-Book-v3/chapter9/2device-driver-2.html# 强烈推荐阅读这个 32 | > 33 | > https://zhuanlan.zhihu.com/p/642100699 34 | > 35 | > https://mp.weixin.qq.com/s/Stmj1weLXUg_EzVJfCIB3g 36 | > 37 | > https://mp.weixin.qq.com/s/6qo2xOUdqCEBOXhbKVNZSA 38 | 39 | 40 | 41 | ![Driver abstractions with virtio](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/./rust-for-linux—Virtio的实现.assets/figure2.gif) 42 | 43 | 44 | 45 | ### virtio设备基本组成要素 46 | 47 | `virtio`设备的基本组成要素如下: 48 | 49 | - 设备状态域(Device status field) 50 | - 特征位(Feature bits) 51 | - 通知(Notifications) 52 | - 设备配置空间(Device Configuration space) 53 | - 一个或多个虚拟队列(virtqueue) 54 | 55 | 56 | 57 | ### virtio设备呈现模式 58 | 59 | `virtio`设备支持三种设备呈现模式: 60 | 61 | - **Virtio Over MMIO**,虚拟设备直接挂载到系统总线上,我们实验中的虚拟计算机就是这种呈现模式;可参考linux内核中`virtio_mmio` 62 | - **Virtio Over PCI BUS**,遵循PCI规范,挂在到PCI总线上,作为virtio-pci设备呈现,在QEMU虚拟的x86计算机上采用的是这种模式;可参考linux内核中`virtio_pci` 63 | - **Virtio Over Channel I/O**:主要用在虚拟IBM s390计算机上,virtio-ccw使用这种基于channel I/O的机制。 64 | 65 | 66 | 67 | ### virtio高层架构 68 | 69 | ![High-level architecture](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/./rust-for-linux—Virtio的实现.assets/figure3.gif) 70 | 71 | 72 | 73 | 在Linux源码中也可以看见在`linux/drivers/virtio` 对`virtio`的实现源码 74 | 75 | ![image-20231202173840125](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/./rust-for-linux—Virtio的实现.assets/image-20231202173840125.png) 76 | 77 | 实现`virtio`的关键在于`virtioqueue`操作 78 | 79 | ### virtioqueue 80 | 81 | virtqueue由三部分组成: 82 | 83 | - 描述符表 Descriptor Table:描述符表是描述符为组成元素的数组,每个描述符描述了一个内存buffer 的address/length。而内存buffer中包含I/O请求的命令/数据(由virtio设备驱动填写),也可包含I/O完成的返回结果(由virtio设备填写)等。 84 | - 可用环 Available Ring:一种vring,记录了virtio设备驱动程序发出的I/O请求索引,即被virtio设备驱动程序更新的描述符索引的集合,需要virtio设备进行读取并完成相关I/O操作; 85 | - 已用环 Used Ring:另一种vring,记录了virtio设备发出的I/O完成索引,即被virtio设备更新的描述符索引的集合,需要vrtio设备驱动程序进行读取并对I/O操作结果进行进一步处理。 86 | 87 | ![../_images/vring.png](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/./rust-for-linux—Virtio的实现.assets/vring.png) 88 | 89 | ## 2. 前置知识 90 | 91 | 要想用rust-for-linux实现virtio,我们首先可以参考一下linux是如何实现的 92 | 93 | 首先我们把目光看到`virtio.c` 看下我们linux的驱动入口 94 | 95 | ```c 96 | static int virtio_init(void) 97 | { 98 | if (bus_register(&virtio_bus) != 0) 99 | panic("virtio bus registration failed"); 100 | return 0; 101 | } 102 | 103 | static void __exit virtio_exit(void) 104 | { 105 | bus_unregister(&virtio_bus); 106 | ida_destroy(&virtio_index_ida); 107 | } 108 | core_initcall(virtio_init); 109 | module_exit(virtio_exit); 110 | ``` 111 | 112 | 这里进行了`bus_register`方法的调用,要看懂这个操作我们还要理解一下bus、device、device_driver之间的关系 113 | 114 | > 在 Linux 内核中,"bus"(总线)是一种抽象概念,它表示一组相互连接的设备和资源。总线提供了设备之间进行通信和协作的框架,使得内核能够有效地管理和控制这些设备。 115 | > 116 | > 总线的概念有助于将设备模型中的各个部分分隔开来,使得内核能够以一致的方式与各种不同类型的设备进行交互。在这个上下文中,"bus" 并不是物理上的电缆或连接,而是一个在软件层面上建立的组织结构。 117 | > 118 | > 在 Linux 内核中,总线可以是硬件总线(如 PCI 总线、USB 总线、I2C 总线等)或虚拟总线(如 sysfs)。总线的注册和管理是通过内核的设备模型来实现的。 119 | > 120 | > 总线的角色包括: 121 | > 122 | > 1. **设备的注册和发现:** 设备驱动可以将自己注册到适当的总线上,从而告诉内核它支持的设备。内核可以在启动时或运行时发现和注册连接到总线上的设备。 123 | > 2. **设备的资源分配:** 总线可以负责为连接到它上面的设备分配和管理资源,如中断、I/O 端口、内存地址等。 124 | > 3. **设备的通信:** 总线提供了设备之间通信的基础。不同设备可以通过总线进行数据传输、同步和协作。 125 | > 126 | > 总线是设备模型的一部分,通过它,内核能够有效地组织、注册和管理系统中的各种设备。总线的概念使得内核能够以一致的方式处理不同类型和架构的设备。 127 | > 128 | > https://docs.kernel.org/driver-api/driver-model/bus.html 129 | > https://mp.weixin.qq.com/s/CIp1ykYasQFKuubV8atE0w 130 | > 131 | > https://mp.weixin.qq.com/s/3pGZXwA9WPPMlWZltu5xMg 132 | 133 | ### bus_type (总线) 134 | 135 | Linux将设备和驱动都挂载在总线上,管理着设备和驱动的注册、匹配、注销等操作,而内核中的每一个bus总线是由`struct bus_type`结构体描述的`bus_type` 的定义如下 136 | 137 | ```c 138 | struct bus_type { 139 | const char *name; // 该总线的名称 140 | const char *dev_name; // 当设备的名称为空时,就会以bus->dev_name + device ID呈现 141 | struct device *dev_root; // 为该bus的默认父设备 142 | const struct attribute_group **bus_groups; 143 | const struct attribute_group **dev_groups; 144 | const struct attribute_group **drv_groups; 145 | 146 | int (*match)(struct device *dev, struct device_driver *drv); 147 | int (*uevent)(struct device *dev, struct kobj_uevent_env *env); 148 | int (*probe)(struct device *dev); 149 | int (*remove)(struct device *dev); 150 | void (*shutdown)(struct device *dev); 151 | 152 | int (*online)(struct device *dev); 153 | int (*offline)(struct device *dev); 154 | 155 | int (*suspend)(struct device *dev, pm_message_t state); 156 | int (*resume)(struct device *dev); 157 | 158 | int (*num_vf)(struct device *dev); 159 | 160 | int (*dma_configure)(struct device *dev); 161 | 162 | const struct dev_pm_ops *pm; 163 | 164 | const struct iommu_ops *iommu_ops; 165 | 166 | struct subsys_private *p; // 私有数据 167 | struct lock_class_key lock_key; 168 | 169 | bool need_parent_lock; 170 | }; 171 | ``` 172 | 173 | - name: 总线名称,在sysfs中以目录的形式存在,比如i2c总线表现为“/sys/bus/i2c”。 174 | - dev_name:当设备的名称为空时,就会以bus->dev_name + device ID呈现 175 | - bus_groups、dev_groups、drv_groups分别表示bus、device、driver的默认属性attribute,在初始化时自动添加它们。 176 | - match回调函数:当任何属于该bus的device或device_driver添加时,Linux内核都会调用该接口进行匹配处理。 177 | - uevent回调函数:当该属于该bus的device发生添加、删除或其他动作时,bus的核心逻辑会调用该接口,以便bus driver可以修改环境变量。 178 | - probe和remove回调函数:当device和device_driver匹配成功时,将会调用总线的probe实现具体的driver初始化,而总线也有自己的初始化函数,一般地,先调用总线的probe函数,然后在总线probe函数中调用driver的probe函数。相应地,bus提供的remove函数是在一处挂载在设备上的driver时,先执行driver的remove函数,然后再调用ubs的remove函数。 179 | - shutdown、online、offline、suspend、resume、pm是电源管理相关。 180 | - p:bus的私有指针。 181 | 182 | 下图是PCI的总线图 183 | 184 | 185 | 186 | ![细说Linux虚拟化KVM-Qemu之virtio驱动](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/./rust-for-linux—Virtio的实现.assets/v2-8a330334ceec77c5102aee3f7ff8e340_1440w.png) 187 | 188 | 189 | 190 | ### device (设备) 191 | 192 | Linux内核中描述设备最基本的结构体是`struct device` 193 | 194 | ```c 195 | struct device { 196 | struct kobject kobj; 197 | struct device *parent; 198 | 199 | struct device_private *p; 200 | 201 | const char *init_name; /* initial name of the device */ 202 | const struct device_type *type; 203 | 204 | struct bus_type *bus; /* type of bus device is on */ 205 | struct device_driver *driver; /* which driver has allocated this 206 | device */ 207 | void *platform_data; /* Platform specific data, device 208 | core doesn't touch it */ 209 | void *driver_data; /* Driver data, set and get with 210 | dev_set_drvdata/dev_get_drvdata */ 211 | #ifdef CONFIG_PROVE_LOCKING 212 | struct mutex lockdep_mutex; 213 | #endif 214 | struct mutex mutex; /* mutex to synchronize calls to 215 | * its driver. 216 | */ 217 | 218 | struct dev_links_info links; 219 | struct dev_pm_info power; 220 | struct dev_pm_domain *pm_domain; 221 | 222 | #ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN 223 | struct irq_domain *msi_domain; 224 | #endif 225 | #ifdef CONFIG_PINCTRL 226 | struct dev_pin_info *pins; 227 | #endif 228 | #ifdef CONFIG_GENERIC_MSI_IRQ 229 | struct list_head msi_list; 230 | #endif 231 | 232 | const struct dma_map_ops *dma_ops; 233 | u64 *dma_mask; /* dma mask (if dma'able device) */ 234 | u64 coherent_dma_mask;/* Like dma_mask, but for 235 | alloc_coherent mappings as 236 | not all hardware supports 237 | 64 bit addresses for consistent 238 | allocations such descriptors. */ 239 | u64 bus_dma_mask; /* upstream dma_mask constraint */ 240 | unsigned long dma_pfn_offset; 241 | 242 | struct device_dma_parameters *dma_parms; 243 | 244 | struct list_head dma_pools; /* dma pools (if dma'ble) */ 245 | 246 | #ifdef CONFIG_DMA_DECLARE_COHERENT 247 | struct dma_coherent_mem *dma_mem; /* internal for coherent mem 248 | override */ 249 | #endif 250 | #ifdef CONFIG_DMA_CMA 251 | struct cma *cma_area; /* contiguous memory area for dma 252 | allocations */ 253 | #endif 254 | /* arch specific additions */ 255 | struct dev_archdata archdata; 256 | 257 | struct device_node *of_node; /* associated device tree node */ 258 | struct fwnode_handle *fwnode; /* firmware device node */ 259 | 260 | #ifdef CONFIG_NUMA 261 | int numa_node; /* NUMA node this device is close to */ 262 | #endif 263 | dev_t devt; /* dev_t, creates the sysfs "dev" */ 264 | u32 id; /* device instance */ 265 | 266 | spinlock_t devres_lock; 267 | struct list_head devres_head; 268 | 269 | struct class *class; 270 | const struct attribute_group **groups; /* optional groups */ 271 | 272 | void (*release)(struct device *dev); 273 | struct iommu_group *iommu_group; 274 | struct iommu_fwspec *iommu_fwspec; 275 | struct iommu_param *iommu_param; 276 | 277 | bool offline_disabled:1; 278 | bool offline:1; 279 | bool of_node_reused:1; 280 | #if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ 281 | defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ 282 | defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) 283 | bool dma_coherent:1; 284 | #endif 285 | }; 286 | ``` 287 | 288 | 在Linux内核中,每个设备都表示为`struct device`的一个实例,该结构体包含了设备模型核心建模所需的信息。`struct device`很少单独使用,更多的是嵌入到一个更高层次的设备结构体中。 289 | 290 | 291 | 292 | - parent:本设备的父节点,大部分情况下,父节点是bus节点或主控制器,如果父节点为NULL,那就是一个(top-level)顶层设备,一般不是我们想要的。 293 | - p:用于保存设备驱动核心部分的私有数据 294 | - kobj:该数据结构对应的struct kobject 295 | - init_name:设备的名称 296 | - type:设备类型 297 | - bus:该设备挂在哪个总线上 298 | - driver:该设备对应的驱动 299 | - platform_data:设备的平台数据,例如:对于定制版的设备,典型的是嵌入式和基于SOC的硬件,linux经常使用platform_data指向该板的数据结构体,描述设备及其链接方式,这可能包含了可用的端口,芯片变量,GPIO等等,这种做法缩小了BSP,并最小化驱动中的#ifdefs。 300 | - driver_data:驱动程序特定数据的指针 301 | - power,pm_domain:和电源管理相关 302 | - pins:设备的引脚管理相关 303 | - dma_ops,dma_mask,bus_dma_mask,dma_pfn_offset,dma_parms,dma_pools,dma_mem:DMA操作相关 304 | - devt:创建sysfs的dev,也叫设备号,一般由Major和Minor两部分组成。 305 | 306 | 307 | 308 | ### device_driver (驱动) 309 | 310 | Linux内核中描述设备驱动最基本的结构体是`struct device_driver` 311 | 312 | ```c 313 | struct device_driver { 314 | const char *name; // 驱动名称 315 | struct bus_type *bus; // 此驱动程序的设备所属的总线 316 | 317 | struct module *owner; 318 | const char *mod_name; /* used for built-in modules */ 319 | 320 | bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ 321 | enum probe_type probe_type; // probe类型 synchronous or asynchronous 322 | 323 | const struct of_device_id *of_match_table; // open firmware匹配表 324 | const struct acpi_device_id *acpi_match_table; // ACPI匹配表 325 | 326 | int (*probe) (struct device *dev); 327 | int (*remove) (struct device *dev); 328 | void (*shutdown) (struct device *dev); 329 | int (*suspend) (struct device *dev, pm_message_t state); 330 | int (*resume) (struct device *dev); 331 | const struct attribute_group **groups; // 由驱动内核自动创建的默认属性 332 | const struct attribute_group **dev_groups; // 设备实例绑定到驱动程序后,附加到设备的其他属性 333 | 334 | const struct dev_pm_ops *pm; // power management相关 335 | void (*coredump) (struct device *dev); // 用于产生uevent 336 | 337 | struct driver_private *p; // 私有数据 338 | }; 339 | ``` 340 | 341 | 342 | 343 | - name,该driver的名称。和device结构一样,该名称非常重要,后面会再详细说明。 344 | - bus,该driver所驱动设备的总线设备。为什么driver需要记录总线设备的指针呢?因为内核要保证在driver运行前,设备所依赖的总线能够正确初始化。 345 | - owner、mod_name,內核module相关的变量,暂不描述。 346 | - suppress_bind_attrs,是不在sysfs中启用bind和unbind attribute。在kernel中,bind/unbind是从用户空间手动的为driver绑定/解绑定指定的设备的机制。这种机制是在bus.c中完成的,后面会详细解释。 347 | - probe、remove,这两个接口函数用于实现driver逻辑的开始和结束。Driver是一段软件code,因此会有开始和结束两个代码逻辑,就像PC程序,会有一个main函数,main函数的开始就是开始,return的地方就是结束。而内核driver却有其特殊性:在设备模型的结构下,只有driver和device同时存在时,才需要开始执行driver的代码逻辑。这也是probe和remove两个接口名称的由来:检测到了设备和移除了设备(就是为热拔插起的!)。 348 | - shutdown、suspend、resume、pm,电源管理相关的内容。 349 | - groups,和struct device结构中的同名变量类似,driver也可以定义一些默认attribute,这样在将driver注册到内核中时,内核设备模型部分的代码(driver/base/driver.c)会自动将这些attribute添加到sysfs中。 350 | - p,driver core的私有数据指针,其它模块不能访问。 351 | 352 | 353 | 354 | 有点抽象画个图 355 | 356 | ```text 357 | +-------------+ 358 | | Device | 359 | +-------------+ 360 | | 361 | +-------------+ 362 | | Bus | 363 | +-------------+ 364 | | 365 | +--------------+ 366 | | Device Driver| 367 | +--------------+ 368 | ----------------------分割线--------------------------------------------------------------- 369 | +--------------------------+ 370 | | Device (struct device) | 371 | +--------------------------+ 372 | | 373 | +--------------------------------+ 374 | | Bus Type (struct bus_type) | 375 | +--------------------------------+ 376 | | 377 | +--------------|------------------+ 378 | | | | 379 | +-----------------+ +-----------------+ +------------------+ 380 | | Device Driver | | Device Driver | | Device Driver | 381 | +-----------------+ +-----------------+ +------------------+ 382 | | (struct | | (struct | | (struct | 383 | | device_driver)| | device_driver)| | device_driver)| 384 | +-----------------+ +-----------------+ +------------------+ 385 | ----------------------分割线--------------------------------------------------------------- 386 | +---------------------+ 387 | | Device Tree | 388 | +----------+----------+ 389 | | 390 | +------------------------|---------------------|------------------------+ 391 | | | | | 392 | +------------------+ +------------------+ +------------------+ +------------------+ 393 | | Bus Type A | | Bus Type B | | Bus Type C | | Bus Type D | 394 | +------------------+ +------------------+ +------------------+ +------------------+ 395 | | | | | 396 | | | | | 397 | +------------------+ +------------------+ +------------------+ +------------------+ 398 | |Device A1|Device A2||Device B1 |Device B2| |Device C1 |Device C2|| Device D1 | Device D2| 399 | +------------------+ +------------------+ +------------------+ +------------------+ 400 | | | | | | | | | 401 | | | | | | | | | 402 | +------------------+ +------------------+ +------------------+ +------------------+ 403 | | Driver A1 |Driver A2|Driver B1 |Driver B2|Driver C1 | Driver C2| Driver D1 | Driver D2| 404 | +------------------+ +------------------+ +------------------+ +------------------+ 405 | ``` 406 | 407 | - `Device` 表示系统中的实际设备,例如硬件设备或虚拟设备。每个设备都属于一个特定的 `Bus`。 408 | - `Bus` 是设备的集合,它负责管理和组织设备。一个 `Bus` 可以包含多个设备。 409 | - `Device Driver` 是负责与特定设备交互的代码。每个设备都有一个关联的设备驱动程序。 410 | 411 | 其中Device和Driver是会进行match 匹配的。 412 | 413 | 在Linux内核中,如果device和device_driver具有相同的名称,内核就会执行device_driver的probe回调函数,来完成device和device_driver的匹配。 414 | 415 | 执行device_driver的probe回调函数实际上是有bus模块实现的,因为device和driver都挂在在bus总线上,bus总线是最清楚哪个device匹配那个driver。 416 | 417 | 具体可以阅读[Linux设备模型之device和device_driver](https://mp.weixin.qq.com/s/3pGZXwA9WPPMlWZltu5xMg)以及[Linux设备模型之总线bus](https://mp.weixin.qq.com/s/CIp1ykYasQFKuubV8atE0w),讲的比较详细。 418 | 419 | 420 | ## 3.virtio-blk 421 | 422 | 在上面我们了解了`driver`那么我们看一下`virtio_driver`的定义 423 | 424 | ```c 425 | struct virtio_driver { 426 | struct device_driver driver; 427 | const struct virtio_device_id *id_table; 428 | const unsigned int *feature_table; 429 | unsigned int feature_table_size; 430 | const unsigned int *feature_table_legacy; 431 | unsigned int feature_table_size_legacy; 432 | int (*validate)(struct virtio_device *dev); 433 | int (*probe)(struct virtio_device *dev); 434 | void (*scan)(struct virtio_device *dev); 435 | void (*remove)(struct virtio_device *dev); 436 | void (*config_changed)(struct virtio_device *dev); 437 | #ifdef CONFIG_PM 438 | int (*freeze)(struct virtio_device *dev); 439 | int (*restore)(struct virtio_device *dev); 440 | #endif 441 | }; 442 | ``` 443 | 444 | 其中,我们最关键的函数就是`probe`和`remove` ,并且我们可以看到结构体中包含了`device_driver`所以实际上`virtio_driver`就是`device_driver` 445 | 446 | 447 | 448 | 我们现在把目光放在`virtio_blk.c`上模块加载如下 449 | 450 | ```c 451 | static int __init virtio_blk_init(void) 452 | { 453 | int error; 454 | //分配workqueue 455 | virtblk_wq = alloc_workqueue("virtio-blk", 0, 0); 456 | if (!virtblk_wq) 457 | return -ENOMEM; 458 | //注册新块设备 459 | major = register_blkdev(0, "virtblk"); 460 | if (major < 0) { 461 | error = major; 462 | goto out_destroy_workqueue; 463 | } 464 | //注册virtio_driver 465 | error = register_virtio_driver(&virtio_blk); 466 | if (error) 467 | goto out_unregister_blkdev; 468 | return 0; 469 | 470 | out_unregister_blkdev: 471 | unregister_blkdev(major, "virtblk"); 472 | out_destroy_workqueue: 473 | destroy_workqueue(virtblk_wq); 474 | return error; 475 | } 476 | 477 | static void __exit virtio_blk_fini(void) 478 | { 479 | unregister_virtio_driver(&virtio_blk); 480 | unregister_blkdev(major, "virtblk"); 481 | destroy_workqueue(virtblk_wq); 482 | } 483 | module_init(virtio_blk_init); 484 | module_exit(virtio_blk_fini); 485 | ``` 486 | 487 | 488 | 489 | `register_virtio_driver` 中做的事就是设置driver中的bus然后注册驱动 490 | 491 | ```c 492 | int register_virtio_driver(struct virtio_driver *driver) 493 | { 494 | /* Catch this early. */ 495 | BUG_ON(driver->feature_table_size && !driver->feature_table); 496 | driver->driver.bus = &virtio_bus; 497 | return driver_register(&driver->driver); 498 | } 499 | ``` 500 | 501 | 其中`virtio_bus`的定义如下: 502 | 503 | ```c 504 | static struct bus_type virtio_bus = { 505 | .name = "virtio", 506 | .match = virtio_dev_match, 507 | .dev_groups = virtio_dev_groups, 508 | .uevent = virtio_uevent, 509 | .probe = virtio_dev_probe, 510 | .remove = virtio_dev_remove, 511 | }; 512 | ``` 513 | 514 | 关于注册中最主要的`virtio_driver` 定义: 515 | 516 | ```c 517 | static struct virtio_driver virtio_blk = { 518 | .feature_table = features, 519 | .feature_table_size = ARRAY_SIZE(features), 520 | .feature_table_legacy = features_legacy, 521 | .feature_table_size_legacy = ARRAY_SIZE(features_legacy), 522 | .driver.name = KBUILD_MODNAME, 523 | .driver.owner = THIS_MODULE, 524 | .id_table = id_table, 525 | .probe = virtblk_probe, 526 | .remove = virtblk_remove, 527 | .config_changed = virtblk_config_changed, 528 | #ifdef CONFIG_PM_SLEEP 529 | .freeze = virtblk_freeze, 530 | .restore = virtblk_restore, 531 | #endif 532 | }; 533 | ``` 534 | 535 | 我们重点关注`virtblk_probe` 的代码 536 | 537 | ```c 538 | static int virtblk_probe(struct virtio_device *vdev) 539 | { 540 | struct virtio_blk *vblk; 541 | struct request_queue *q; 542 | int err, index; 543 | 544 | // ... (省略参数声明和错误检查) 545 | 546 | // 为块设备分配索引 547 | err = allocate_device_index(&index); 548 | if (err < 0) 549 | goto out; 550 | 551 | // 为 virtio_blk 结构分配内存 552 | vblk = allocate_virtio_blk_structure(vdev); 553 | if (!vblk) { 554 | err = -ENOMEM; 555 | goto out_free_index; 556 | } 557 | 558 | // 初始化 virtio_blk 结构 559 | err = initialize_virtio_blk_structure(vblk); 560 | if (err) 561 | goto out_free_vblk; 562 | 563 | // 初始化块设备的请求队列 564 | q = initialize_request_queue(vblk); 565 | if (IS_ERR(q)) { 566 | err = PTR_ERR(q); 567 | goto out_cleanup_disk; 568 | } 569 | 570 | // 配置块设备的其他参数 571 | configure_block_device_parameters(vdev, vblk, q); 572 | 573 | // 更新块设备容量并标记设备为准备就绪 574 | virtblk_update_capacity(vblk, false); 575 | virtio_device_ready(vdev); 576 | 577 | // 向系统添加块设备 578 | err = add_block_device_to_system(vdev, vblk->disk, virtblk_attr_groups); 579 | if (err) 580 | goto out_cleanup_disk; 581 | 582 | return 0; 583 | .......... 584 | return err; 585 | } 586 | ``` 587 | 588 | 做的事大致如下: 589 | 590 | 1. 为块设备分配索引(`allocate_device_index`) 591 | 2. 为 `virtio_blk` 结构分配内存(`allocate_virtio_blk_structure`) 592 | 3. 初始化 `virtio_blk` 结构(`initialize_virtio_blk_structure`) 593 | 4. 初始化块设备的请求队列(`initialize_request_queue`) 594 | 5. 配置块设备的其他参数(`configure_block_device_parameters`) 595 | 6. 更新块设备容量并标记设备为准备就绪(`virtblk_update_capacity` 和 `virtio_device_ready`) 596 | 7. 向系统添加块设备(`add_block_device_to_system`) 597 | 598 | 我们再看一下`virtio_blk` 结构体定义 599 | 600 | ```c 601 | struct virtio_blk { 602 | /* 603 | * This mutex must be held by anything that may run after 604 | * virtblk_remove() sets vblk->vdev to NULL. 605 | * 606 | * blk-mq, virtqueue processing, and sysfs attribute code paths are 607 | * shut down before vblk->vdev is set to NULL and therefore do not need 608 | * to hold this mutex. 609 | */ 610 | struct mutex vdev_mutex; 611 | struct virtio_device *vdev; 612 | 613 | /* The disk structure for the kernel. */ 614 | struct gendisk *disk; 615 | 616 | /* Block layer tags. */ 617 | struct blk_mq_tag_set tag_set; 618 | 619 | /* Process context for config space updates */ 620 | struct work_struct config_work; 621 | 622 | /* Ida index - used to track minor number allocations. */ 623 | int index; 624 | 625 | /* num of vqs */ 626 | int num_vqs; 627 | int io_queues[HCTX_MAX_TYPES]; 628 | struct virtio_blk_vq *vqs; 629 | }; 630 | ``` 631 | 632 | 这里我们在rust实现中就需要实现这个结构体 633 | 634 | 635 | 636 | 637 | 638 | 未完todo。。。。。。 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | ## 4.rust的相关驱动抽象 647 | 648 | 649 | 650 | 651 | -------------------------------------------------------------------------------- /优秀文档参考/lcx-Rust-for-Linux-net-driver.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust for Linux驱动模块开发报告-lcx 3 | Date: 2023-11 4 | Tags: 5 | - Author: lcx 6 | - Reports Repo: https://github.com/451846939/TimeMachine/tree/master/src/os/rust-for-linux 7 | --- 8 | 9 | # rust for linux 10 | 11 | ## 1. 背景 12 | 13 | Rust for Linux 这个项目的目的就是为了将 Rust 引入 Linux,让 Rust 成为 C 语言之后的第二语言。但它最初的目的是:实验性地支持Rust来写内核驱动。 14 | 15 | 以往,Linux 内核驱动的编写相对于应用其实是比较复杂的,具体复杂性主要表现在以下两个方面: 16 | 17 | - 编写设备驱动必须了解Linux 内核基础概念、工作机制、硬件原理等知识 18 | - 设备驱动中涉及内存和多线程并发时容易出现 Bug,linux驱动跟linux内核工作在同一层次,一旦发生问题,很容易造成内核的整体崩溃 19 | 20 | 引入 Rust 从理想层面来看,一方面在代码抽象和跨平台方面比 C 更有效,另一方面代码质量会更高,有效减少内存和多线程并发类 Bug 。但具体实践如何,是否真如理想中有效,这就需要后续的实验。 21 | 22 | Rust for Linux 就是为了帮助实现这一目标,为 Linux 提供了 Rust 相关的基础设施和方便编写 Linux 驱动的安全抽象。 23 | 24 | ### [Rust for Linux 第四次补丁提审](https://rustmagazine.github.io/rust_magazine_2022/Q1/contribute/rust-for-linux-clk.html#rust-for-linux-第四次补丁提审) 25 | 26 | 补丁的具体细节可以在Linux 邮件列表中找到: 27 | 28 | - RFC: https://lore.kernel.org/lkml/20210414184604.23473-1-ojeda@kernel.org/ 29 | - v1: https://lore.kernel.org/lkml/20210704202756.29107-1-ojeda@kernel.org/ 30 | - v2: https://lore.kernel.org/lkml/20211206140313.5653-1-ojeda@kernel.org/ 31 | - v3: https://lore.kernel.org/lkml/20220117053349.6804-1-ojeda@kernel.org/ 32 | 33 | 第二次补丁改进摘要可参考:[Rust for Linux 源码导读 | Ref 引用计数容器](https://rustmagazine.github.io/rust_magazine_2021/chapter_12/ref.html)。 34 | 35 | **第三次补丁改进摘要:** 36 | 37 | - 对 Rust 的支持有一些改进: 38 | - 升级到 Rust 1.58.0 39 | - 增加了自动检测来判断是否有合适的 Rust 可用工具链(`CONFIG_RUST_IS_AVAILABLE`,用于替换`HAS_RUST`) 40 | - 移除`!COMPILE_TEST` 41 | - 其他构建系统的改进 42 | - 文档改进 43 | - 需要的不稳定功能之一,`-Zsymbol-mangling-version=v0`,在 1.59.0 中变得稳定。另一个,“maybe_uninit_extra”,将在 1.60.0 中。 44 | - 对抽象和示例驱动程序的一些改进: 45 | - 加了将在总线中使用的“IdArray”和“IdTable”,以允许驱动程序指定在编译时保证为零终止(zero-terminated)的设备 ID 表。 46 | - 更新了 `amba` 以使用新的常量设备 ID 表支持。 47 | - 初始通用时钟框架抽象。 48 | - 平台驱动程序现在通过实现特质(trait)来定义。包括用于简化平台驱动程序注册的新宏和新示例/模板。 49 | - `dev_*` 打印宏。 50 | - `IoMem` 的`{read,write}*_relaxed` 方法。 51 | - 通过删除 `FileOpener` 来简化文件操作。 52 | - 在驱动程序注册的参数中添加了“ThisModule”。 53 | - 添加用 Rust 编写的树外 Linux 内核模块的基本模板: [https ://github.com/Rust-for-Linux/rust-out-of-tree-module](https ://github.com/Rust-for-Linux/rust-out-of-tree-module) 54 | 55 | **第四次补丁改进摘要:** 56 | 57 | - 基础设施更新: 58 | 59 | - 整合 CI : 英特尔 0DAY/LKP 内核测试机器人 / kernelCI/ GitHub CI 60 | - 内核模块不需要写 crate 属性,`#![no_std]` 和 `#![feature(...)]` 不再存在,删除样板。 61 | - 添加了单目标支持,包括`.o`、`.s`、`.ll`和`.i`(即宏扩展,类似于 C 预处理源)。 62 | - 对`helpers.c`文件的解释和对helpers的许可和导出的许可。 63 | - 文档Logo现在基于矢量 (SVG)。此外,已经为上游提出了 Tux 的矢量版本,并且用于改进自定义Logo支持的 RFC 已提交至上游 Rust。 64 | - 添加了关于注释 (`//`) 和代码文档的编码指南(`///`)。 65 | - `is_rust_module.sh` 返工。 66 | - 现在跳过了叶子模块的`.rmeta`的生成。 67 | - 其他清理、修复和改进。 68 | 69 | - 抽象和驱动更新: 70 | 71 | - 增加了对静态(全局共享变量)同步原语的支持。`CONFIG_CONSTRUCTORS`被用于实现。 72 | 73 | - 通过使用标记类型简化了锁防护,即`Guard`和`GuardMut`统一为一个参数化的类型。如果标记是 `WriteLock`,那么 `Guard`就会实现`DerefMut`(以前只由`GuardMut`实现)。 74 | 75 | - 可选参数添加到杂项设备(misc device)的注册中。遵循构建者模式,例如: 76 | 77 | ```rust 78 | miscdev::Options::new() 79 | .mode(0o600) 80 | .minor(10) 81 | .parent(parent) 82 | .register(reg, c_str!("sample"), ()) 83 | ``` 84 | 85 | - 增加了 "RwSemaphore "的抽象,该抽象包裹了C端`struct rw_semaphore`。 86 | 87 | - 新的`mm`模块和VMA抽象(包装C端`struct vm_area_struct`)用于`mmap`。 88 | 89 | - GPIO PL061现在使用最近增加的`dev_*!` Rust宏。 90 | 91 | - 支持`!CONFIG_PRINTK`情况。 92 | 93 | - 其他清理、修复和改进。 94 | 95 | rust驱动和内核的关系正如下图: 96 | 97 | ![image-20231117155649678](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/rust-for-linux.assets/image-20231117155649678-0215557.png) 98 | 99 | ## 2. 编译 100 | 101 | > https://rust-for-linux.com 102 | > 103 | > https://www.kernel.org/doc/html/next/rust/quick-start.html 104 | > 105 | > https://github.com/Rust-for-Linux/linux 106 | 107 | 首先 108 | 109 | ```shell 110 | git clone https://github.com/Rust-for-Linux/linux.git 111 | cd linux 112 | make LLVM=1 rustavailable 113 | rustup override set $(scripts/min-tool-version.sh rustc) 114 | rustup component add rust-src 115 | cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli 116 | ``` 117 | 118 | 根据提示[官方文档](https://www.kernel.org/doc/html/next/rust/quick-start.html) 配置好你的rust环境最重要的是要注意llvm要使用16以上的版本 119 | 120 | 这里给出llvm如何配置apt 以及如何安装clang+llvm等开发环境的文档地址: https://apt.llvm.org 121 | 122 | 基本需要的: 123 | 124 | ```shell 125 | sudo apt-get -y install \ 126 | binutils build-essential libtool texinfo \ 127 | gzip zip unzip patchutils curl git \ 128 | make cmake ninja-build automake bison flex gperf \ 129 | grep sed gawk bc \ 130 | zlib1g-dev libexpat1-dev libmpc-dev \ 131 | libglib2.0-dev libfdt-dev libpixman-1-dev libelf-dev libssl-dev 132 | ``` 133 | 134 | ```shell 135 | apt-get install clang-format clang-tidy clang-tools clang clangd libc++-dev libc++1 libc++abi-dev libc++abi1 libclang-dev libclang1 liblldb-dev libllvm-ocaml-dev libomp-dev libomp5 lld lldb llvm-dev llvm-runtime llvm python3-clang 136 | ``` 137 | 138 | llvm和clang请注意版本,如果安装的clang16可能运行文件名字为clang-16 可以使用 139 | 140 | ```she 141 | ln -s /usr/bin/clang-16 /usr/bin/clang 142 | ``` 143 | 144 | 然后 145 | 146 | ```shell 147 | make ARCH=arm64 LLVM=1 O=build defconfig 148 | 149 | make ARCH=arm64 LLVM=1 O=build menuconfig 150 | #set the following config to yes 151 | General setup 152 | ---> [*] Rust support 153 | Kernel hacking 154 | ---> Sample kernel code 155 | ---> [*] Rust samples 156 | cd build 157 | make ARCH=arm64 LLVM=1 -j8 158 | ``` 159 | 160 | menuconfig 会进入一个菜单选择,记得打开`Rust support` 和`Rust samples` 161 | 162 | ![image-20231107214913238](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/rust-for-linux.assets/image-20231107214913238.png) 163 | 164 | ## 3. 自定义内核驱动模块 165 | 166 | 我们可以先前往linux的`samples/rust/` 下可以看到rust的驱动例子,那我们可以仿造来写一个hello_world 167 | 168 | 创建`rust_helloworld.rs` 169 | 170 | ```rust 171 | use kernel::prelude::*; 172 | 173 | module! { 174 | type: RustHelloWorld, 175 | name: "rust_helloworld", 176 | author: "whocare", 177 | description: "hello world module in rust", 178 | license: "GPL", 179 | } 180 | 181 | struct RustHelloWorld {} 182 | 183 | impl kernel::Module for RustHelloWorld { 184 | fn init(_module: &'static ThisModule) -> Result { 185 | pr_info!("Hello World from Rust module"); 186 | Ok(RustHelloWorld {}) 187 | } 188 | } 189 | ``` 190 | 191 | 在rust目录下的Makefile加入`obj-$(CONFIG_SAMPLE_RUST_HELLOWORLD) += rust_helloworld.o` 192 | 193 | ```rust 194 | # SPDX-License-Identifier: GPL-2.0 195 | 196 | obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o 197 | obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o 198 | // +++++++++++++++ add here 199 | obj-$(CONFIG_SAMPLE_RUST_HELLOWORLD) += rust_helloworld.o 200 | // +++++++++++++++ 201 | 202 | subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs 203 | ``` 204 | 205 | 在Kconfig中加入 206 | 207 | ```Kconfig 208 | # SPDX-License-Identifier: GPL-2.0 209 | 210 | menuconfig SAMPLES_RUST 211 | bool "Rust samples" 212 | depends on RUST 213 | help 214 | You can build sample Rust kernel code here. 215 | 216 | If unsure, say N. 217 | 218 | if SAMPLES_RUST 219 | 220 | config SAMPLE_RUST_MINIMAL 221 | tristate "Minimal" 222 | help 223 | This option builds the Rust minimal module sample. 224 | 225 | To compile this as a module, choose M here: 226 | the module will be called rust_minimal. 227 | 228 | If unsure, say N. 229 | 230 | config SAMPLE_RUST_PRINT 231 | tristate "Printing macros" 232 | help 233 | This option builds the Rust printing macros sample. 234 | 235 | To compile this as a module, choose M here: 236 | the module will be called rust_print. 237 | 238 | If unsure, say N. 239 | // +++++++++++++++ add here 240 | config SAMPLE_RUST_HELLOWORLD 241 | tristate "Print Helloworld in Rust" 242 | help 243 | This option builds the Rust HelloWorld module sample. 244 | 245 | To compile this as a module, choose M here: 246 | the module will be called rust_helloworld. 247 | 248 | If unsure, say N. 249 | // +++++++++++++++ 250 | config SAMPLE_RUST_HOSTPROGS 251 | bool "Host programs" 252 | help 253 | This option builds the Rust host program samples. 254 | 255 | If unsure, say N. 256 | 257 | endif # SAMPLES_RUST 258 | ``` 259 | 260 | 回到linux目录 261 | 262 | ```shell 263 | make ARCH=arm64 LLVM=1 O=build menuconfig 264 | ``` 265 | 266 | 这时候我们可以去 267 | 268 | ```test 269 | Kernel hacking 270 | ---> Sample Kernel code 271 | ---> Rust samples 272 | ---> <*>Print Helloworld in Rust (NEW) 273 | ``` 274 | 275 | 打开后 276 | 277 | ```shell 278 | cd build 279 | make ARCH=arm64 LLVM=1 -j8 280 | ``` 281 | 282 | 283 | 284 | ## 4. qemu启动写的驱动 285 | 286 | ### 1. 方法1 287 | 288 | 之后需要用qemu来进行内核的运行并且要制作一个initramfs 289 | 290 | 我们需要用到busybox 291 | 292 | 这里我们先下载一个由于写的时候busybox最新版是`1.36.1` 所以下载`1.36.1` 293 | 294 | ```shell 295 | cd ~ 296 | wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 297 | 298 | tar -xf busybox-1.36.1.tar.bz2 299 | cd busybox-1.36.1 300 | 301 | make menuconfig ARCH=arm64 302 | # 修改配置,选中如下项目,静态编译 303 | # Settings -> Build Options -> [*] Build static binary (no share libs) 304 | 305 | make -j8 306 | 307 | cp ~/busybox-1.36.1/busybox rust-for-linux/linux/ 308 | ``` 309 | 310 | 完整的流程[busybox制作initramfs](./使用busybox制作内存文件系统initramfs.md) 在这里我们到这一步暂时就够了 当然你也可以自己从[0构建一个](https://docs.kernel.org/admin-guide/initrd.html) 311 | 312 | 313 | 314 | 这里我们使用rust-for-linux 的rust 分支 .github中workflows来操作 315 | 316 | 在linux源码目录创建一个qemu-ini.sh 317 | 318 | ```shell 319 | #!/bin/sh 320 | 321 | busybox insmod rust_print.ko 322 | busybox rmmod rust_print.ko 323 | 324 | busybox insmod rust_helloworld.ko 325 | busybox rmmod rust_helloworld.ko 326 | 327 | busybox insmod rust_minimal.ko 328 | busybox rmmod rust_minimal.ko 329 | 330 | busybox reboot -f 331 | ``` 332 | 333 | 再创建一个qemu-initramfs.desc 334 | 335 | ```shell 336 | dir /bin 0755 0 0 337 | dir /sys 0755 0 0 338 | dir /dev 0755 0 0 339 | file /bin/busybox busybox 0755 0 0 340 | slink /bin/sh /bin/busybox 0755 0 0 341 | file /init ./qemu-init.sh 0755 0 0 342 | 343 | file /rust_minimal.ko build/samples/rust/rust_minimal.ko 0755 0 0 344 | file /rust_print.ko build/samples/rust/rust_print.ko 0755 0 0 345 | file /rust_helloworld.ko build/samples/rust/rust_helloworld.ko 0755 0 0 346 | ``` 347 | 348 | 运行 349 | 350 | ```shell 351 | build/usr/gen_init_cpio ./qemu-initramfs.desc > qemu-initramfs.img 352 | ``` 353 | 354 | ```shell 355 | 356 | qemu-system-aarch64 \ 357 | -kernel build/arch/arm64/boot/Image.gz \ 358 | -initrd qemu-initramfs.img \ 359 | -M virt \ 360 | -cpu cortex-a72 \ 361 | -smp 2 \ 362 | -nographic \ 363 | -vga none \ 364 | -no-reboot \ 365 | -append 'root=/dev/sda' \ 366 | | sed 's:\r$::' 367 | ``` 368 | 369 | 可以看到如下输出: 370 | 371 | ```text 372 | .................... 373 | [ 0.381944] No soundcards found. 374 | [ 0.383772] uart-pl011 9000000.pl011: no DMA platform data 375 | [ 0.442688] Freeing unused kernel memory: 2112K 376 | [ 0.443427] Run /init as init process 377 | [ 0.481102] rust_print: Rust printing macros sample (init) 378 | [ 0.481270] rust_print: Emergency message (level 0) without args 379 | [ 0.481333] rust_print: Alert message (level 1) without args 380 | [ 0.481391] rust_print: Critical message (level 2) without args 381 | [ 0.481455] rust_print: Error message (level 3) without args 382 | [ 0.481516] rust_print: Warning message (level 4) without args 383 | [ 0.481577] rust_print: Notice message (level 5) without args 384 | [ 0.481674] rust_print: Info message (level 6) without args 385 | [ 0.481742] rust_print: A line that is continued without args 386 | [ 0.481837] rust_print: Emergency message (level 0) with args 387 | [ 0.481896] rust_print: Alert message (level 1) with args 388 | [ 0.481957] rust_print: Critical message (level 2) with args 389 | [ 0.482021] rust_print: Error message (level 3) with args 390 | [ 0.482102] rust_print: Warning message (level 4) with args 391 | [ 0.482173] rust_print: Notice message (level 5) with args 392 | [ 0.482234] rust_print: Info message (level 6) with args 393 | [ 0.482292] rust_print: A line that is continued with args 394 | [ 0.482453] rust_print: 1 395 | [ 0.482596] rust_print: "hello, world" 396 | [ 0.482884] rust_print: [../samples/rust/rust_print.rs:34] c = "hello, world" 397 | [ 0.483099] rust_print: "hello, world" 398 | [ 0.489795] rust_print: Rust printing macros sample (exit) 399 | [ 0.510844] rust_helloworld: Hello World from Rust module 400 | [ 0.546105] rust_minimal: Rust minimal sample (init) 401 | [ 0.546284] rust_minimal: Am I built-in? false 402 | [ 0.550119] rust_minimal: My numbers are [72, 108, 200] 403 | [ 0.550376] rust_minimal: Rust minimal sample (exit) 404 | [ 0.576403] Flash device refused suspend due to active operation (state 20) 405 | [ 0.576539] Flash device refused suspend due to active operation (state 20) 406 | [ 0.576833] reboot: Restarting system 407 | ``` 408 | 409 | 可以看到我们自己写的 Hello World from Rust module已经打印出来了 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | ### 2. 方法2 418 | 419 | 也可以直接下载一个[debian](https://people.debian.org/~gio/dqib/)的 420 | 421 | debian的镜像解压后可以直接修改 -kernel 为rust-for-linux/linux/build/arch/arm64/boot/Image.gz 以及image.qcow2 422 | 423 | ```shell 424 | qemu-system-aarch64 -machine 'virt' -cpu 'cortex-a57' -m 1G -device virtio-blk-device,drive=hd -drive file=image.qcow2,if=none,id=hd -device virtio-net-device,netdev=net -netdev user,id=net,hostfwd=tcp::2222-:22 -kernel Image -initrd initrd -nographic -append "root=LABEL=rootfs console=ttyAMA0" 425 | ``` 426 | 427 | 由于image.qcow2是虚拟镜像格式所以挂载需要一点特殊的技巧 428 | 429 | ```shell 430 | modprobe nbd max_part=12 431 | qemu-nbd --connect=/dev/nbd0 image.qcow2 432 | mkdir /mnt/test 433 | mount /dev/nbd0p1 /mnt/test/ 434 | cp rust-for-linux/linux/build/samples/rust/rust_helloworld.ko /mnt/test/rust_helloworld.ko 435 | umount /mnt/test 436 | qemu-nbd --disconnect /dev/nbd0 437 | modprobe -r nbd 438 | ``` 439 | 440 | 再次运行 441 | 442 | ```shell 443 | qemu-system-aarch64 -machine 'virt' -cpu 'cortex-a57' -m 1G -device virtio-blk-device,drive=hd -drive file=image.qcow2,if=none,id=hd -device virtio-net-device,netdev=net -netdev user,id=net,hostfwd=tcp::2222-:22 -kernel rust-for-linux/linux/build/arch/arm64/boot/Image.gz -initrd initrd -nographic -append "root=LABEL=rootfs console=ttyAMA0" 444 | ``` 445 | 446 | 之后会进入debian 输入账号密码 root root 447 | 448 | 直接 449 | 450 | ```shell 451 | dmesg -C 452 | insmod /rust_helloworld.ko 453 | #[ 57.144725] rust_helloworld: Hello World from Rust module 454 | dmesg 455 | rmmod rust_helloworld 456 | ``` 457 | 458 | 459 | 460 | ## 5.和内核函数互相调用 461 | 462 | 由于rust-for-linux还在发展中有很多内核函数是没有被封装的,利用的是bindgen来自动生成对C(和一些C++)库的Rust FFI绑定 463 | 464 | 所以我们必须要知道rust-for-linux如何调用c 465 | 466 | > https://rust-lang.github.io/rust-bindgen/introduction.html 467 | > 468 | > 这是官方文档 469 | > 470 | > 对于linux是如何使用的可以看 471 | > 472 | > https://github.com/d0u9/Linux-Device-Driver-Rust/blob/master/00_Introduction_to_Rust_Module_in_Linux/03-Rust_build_processes_in_kernel.md 473 | 474 | linux 对于bindgen的使用是命令行的方式使用,并且放在了Makeflie中处理流程如下 475 | 476 | [![Dependency Graph](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/rust-for-linux.assets/dependency_graph.png)](https://github.com/d0u9/Linux-Device-Driver-Rust/blob/master/00_Introduction_to_Rust_Module_in_Linux/dependency_graph.png) 477 | 478 | 所以我们怎么使用呢在` /rust/kernel/bindings_helper.h`中添加内核头文件 479 | 480 | 例如: 481 | 482 | ```c 483 | #include 484 | ``` 485 | 486 | 之后编写你的c函数比如pci中`pci_set_drvdata` 生成`rust_helper_pci_set_drvdata` rust函数 487 | 488 | 添加在`/rust/kernel/helpers.c`中 489 | 490 | ```c 491 | #include 492 | void rust_helper_pci_set_drvdata(struct pci_dev *pdev, void *data) 493 | { 494 | pci_set_drvdata(pdev, data); 495 | } 496 | EXPORT_SYMBOL_GPL(rust_helper_pci_set_drvdata); 497 | ``` 498 | 499 | 在编译内核的时候会在`/linux/build/rust/bindings`中产生下面2个文件 500 | 501 | - bindings_generated.rs 502 | - bindings_helpers_generated.rs 503 | 504 | ![image-20231117154615901](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/rust-for-linux.assets/image-20231117154615901-0207180-0215569.png) 505 | 506 | 至此可以使用`use kernel::bindings` 来使用`rust_helper_pci_set_drvdata` 507 | 508 | 509 | 510 | ## 6. 模块化驱动开发e1000网卡驱动 511 | 512 | > 我们可以在linux目录之外编写我们的自定义驱动模块,具体参考如下链接 513 | > 514 | > https://github.com/Rust-for-Linux/rust-out-of-tree-module 515 | 516 | ### 0.基础知识 517 | 518 | > 关于e1000在MIT 6.S081中有相关的介绍 519 | > 520 | > https://pdos.csail.mit.edu/6.S081/2020/labs/net.html 521 | > 522 | > https://pdos.csail.mit.edu/6.S081/2020/readings/8254x_GBe_SDM.pdf 523 | 524 | 525 | 526 | 首先我们要了解什么是网卡,网卡和操作系统的交互可以看[参考](./网卡框架.pdf) 527 | 528 | 我们需要了解[e1000](./8254x_GBe_SDM.pdf) 的相关知识,重点看以下: 529 | 530 | - 第2部分是必不可少的,并提供了整个设备的概述。 531 | - 第3.2部分概述了数据包接收的过程。 532 | - 第3.3部分概述了数据包传输,以及第3.4部分。 533 | - 第13部分提供了E1000使用的寄存器的概述。 534 | - 第14部分可能有助于理解我们提供的初始化代码。 535 | 536 | 537 | 538 | ### 1.环境准备 539 | 540 | #### 仓库准备 541 | 542 | 这里我们使用别人已经写过的一些方法仓库不然从0开始写驱动需要进行大量的工作我这里直接fork了一份支持生成外部rust-analyzer 543 | 544 | e1000使用清华os的训练营仓库,只需要填写checkpoint即可,可以省去一些基础工作,不过建议仔细看 545 | 546 | > linux: https://github.com/451846939/rust-for-linux-e1000/tree/rust-e1000 547 | > 548 | > e1000: https://github.com/451846939/e1000-driver/ 549 | 550 | ps:linux内核其实有一份c的e1000驱动所以我们还需要关闭他们然后重新编译内核,clang和llvm推荐使用14 551 | 552 | 编译内核的方法和[2.编译](#2)一样只是这里的环境需要修改按照本身的版本进行 553 | 554 | 如果出现了bindgen 0.56.0下载不下来 555 | 556 | 可以去 https://github.com/rust-lang/rust-bindgen.git 下载然后切换分支使用cargo install 以及可以把bindgen-cli的cli去掉如下: 557 | 558 | ```shell 559 | cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen 560 | ``` 561 | 562 | 563 | 564 | 565 | 566 | #### 开启代码提示 567 | 568 | linux目录中使用 569 | 570 | ```shell 571 | make LLVM=1 O=build rust-analyzer 572 | ``` 573 | 574 | 目录之外如果我们需要开启rust-analyzer提示需要在代码的src平级目录使用 575 | 576 | ```shell 577 | make LLVM=1 -C /mnt/rust-for-linux/linux/build M=$PWD rust-analyzer 578 | ``` 579 | 580 | /mnt/rust-for-linux/linux 替换成你自己的linux目录 581 | 582 | 583 | 584 | 在vscode `.vscode/settings.json`中添加如下 585 | 586 | ```json 587 | "rust-analyzer.linkedProjects": [ 588 | "${workspaceFolder}/e1000-driver/rust-project.json", 589 | "${workspaceFolder}/linux/build/rust-project.json" 590 | ], 591 | ``` 592 | 593 | 594 | 595 | ### 2.完成checkpoint 596 | 597 | 598 | 599 | 600 | 1. 首先在网卡驱动初始化的时候我们需要分配tx_ring和rx_ring的内存空间并返回dma虚拟地址和物理地址 601 | 602 | ```rust 603 | let (tx_ring_vaddr, tx_ring_dma) = kfn.dma_alloc_coherent(alloc_tx_ring_pages); 604 | let (rx_ring_vaddr, rx_ring_dma) = kfn.dma_alloc_coherent(alloc_rx_ring_pages); 605 | ``` 606 | 607 | 对于这里我们一定要理解ring 608 | 609 | ![image-20231117162131653](https://github.com/451846939/TimeMachine/blob/master/src/os/rust-for-linux/rust-for-linux.assets/image-20231117162131653-0215580.png) 610 | 611 | 2. 接着我们需要分配tx_buffer和rx_buffer的内存空间 并返回dma虚拟地址和物理地址 612 | 613 | ```rust 614 | let (mut tx_mbufs_vaddr, mut tx_mbufs_dma) =kfn.dma_alloc_coherent(alloc_tx_buffer_pages); 615 | let (mut rx_mbufs_vaddr, mut rx_mbufs_dma) =kfn.dma_alloc_coherent(alloc_rx_buffer_pages); 616 | ``` 617 | 618 | 619 | 620 | 根据MIT 6.S081的HINT得知 621 | 622 | `e1000_transmit`的流程 623 | 624 | 1. 首先,通过获取E1000_RDT控制寄存器并加一模RX_RING_SIZE,询问E1000下一个等待接收的数据包(如果有)的环索引。 625 | 2. 然后,通过在描述符的状态部分检查E1000_RXD_STAT_DD位来检查是否有新的数据包可用。如果没有,停止。 626 | 3. 否则,将mbuf的`m->len`更新为描述符中报告的长度。使用`net_rx()`将mbuf传递给网络栈。 627 | 4. 然后,使用`mbufalloc()`分配一个新的mbuf以替换刚刚传递给`net_rx()`的mbuf。将其数据指针(`m->head`)编程到描述符中。将描述符的状态位清零。 628 | 5. 最后,更新E1000_RDT寄存器为最后处理的环描述符的索引。 629 | 630 | 631 | 632 | `e1000_recv`流程: 633 | 634 | 1. 通过读取E1000_TDT控制寄存器,询问E1000它期望下一个数据包的TX环索引。 635 | 2. 然后检查环是否溢出。如果在由E1000_TDT索引的描述符中未设置E1000_TXD_STAT_DD,说明E1000尚未完成相应的先前传输请求,因此返回错误。 636 | 3. 否则,使用`mbuffree()`释放从该描述符传输的上一个mbuf(如果有的话)。 637 | 4. 接着,填充描述符。`m->head`指向内存中包的内容,`m->len`是包的长度。设置必要的cmd标志(查看E1000手册中的第3.3节),并储存mbuf的指针以供稍后释放。 638 | 5. 最后,通过将E1000_TDT模TX_RING_SIZE加一来更新环位置。 639 | 6. 如果`e1000_transmit()`成功将mbuf添加到环中,返回0。如果失败(例如,没有可用的描述符传输mbuf),返回-1,以便调用者知道释放mbuf。 640 | 641 | 642 | 643 | 这个mbuffer其实就是ring 里做了一个备份 644 | 645 | 3. 给寄存器设置ring 646 | 647 | ```rust 648 | // set tx descriptor base address and tx ring length 649 | self.regs[E1000_TDBAL].write(self.tx_ring_dma as u32); 650 | self.regs[E1000_TDLEN].write((self.tx_ring.len() * size_of::()) as u32); 651 | 652 | // set rx descriptor base address and rx ring length 653 | self.regs[E1000_RDBAL].write(self.rx_ring_dma as u32); 654 | self.regs[E1000_RDLEN].write((self.rx_ring.len() * size_of::()) as u32); 655 | ``` 656 | 657 | 658 | 659 | 4. 设置中断处理 660 | 661 | ```rust 662 | 663 | // Enable interrupts 664 | // step1 set up irq_data 665 | // step2 request_irq 666 | // step3 set up irq_handler 667 | let irq_data = Box::try_new(IrqData { 668 | dev_e1000: data.dev_e1000.clone(), 669 | res: data.res.clone(), 670 | napi: data.napi.clone(), 671 | })?; 672 | let irq_regist = request_irq(data.irq, irq_data)?; 673 | data.irq_handler 674 | .store(Box::into_raw(Box::try_new(irq_regist)?), Ordering::Relaxed); 675 | ``` 676 | 677 | 678 | 679 | 5. 收包中断处理 680 | 681 | ```rust 682 | fn handle_rx_irq(dev: &net::Device, napi: &Napi, data: &NetData) { 683 | // Exercise4 Checkpoint 1 684 | let mut packets = 0; 685 | let mut bytes = 0; 686 | let recv_vec: Option>> = { 687 | let mut dev_e1k = data.dev_e1000.lock(); 688 | dev_e1k.as_mut().unwrap().e1000_recv() 689 | }; 690 | if let Some(vec) = recv_vec { 691 | packets = vec.len(); 692 | vec.into_iter().for_each(|packet| { 693 | let mut len = packet.len(); 694 | let skb = dev.alloc_skb_ip_align(RXBUFFER).unwrap(); 695 | let skb_buf = 696 | unsafe { from_raw_parts_mut(skb.head_data().as_ptr() as *mut u8, len) }; 697 | skb_buf.copy_from_slice(&packet); 698 | 699 | skb.put(len as u32); 700 | let protocol = skb.eth_type_trans(dev); 701 | skb.protocol_set(protocol); 702 | 703 | napi.gro_receive(&skb); 704 | 705 | bytes += len; 706 | }); 707 | pr_info!("handle_rx_irq {} packets,{} bytes\n", packets, bytes); 708 | } else { 709 | pr_info!("handle_rx_irq no packets\n"); 710 | } 711 | 712 | data.stats 713 | .rx_bytes 714 | .fetch_add(bytes as u64, Ordering::Relaxed); 715 | data.stats 716 | .rx_packets 717 | .fetch_add(packets as u64, Ordering::Relaxed); 718 | } 719 | ``` 720 | 721 | 首先调用收包函数获取收到的包之后,copy到linux中的核心数据结构skb中设置协议栈后把skb发送到linux网络协议栈,最后更新 `data.stats` 中的 `rx_bytes` 和 `rx_packets` 统计信息 722 | 723 | 724 | 725 | 6. 数据发送 726 | 727 | ```rust 728 | /// Corresponds to `ndo_start_xmit` in `struct net_device_ops`. 729 | fn start_xmit( 730 | skb: &SkBuff, 731 | dev: &Device, 732 | data: ::Borrowed<'_>, 733 | ) -> NetdevTx { 734 | pr_info!("start xmit\n"); 735 | // Exercise4 Checkpoint 2 736 | skb.put_padto(bindings::ETH_ZLEN); 737 | let skb_data = skb.len() - skb.data_len(); 738 | let skb_data = skb.head_data(); 739 | dev.sent_queue(skb.len()); 740 | 741 | let mut dev_e1k = data.dev_e1000.lock_irqdisable(); 742 | let len = dev_e1k.as_mut().unwrap().e1000_transmit(skb_data); 743 | drop(dev_e1k); 744 | if len < 0 { 745 | pr_warn!("skb packet:{}", len); 746 | return net::NetdevTx::Busy; 747 | } 748 | let bytes = skb.len(); 749 | let packets = 1; 750 | 751 | skb.napi_consume(64); 752 | data.stats 753 | .tx_bytes 754 | .fetch_add(bytes as u64, Ordering::Relaxed); 755 | 756 | data.stats.tx_packets.fetch_add(packets, Ordering::Relaxed); 757 | dev.completed_queue(packets as u32, bytes as u32); 758 | 759 | return net::NetdevTx::Ok; 760 | } 761 | ``` 762 | 763 | 首先我们要设置以太网帧的最小长度,在设备中记录已发送的数据包长度,调用 `e1000_transmit` 方法,把数据包发送到 E1000 设备, 重新启用中断,如果发送数据包的时候没有成功,可能会存在数据正在接收的情况,所以返回Busy。之后在dev设备中记录已完成的队列和统计信息。 764 | 765 | 766 | 767 | ### 3.验证 768 | 769 | > linux内核其实有一份c的e1000驱动所以我们还需要关闭它们然后重新编译内核 770 | > 771 | > ```shell 772 | > make ARCH=arm64 LLVM=1 O=build menuconfig 773 | > ``` 774 | > 775 | > 使用/ 搜索e1000按数字键 就可以直接到达对应的地方,和 e1000的全部关闭然后编译 776 | > 777 | > ```shell 778 | > make ARCH=arm64 LLVM=1 -j8 779 | > ``` 780 | 781 | 782 | 783 | 首先编译我们的驱动 784 | 785 | ```shell 786 | cd e1000-driver/src/linux 787 | make KDIR=/mnt/rust-for-linux/linux/build 788 | ``` 789 | 790 | 791 | 792 | 这里我们直接使用debian的镜像也就是[4.2中的方法2](#4.2) 793 | 794 | ```shell 795 | qemu-system-aarch64 -machine virt -cpu cortex-a57 -m 1G -device virtio-blk-device,drive=hd -drive file=image.qcow2,if=none,id=hd -device virtio-net-device,netdev=net -netdev user,id=net,hostfwd=tcp::2222-:22 -nographic -append "root=LABEL=rootfs console=ttyAMA0" -initrd initrd -device e1000,netdev=net0,bus=pcie.0 -netdev user,id=net0 -kernel ../arch/arm64/boot/Image 796 | ``` 797 | 798 | 799 | 800 | ```shell 801 | ip address 802 | ``` 803 | 804 | 我们可以看到有 805 | 806 | ```text 807 | 2: eth0: mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 808 | link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff 809 | ``` 810 | 811 | eth0 网卡 812 | 813 | 这时候我们可以用scp 把编译的ko文件复制进来 814 | 815 | ``` 816 | scp xxx@ip:/mnt/rust-for-linux/e1000-driver/src/linux/../e1000_for_linux.ko /root/ 817 | ``` 818 | 819 | 然后禁用我们的eth0 820 | 821 | ```shell 822 | ip link set eth0 down 823 | ``` 824 | 825 | 加载我们用rust写的e1000的驱动 826 | 827 | ```shell 828 | insmod e1000_for_linux.ko 829 | ``` 830 | 831 | 这时候执行`ip l`会发现多了一个eth1 832 | 833 | 启动eth1 834 | 835 | ```shell 836 | ip link set eth1 up 837 | ``` 838 | 839 | 由于没有分配ip所以手动分配ip 840 | 841 | 因为qemu网关是10.0.2.2 842 | 843 | 所以这里分配 844 | 845 | ```shell 846 | ip addr add 10.0.2.20/24 dev eth1 847 | ip route add default via 10.0.2.2 dev eth1 848 | ``` 849 | 850 | 之后执行ping 851 | 852 | ```shell 853 | ping 10.0.2.2 854 | ``` 855 | 856 | 可以看到如下打印 857 | 858 | ```text 859 | PING 10.0.2.2 (10.0.2.2) 56(84) bytes of data. 860 | [ 336.638258] rust_e1000dev: start xmit 861 | [ 336.638671] rust_e1000dev: Read E1000_TDT = 0x0 862 | [ 336.638733] rust_e1000dev: >>>>>>>>> TX PKT 60 863 | [ 336.638844] rust_e1000dev: 864 | [ 336.638844] 865 | [ 336.639136] rust_e1000dev: handle_irq 866 | [ 336.639289] rust_e1000dev: irq::Handler E1000_ICR = 0x83 867 | [ 336.639680] rust_e1000dev: NapiPoller poll 868 | [ 336.639777] rust_e1000dev: Read E1000_RDT + 1 = 0x0 869 | [ 336.639814] rust_e1000dev: RX PKT 64 <<<<<<<<< 870 | [ 336.640057] rust_e1000dev: e1000_recv 871 | [ 336.640057] 872 | [ 336.640605] rust_e1000dev: handle_rx_irq 1 packets, 64 bytes 873 | [ 336.641801] rust_e1000dev: start xmit 874 | [ 336.641883] rust_e1000dev: Read E1000_TDT = 0x1 875 | [ 336.641890] rust_e1000dev: >>>>>>>>> TX PKT 98 876 | [ 336.641972] rust_e1000dev: 877 | [ 336.641972] 878 | [ 336.642191] rust_e1000dev: handle_irq 879 | [ 336.642366] rust_e1000dev: irq::Handler E1000_ICR = 0x83 880 | [ 336.646625] rust_e1000dev: NapiPoller poll 881 | [ 336.646726] rust_e1000dev: Read E1000_RDT + 1 = 0x1 882 | [ 336.646734] rust_e1000dev: RX PKT 98 <<<<<<<<< 883 | [ 336.646835] rust_e1000dev: e1000_recv 884 | [ 336.646835] 885 | [ 336.647052] rust_e1000dev: handle_rx_irq 1 packets, 98 bytes 886 | 64 bytes from 10.0.2.2: icmp_seq=1 ttl=255 time=11.5 ms 887 | [ 337.641037] rust_e1000dev: start xmit 888 | [ 337.641723] rust_e1000dev: Read E1000_TDT = 0x2 889 | [ 337.641802] rust_e1000dev: >>>>>>>>> TX PKT 98 890 | [ 337.642021] rust_e1000dev: 891 | [ 337.642021] 892 | [ 337.642528] rust_e1000dev: handle_irq 893 | [ 337.649010] rust_e1000dev: irq::Handler E1000_ICR = 0x83 894 | [ 337.650481] rust_e1000dev: NapiPoller poll 895 | [ 337.650852] rust_e1000dev: Read E1000_RDT + 1 = 0x2 896 | [ 337.650886] rust_e1000dev: RX PKT 98 <<<<<<<<< 897 | [ 337.651381] rust_e1000dev: e1000_recv 898 | [ 337.651381] 899 | [ 337.651737] rust_e1000dev: handle_rx_irq 1 packets, 98 bytes 900 | 64 bytes from 10.0.2.2: icmp_seq=2 ttl=255 time=13.1 ms 901 | ``` 902 | 903 | 完成了我们的e1000网卡的checkpoint编写 904 | -------------------------------------------------------------------------------- /优秀文档参考/nnull-block-driver分析-童浩轩.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust for Linux驱动模块开发报告-童浩轩 3 | Date: 2023-11 4 | Tags: 5 | - Author: 童浩轩 6 | - Reports Repo: https://github.com/Tunghohin/rust-for-linux/blob/main/reports/final_report.md 7 | --- 8 | 9 | # null block driver 分析 10 | 11 | 项目源码地址 12 | 13 | > https://github.com/metaspace/linux/tree/null_block-RFC 14 | 15 | ## 什么是 null block driver? 16 | 17 | "Null block driver" 通常是指一个虚拟的块设备驱动程序,它并不代表任何真实的硬件设备,而是用于模拟块设备的行为。在 linux 中,C 版本的 null block driver 在 drivers/block/null_blk 中。 18 | 19 | linux 系统中,块设备系统的结构大概可以看成 20 | 21 | 22 | 23 | 进程操作 filesystem,filesystem 24 | 25 | 操作系统为每个进程分配了一个 staging queue,而 hardware dispatch queues 的数量取决于硬件 26 | 27 | ## 代码分析 28 | 29 | #### struct blk_mq_tag_set 30 | 31 | struct blk_mq_tag_set 是 Linux 内核中用于表示块多队列(blk-mq)层中队列标签集合的数据结构。在 blk-mq 中,队列标签(tag)用于标识和跟踪块设备上发出的 I/O 请求。 32 | 33 | C 版本 struct blk_mq_tag_set 34 | 35 | ```c 36 | struct blk_mq_tag_set { 37 | struct blk_mq_ops *ops; // 操作函数指针 38 | unsigned int flags; // 标志位 39 | unsigned int nr_hw_queues; // 硬件队列数量 40 | unsigned int queue_depth; // 每个队列的深度(即队列标签的数量) */ 41 | struct request_queue *queue; // 关联的请求队列 42 | void *driver_data; // 驱动程序私有数据指针 43 | ... 44 | }; 45 | ``` 46 | 47 | 在 rust 中,一个实用的技巧是,将 C 结构体指针用 rust 的 struct wrap 一层 48 | 49 | ```rust 50 | pub struct TagSet { 51 | inner: UnsafeCell, 52 | _p: PhantomData, 53 | } 54 | ``` 55 | 56 | 其中需要注意的点是,特征约束 T 在结构体中没有使用,我们只想让其发挥约束的作用,如果强行创建一个元素占用显然是不优的。但是使用 PhantomData 可以在类型系统中表示这个类型的存在,总的来说,PhantomData 是 Rust 中用于表达类型系统中一些约束、关系或者存在性的一种方式,它在类型层面上提供了一些信息,而不引入实际的运行时开销。 57 | 58 | 注意到,blk_mq_tag_set 中,有个 ops 结构体,里面存了一系列函数指针 59 | 60 | 61 | 62 | 在 rust 中,我们要怎样实现呢? 63 | 64 | /kernel/rust/macro/vtable.rs 中,提供了一个 vtable 宏,我们可以通过 table 宏,将 rust 代码转换成 C 代码。 65 | 一个被标注了 #[vtable] attribute 的 trait,他的实现不应该在 rust 端被调用。 66 | 67 | 使用方法如下(以 commit_rqs 为例) 68 | 69 | ```rust 70 | #[macro::vtable] 71 | pub trait Operations: Sized { 72 | ... //以commit_rqs为例 73 | type HwData: ForeignOwnable; 74 | 75 | ... 76 | 77 | fn commit_rqs( 78 | hw_data: ::Borrowed<'_>, 79 | queue_data: ::Borrowed<'_>, 80 | ); 81 | ... 82 | } 83 | 84 | pub(crate) struct OperationsVtable(PhantomData); 85 | 86 | impl OperationsVtable { 87 | ... 88 | unsafe extern "C" fn commit_rqs_callback(hctx: *mut bindings::blk_mq_hw_ctx) { 89 | let hw_data = unsafe { T::HwData::borrow((*hctx).driver_data) }; 90 | 91 | // SAFETY: `hctx` is valid as required by this function. 92 | let queue_data = unsafe { (*(*hctx).queue).queuedata }; 93 | 94 | // SAFETY: `queue.queuedata` was created by `GenDisk::try_new()` with a 95 | // call to `ForeignOwnable::into_pointer()` to create `queuedata`. 96 | // `ForeignOwnable::from_foreign()` is only called when the tagset is 97 | // dropped, which happens after we are dropped. 98 | let queue_data = unsafe { T::QueueData::borrow(queue_data) }; 99 | T::commit_rqs(hw_data, queue_data) 100 | } 101 | ... 102 | 103 | const VTABLE: bindings::blk_mq_ops = bindings::blk_mq_ops { 104 | ... 105 | commit_rqs: Some(Self::commit_rqs_callback), 106 | ... 107 | }; 108 | 109 | pub(crate) const unsafe fn build() -> &'static bindings::blk_mq_ops { 110 | &Self::VTABLE 111 | } 112 | } 113 | ``` 114 | 115 | 其中 types::ForeignOwnable 表示可用于 C 于 rust 之间的资源所有权转移 116 | 117 | ### bio 118 | 119 | 在 Linux 内核中,struct bio 是用于表示块 I/O 操作的结构体。这个结构体包含了有关块 I/O 操作的重要信息,比如要执行的操作、数据缓冲区的位置等。 120 | 121 | struct bio 通常涉及到一个或多个 struct bio_vec,它们描述了块 I/O 操作的数据块的位置和大小。struct bio_vec 中包含了指向数据缓冲区的指针、偏移量和长度。 122 | 123 | ```c 124 | struct bio { 125 | struct bio *bi_next; // 链表中的下一个 bio 结构体 126 | struct block_device *bi_bdev; // 目标块设备 127 | unsigned long bi_flags; // 标志位,用于描述 I/O 操作的状态 128 | bio_flags_t bi_rw; // 读写标志位,指示是读操作还是写操作 129 | unsigned short bi_vcnt; // 向量个数,用于描述 bio 中的数据块数量 130 | unsigned short bi_idx; // 当前处理的数据块索引 131 | unsigned int bi_size; // 数据块大小 132 | unsigned int bi_iter.bi_sector; // 起始扇区号 133 | struct bio_vec bi_io_vec; // 包含数据缓冲区的信息 134 | struct bio_set *bi_pool; // 内存池指针 135 | ... 136 | }; 137 | ``` 138 | 139 | 在 rust 里,依然采用指针的形式对其进行封装,但是值得注意的是,采用了 NonNull 指针,而非裸指针 140 | 141 | ```rust 142 | pub struct Bio<'a>( 143 | NonNull, 144 | core::marker::PhantomData<&'a ()>, 145 | ); 146 | ``` 147 | 148 | NonNull 通常与裸指针一起使用,把裸指针 wrap 一层,NonNull 在类型层面上确保了指针非空,减少了犯错的概率。 149 | 150 | bio 类似链表节点,我们可以对其进行 Iterator trait 的封装,使迭代更安全。 151 | 152 | ```rust 153 | pub struct BioIterator<'a> { 154 | pub(crate) bio: Option>, 155 | } 156 | 157 | impl<'a> core::iter::Iterator for BioIterator<'a> { 158 | type Item = Bio<'a>; 159 | 160 | #[inline(always)] 161 | fn next(&mut self) -> Option> { 162 | if let Some(current) = self.bio.take() { 163 | self.bio = current.next(); 164 | Some(current) 165 | } else { 166 | None 167 | } 168 | } 169 | } 170 | ``` 171 | 172 | ### 驱动 173 | 174 | 待续 175 | -------------------------------------------------------------------------------- /优秀文档参考/readme.md: -------------------------------------------------------------------------------- 1 | ## 优秀文档参考 2 | 优秀文档参考 3 | -------------------------------------------------------------------------------- /优秀文档参考/ye-junzhe-叶钧喆-Rust_for_Linux驱动模块开发.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: Rust for Linux驱动模块开发报告-叶钧喆 3 | Date: 2023-11 4 | Tags: 5 | - Author: 叶钧喆 6 | - Reports Repo: https://github.com/ye-junzhe/rust-for-linux/blob/main/README.md 7 | --- 8 | 9 | # rust-for-linux 10 | 11 | 12 | - [rust-for-linux](#rust-for-linux) 13 | - [Env](#env) 14 | - [编译内核(Exercise 1)](#编译内核exercise-1) 15 | - [编译Rust模块helloworld(Exercise 2)](#编译rust模块helloworldexercise-2) 16 | - [安装QEMU](#安装qemu) 17 | - [利用busybox制作文件系统](#利用busybox制作文件系统) 18 | - [e1000网卡驱动(Exercise 3)](#e1000网卡驱动exercise-3) 19 | - [Preferences](#preferences) 20 | - [先学习编译fujita e1000,了解busybox加载模块的流程](#先学习编译fujita-e1000了解busybox加载模块的流程) 21 | - [编译rust_e1000.ko](#编译ruste1000ko) 22 | - [Linux kernel网络参数设置](#linux-kernel网络参数设置) 23 | - [QEMU启动参数](#qemu启动参数) 24 | - [作业3 e1000-driver 添加代码](#作业3-e1000-driver-添加代码) 25 | - [e1000网卡驱动(Exercise 4)](#e1000网卡驱动exercise-4) 26 | - [停用内置e1000网卡驱动](#停用内置e1000网卡驱动) 27 | - [作业4添加代码](#作业4添加代码) 28 | - [实习项目](#实习项目) 29 | - [适配Rust for Linux内核](#适配rust-for-linux内核) 30 | - [对image做处理来适配QEMU启动](#对image做处理来适配qemu启动) 31 | - [fdisk显示img信息](#fdisk显示img信息) 32 | - [计算第一个偏移量,mount img1](#计算第一个偏移量mount-img1) 33 | - [计算第二个偏移量,mount img2](#计算第二个偏移量mount-img2) 34 | - [可以看到运行在6.7.0的Linux内核上](#可以看到运行在670的linux内核上) 35 | - [实现Rust Uart串口驱动](#实现rust-uart串口驱动) 36 | 37 | 38 | ## Env 39 | 40 | - Debian GNU/Linux 12 (bookworm) aarch64 in Parallel Desktop Virtual Machine 41 | - QEMU qemu-system-aarch64 version 7.2.5 42 | - rust-for-linux arm64 6.6.0-rc4 43 | - BusyBox v1.35.0 (Debian 1:1.35.0-4+b3) multi-call binary. 44 | 图片 45 | 46 | ## 编译内核(Exercise 1) 47 | 48 | 使用fujita的linux分支 49 | 50 | ```bash 51 | git clone https://github.com/fujita/linux -b rust-dev --depth=1 52 | ``` 53 | 54 | - https://github.com/rcore-os/rust-for-linux/blob/main/exercise1.md 55 | 56 | 按照指导安装依赖, 57 | - 但是注意cargo install bindgen,没有后面的cli,因为fujiya使用的是比较早的linux kernel版本 58 | 59 | ```bash 60 | make ARCH=arm64 LLVM=1 O=build defconfig 61 | 62 | make ARCH=arm64 LLVM=1 O=build menuconfig 63 | #set the following config to yes 64 | General setup 65 | ---> [*] Rust support 66 | 67 | make ARCH=arm64 LLVM=1 -j8 68 | ``` 69 | 70 | image 71 | 72 | ## 编译Rust模块helloworld(Exercise 2) 73 | 74 | ### 安装QEMU 75 | 76 | ```bash 77 | sudo apt install qemu-user qemu-system-arm # 安装qemu 7.x.x 78 | ``` 79 | 80 | > qemu 8.x.x以上运行DQIB kernel会报错 => "...net user not compiled into kernel..." 81 | 82 | ### 利用busybox制作文件系统 83 | 84 | - [Debian Quick Image Baker(DQIB)](https://people.debian.org/~gio/dqib/) 85 | 另一个选择,但是不是很会用 86 | 87 | ```bash 88 | make menuconfig ARCH=arm64 89 | 90 | Settings ---> 91 | --- Build Options 92 | [*] Build static binary (no shared libs) 93 | 94 | make -j20 95 | make install 96 | ``` 97 | 98 | `cat qemu-init.sh` 99 | 100 | ```bash 101 | #!/bin/sh 102 | busybox echo "[INFO] Init from a minimal initrd!" 103 | busybox echo "============================" 104 | busybox poweroff -f 105 | ``` 106 | 107 | `cat qemu-initramfs.desc` 108 | 109 | ```bash 110 | dir /bin 0755 0 0 111 | file /bin/busybox busybox 0755 0 0 112 | slink /bin/sh /bin/busybox 0755 0 0 113 | file /init qemu-init.sh 0755 0 0 114 | ``` 115 | 116 | ```bash 117 | # 制作img 118 | # 每次修改完qemu-init.sh & qemu-initramfs.desc要记得重新制作 119 | ../linux-fujita/build/usr/gen_init_cpio qemu-initramfs.desc > qemu-initramfs.img 120 | ``` 121 | 122 | - https://github.com/rcore-os/rust-for-linux/blob/main/exercise2.md 123 | 124 | 按照指导添加代码 125 | 126 | ```bash 127 | Kernel hacking 128 | ---> Sample Kernel code 129 | ---> Rust samples 130 | ---> Print Helloworld in Rust (NEW) # 其它也要勾选 131 | ``` 132 | `cat qemu-init.sh` 133 | 134 | ```bash 135 | #!/bin/sh 136 | busybox echo "[INFO] Init from a minimal initrd!" 137 | busybox echo "============================" 138 | busybox insmod rust_helloworld.ko 139 | 140 | busybox poweroff -f 141 | ``` 142 | 143 | `cat qemu-initramfs.desc` 144 | 145 | ```bash 146 | dir /bin 0755 0 0 147 | file /bin/busybox busybox 0755 0 0 148 | slink /bin/sh /bin/busybox 0755 0 0 149 | file /init qemu-init.sh 0755 0 0 150 | file /rust_helloworld.ko ../linux-fujita/build/samples/rust/rust_helloworld.ko 0755 0 0 151 | ``` 152 | 153 | - QEMU启动参数 154 | 155 | ```bash 156 | #!/bin/sh 157 | 158 | qemu-system-aarch64 \ 159 | -machine 'virt' \ 160 | -cpu 'cortex-a57' \ 161 | -m 1G \ 162 | -netdev user,id=net,hostfwd=tcp::2222-:22 \ 163 | -kernel /home/junzhe/Dev/Software/linux-fujita/build/arch/arm64/boot/Image \ 164 | -initrd ../busybox/qemu-initramfs.img \ 165 | -nographic \ 166 | -append "root=LABEL=rootfs console=ttyAMA0" \ 167 | ``` 168 | 169 | 图片 170 | 171 | 172 | ## e1000网卡驱动(Exercise 3) 173 | 174 | - https://github.com/rcore-os/rust-for-linux/blob/main/exercise3.md 175 | 176 | ### Preferences 177 | 178 | - https://github.com/fujita/rust-e1000 179 | - https://github.com/fujita/linux/tree/rust-e1000 180 | 181 | 图片 182 | 183 | ### 先学习编译fujita e1000,了解busybox加载模块的流程 184 | 185 | #### 编译rust_e1000.ko 186 | 187 | ```bash 188 | git clone https://github.com/fujita/rust-e1000 189 | make KDIR=/home/junzhe/Dev/Software/linux-fujita/build/ LLVM=1 190 | ``` 191 | 192 | `cat qemu-initramfs.desc` 193 | 194 | ```bash 195 | dir /bin 0755 0 0 196 | file /bin/busybox busybox 0755 0 0 197 | slink /bin/sh /bin/busybox 0755 0 0 198 | file /init qemu-init.sh 0755 0 0 199 | file /rust_helloworld.ko ../linux-fujita/build/samples/rust/rust_helloworld.ko 0755 0 0 200 | file /rust_e1000.ko ../rust-e1000/rust_e1000.ko 0755 0 0 # 新增 201 | ``` 202 | 203 | #### Linux kernel网络参数设置 204 | 205 | `cat qemu-init.sh` 206 | 207 | ```bash 208 | #!/bin/sh 209 | 210 | busybox echo "[INFO] Init from a minimal initrd!" 211 | busybox echo "============================" 212 | busybox echo "[INFO] Modules Loaded" 213 | busybox insmod rust_helloworld.ko 214 | busybox insmod rust_e1000.ko 215 | busybox echo "============================" 216 | busybox echo "[INFO] Network configured" 217 | busybox echo "IP set to 192.168.100.224" 218 | busybox ifconfig lo 127.0.0.1 netmask 255.0.0.0 up 219 | busybox ifconfig eth0 192.168.100.224 netmask 255.255.255.0 broadcast 192.168.100.255 up 220 | busybox ip addr 221 | busybox echo "============================" 222 | 223 | export 'PS1=(kernel) >' 224 | busybox sh 225 | 226 | busybox poweroff -f 227 | ``` 228 | 229 | #### QEMU启动参数 230 | 231 | ```bash 232 | qemu-system-aarch64 \ 233 | -machine 'virt' \ 234 | -cpu 'cortex-a57' \ 235 | -m 1G \ 236 | -kernel /home/junzhe/Dev/Software/linux-fujita/build/arch/arm64/boot/Image \ 237 | -initrd ../busybox/qemu-initramfs.img \ 238 | -netdev tap,ifname=tap0,id=tap0,script=no,downscript=no -device e1000,netdev=tap0 \ 239 | -nographic \ 240 | -append "root=LABEL=rootfs console=ttyAMA0" \ 241 | ``` 242 | 243 | - ping通 244 | 245 | 图片 246 | 247 | 248 | ### 作业3 e1000-driver 添加代码 249 | 250 | - https://github.com/yuoo655/e1000-driver/tree/main/src 251 | 252 | - 修改后的代码 https://github.com/ye-junzhe/e1000-driver 253 | 254 | - 增加自定义函数并编译成功 255 | 256 | image 257 | 258 | ## e1000网卡驱动(Exercise 4) 259 | 260 | ### 停用内置e1000网卡驱动 261 | 262 | 在menuconfig中搜索e1000,并停用 263 | 264 | ### 作业4添加代码 265 | 266 | - 修改后的代码 https://github.com/ye-junzhe/e1000-driver 267 | 268 | 编译后对e1000模块进行加载 269 | 270 | image 271 | 272 | image 273 | 274 | image 275 | 276 | ```bash 277 | busybox insmod e1000_for_linux.ko 278 | busybox ifconfig eth0 up 279 | busybox ifconfig eth0 10.0.2.20 280 | busybox ip route add default via 10.0.2.2 dev eth0 281 | ``` 282 | 283 | - ping通外网 284 | image 285 | 286 | ## 实习项目 287 | 288 | 在树莓派模拟器上,适配Rust for Linux内核,并实现Rust Uart串口驱动 289 | 290 | ### 适配Rust for Linux内核 291 | 292 | #### 对image做处理来适配QEMU启动 293 | 294 | - 下载Raspberry Pi OS image: https://www.raspberrypi.com/software/operating-systems/ 295 | 296 | ##### fdisk显示img信息 297 | 298 | 图片 299 | 300 | ##### 计算第一个偏移量,mount img1 301 | 302 | 图片 303 | 304 | - 由于较新版的Raspberry Pi OS不再支持默认用户名密码登陆,所以需要在镜像根目录添加userconf.txt,手动添加用户,利用openssl生成密码密文 305 | 306 | `echo 'raspberry' | openssl passwd -6 -stdin` 307 | 308 | 图片 309 | 310 | ##### 计算第二个偏移量,mount img2 311 | 312 | 图片 313 | 314 | - 复制出内核vmlinuz(后面会替换成rust-for-linux内核)和文件系统initrd 315 | 316 | 图片 317 | 318 | - 适配rust-for-linux 内核 319 | 320 | ```bash 321 | # 目前最新的 322 | git clone https://github.com/Rust-for-Linux/linux -b rust-dev --depth=1 323 | 324 | make ARCH=arm64 LLVM=1 O=build defconfig 325 | 326 | make ARCH=arm64 LLVM=1 O=build menuconfig 327 | #set the following config to yes 328 | General setup 329 | ---> [*] Rust support 330 | 331 | make ARCH=arm64 LLVM=1 -j8 332 | ``` 333 | 334 | - QEMU 运行 335 | 336 | ```bash 337 | qemu-system-aarch64 \ 338 | -cpu cortex-a57 \ 339 | -M virt \ 340 | -m 1G \ 341 | -kernel ../linux/build/arch/arm64/boot/Image \ 342 | -initrd ../raspberry-pi/initrd.img \ 343 | -drive file=../raspberry-pi/2023-10-10-raspios-bookworm-arm64-lite.img,if=none,id=drive0,cache=writeback -device virtio-blk,drive=drive0,bootindex=0 \ 344 | -append 'root=/dev/vda2 noresume rw' \ 345 | -nographic \ 346 | -no-reboot \ 347 | ``` 348 | 349 | ##### 可以看到运行在6.7.0的Linux内核上 350 | 351 | 图片 352 | 353 | ### 实现Rust Uart串口驱动 354 | 355 | Linux 中的 UART 驱动通常作为内核的一部分提供,而不是独立于内核的用户空间程序。UART 驱动的源代码位于 Linux 内核源代码的 drivers/tty/serial/ 目录下。在这个目录下,你可以找到多个 UART 驱动,每个驱动通常对应于支持的特定硬件。 356 | 357 | 358 | 359 | TODO: 360 | - https://github.com/rcore-os/virtio-drivers 361 | --------------------------------------------------------------------------------