5个版本
0.1.11 | 2023年8月23日 |
---|---|
0.1.10 | 2023年8月14日 |
563 在 编码
每月64次下载
60KB
1.5K SLoC
Syscall Encode
所以,我们做函数调用的方式相当好。在Rust中,大约是...
fn some_functionality(some_arg: ArgType, ...) -> ReturnType...;
另一方面,系统调用看起来不像这样。它们通常更像是将一堆东西放入一些寄存器中,然后发出一个“跳转”指令。当然,在底层,正常的函数调用“没有区别”,但问题是语言没有像对普通函数那样提供对系统调用的良好抽象。
相反,我们依赖于标准库,这可能依赖于libc或其他crate来发出系统调用,部分原因是因为编写它们很痛苦。幸运的是,大多数人都是为像真正的操作系统那样的真实库编写代码。但是……如果我们是编写操作系统的人呢?如果我们有一个简单的方法来定义系统调用参数,自动将它们编码到寄存器中,半自动推导系统调用表,并摆脱手动实现系统调用类型这种烦人的单调工作,那岂不是很好?
这就是这个crate的作用。
系统调用实际上是一组东西的集合
- 一个定义参数的类型。
- 一个定义错误的类型。
- 一个定义成功返回值的类型。
- 一个数字。
- 一个内核端接收器。
该系统调用可以针对实现了SyscallAbi特质的某个ABI发出。在内核端,我们定义一个处理程序来捕获传入的系统调用并将它们传递给syscall_api宏生成的代码。
以类型而不是函数的形式定义系统调用的两个优点。第一个优点是,我们可以使用有能力的语言类型系统来约束系统调用类型的行为和使用,这也是另一个原因。
例如,在Rust中,一个常见的模式是通过构造来确保代码的正确性。在这种情况下,通过限制结构体的创建方式,您限制了API用户的错误操作能力。如果我们有一个名为Foo的系统调用,它只能通过调用系统调用Bar来创建,那么我们已确保(在不使用unsafe的情况下)用户不能在先调用Bar之前发出对Foo的调用。当然,内核需要比盲目地假设要小心一点。但这有助于用户空间代码避免某些类别的错误。
这是否安全?
嗯。它通过Miri严格验证通过。至少,在测试框架中,它不会实际发出系统调用。它确实使用了unsafe,但每种情况都有文档记录。对于系统调用,你必须预料到会有一些unsafe。
它有多快?
我们提供了两种定义系统调用的方式。一种是使用“常规”API(实现SyscallApi trait),它可以应用于任何派生自SyscallEncodable trait(可以派生)的类型。另一种是“快速”API,它要求类型实现一系列Into和From方法,以便将其用作系统调用(实现SyscallFastApi trait)。
如果所讨论的系统调用是性能关键且可以轻松适应由ABI定义的寄存器,请使用SyscallFastApi trait。否则请使用SyscallApi(它更易于使用且更容易实现,但速度不是很快)。
进行基准测试!
在我的Mac M2上
Running benches/encode.rs (target/release/deps/encode-913d3832e9f8a495)
encode_normal time: [175.53 ns 176.04 ns 176.59 ns]
change: [+0.2809% +0.6424% +1.0522%] (p = 0.00 < 0.05)
Change within noise threshold.
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe
encode_fast time: [2.3385 ns 2.3446 ns 2.3514 ns]
change: [+0.6778% +1.0142% +1.3560%] (p = 0.00 < 0.05)
Change within noise threshold.
Found 7 outliers among 100 measurements (7.00%)
4 (4.00%) high mild
3 (3.00%) high severe
依赖项
~0.3–1MB
~22K SLoC