0%

2024秋冬季开源操作系统训练营第三阶段总结报告_郑昱可

内容

三阶段的项目我选择了Rust for Linux驱动开发方向。

具体内容是用Rust重写PL011串口驱动,并能通过跨内核驱动框架,使驱动在arceos和linux运行

这次的项目基于Raspi4b or Raspi3b in Qemu进行开发。

我使用的开发的仓库地址是(R4L_DRV)[https://github.com/Oveln/R4L_DRV]

练习部分

内容

练习部分分三个

  • 用Rust for Linux写一个简单的杂项设备驱动,能做到输入和输出
  • 用跨内核驱动框架,实现一个设备驱动,能控制Raspi4b上的某个引脚,从而控制LED的亮灭

遇到的困难

此前我完全没有接触过驱动的编写,对硬件的原理知之甚少。经过很长一段时间的RTFM,以及了解硬件编程相关的知识。

同时对Linux设备驱动相关的内容也进行了比较充足的了解。

收获

练习部分的收获主要是

  • 第一次接触了Linux的设备驱动,对文件树中的/dev/xxx的设备文件有了认知
  • 更加明白了什么叫Every thing is File
  • 稍微看的懂电路原理图了x
  • 对很长的文档的恐惧逐渐消退

实战部分:使用Rust重写PL011驱动

问题一

串口驱动的内容相较于简单的GPIO繁琐且复杂,需要我进一步的了解Linux的串口子系统tty-uart_driver-uart_port相关的注册关系。

在经过一些努力

  • RTFS
  • 找网上的资料学习(特别感谢野火的文档教程

这个问题逐渐的被解决了

问题二

Rust for Linux经过数年的发展,在某些方面有了完备的抽象。但这次项目所涉及的uart_driver、uart_port、console等结构体没有被支持。

这个问题没什么简单的办法,在Rust for Linux社区中也没有找到好的实现。只能自己做。

如何抽象函数指针

比如,在uart_port中有很多void*类型的函数指针,如何对这些函数指针进行良好的Rust抽象就成了一大问题。

我在R4L的代码中找到的解决方案是

1
2
3
4
struct ops {
void* print_int(int)
void* print_char(char)
}

以上代码可以通过Rust进行如下封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
pub struct OperationsVtable<T>(marker::PhantomData<T>);
pub trait Ops {
fn print_int(x: i32);
fn print_char(x: char);
}
impl<T: Ops> OperationsVtable<T> {
unsafe extern "C" fn print_int(x: c_int) {
T::print_int(x);
}
unsafe extern "C" fn print_char(x: c_char) {
T::print_char(x);
}

const VTABLE: bindings::ops = bindings::ops {
print_int: Some(Self::print_int),
print_char: Some(Self::print_char)
};

pub(crate) unsafe fn build() -> *const bindings::ops {
&Self::VTABLE as *const _
}
}

// 在定义了上面三段后就可以定义一个自己的Ops

struct MyOps;

impl Ops for MyOps {
...
}

// 之后想要得到MyOps的对应的C的ops结构体只需要

let ops = OperationsVtable<MyOps>::build();

现在的成果和收获

对Linux的C代码中的serial_core.h中的uart_driver和uart_port以及console做了比较完整的抽象。

我也能够比较好的使用Rust中的Pin使数据固定在内存上的某个地方了。