7 个不稳定版本 (3 个重大更改)
0.4.1 | 2020 年 10 月 28 日 |
---|---|
0.4.0 | 2020 年 10 月 28 日 |
0.3.0 | 2020 年 10 月 27 日 |
0.2.1 | 2020 年 9 月 28 日 |
0.1.1 | 2020 年 9 月 18 日 |
#661 in 内存管理
每月 28 次下载
用于 redox-buffer-pool
23KB
323 行
guard-trait-rs
通过强制执行对受保护内存使用的一定限制,为与其他进程或内核(特别是 io_uring
)共享的内存提供安全抽象。
lib.rs
:
该 crate 提供了一种内存保护机制,其接口在某些方面类似于 core::pin::Pin
。
动机
该 crate 尝试解决的问题是在与另一个进程或内核(例如通过 io_uring
)共享的内存中可能发生数据竞争的问题。例如,如果在系统调用之后原始线程继续执行时内存仍然共享,则原始缓冲区仍然可以被访问,而系统调用允许处理程序继续使用该内存。这种情况不会发生在传统的阻塞系统调用中;内核仅在系统调用期间访问内存,此时进程不能暂时做其他任何事情。
然而,对于像 io_uring
这样的更高级的异步接口,它从不复制内存,系统调用开始后内存仍然可以使用,这可能导致共享内存的两个演员之间发生数据竞争。为了防止这种数据竞争,无法
- 在内核写入内存的同时读取内存,或者在内核读取内存的同时写入内存。这就像Rust的别名规则一样:我们可以允许内核和此进程同时读取一个缓冲区,例如在系统调用 write(2) 中,我们可以临时将一个或多个缓冲区的独占所有权交给内核,当内核即将写入它们时,或者我们可以完全避免与内核共享内存,但我们不能让任一操作者在另一操作者有任何访问权限时拥有可变访问权限。 (别名不变性)
- 在内核读取或写入内存的同时回收内存。这听起来很简单:我们只是不希望缓冲区被用于其他目的,无论是通过将内存返回到堆上,以便内核在不需要时覆盖它,还是可能破坏栈变量。(回收不变性)
术语“内核”不一定是共享内存的另一个操作者;例如,在Redox中,io_uring
接口可以在常规用户空间进程之间独立工作。此外,虽然这是一个相对较窄的用例,但这也可以用于保护设备驱动程序中DMA内存的安全包装,需要一些额外的限制(关于缓存一致性)以使其工作。
不幸的是,这种缓冲区共享逻辑与当前的异步生态系统不太兼容,其中几乎所有I/O都是使用常规借用切片进行的,引用只是可随时取消的借用,甚至包括泄漏。当您使用同步(但非阻塞)系统调用时,进程或内核可以执行,这个功能就非常完美。相反,io_uring
是异步的,这意味着内核可以在我们的程序执行时读写缓冲区。因此,如果未来在本地存储一个数组,由内核在 io_uring
中别名,那么未来在 Drop
ed 时,如果不能阻止内核以任何合理的方式再次使用内存,那么就不能停止内核使用内存。更糟糕的是,未来可以在任何时候泄漏,堆栈上分配的数组也可以在内核仍在使用该内存(例如,作为从套接字写入数据的缓冲区)时被丢弃。如果之后再次将堆栈上的(可变)缓冲区用于常规变量...任意程序损坏!
为了解决这两个问题,我们需要一种方法来标记内存区域为“被内核借用”(可变或不可变)以及“不可丢弃”。由于Rust借用检查器很智能,任何具有比 'static
短的生存期的可变引用都可以简单地泄漏,指针可以再次使用。这排除了任何 'a
引用,它们可能在外部借用中再次使用,可能以可变方式使用。然而,不可变静态引用是完全无害的,因为它们不能被丢弃也不能以可变方式访问,不可变别名始终是允许的。
因此,所有将要用于安全代码的缓冲区都必须是拥有的。这意味着堆分配的对象(因为我们假设整个堆都具有 'static
生存期,并且分配将永远存在,直到显式释放),具有保护机制的缓冲池,以及静态引用(可变和不可变)。然而,我们可以允许借用数据,但由于生命周期的语义以及编译器根本不知道内核也参与其中,这需要不安全代码。
请考虑阅读以下链接以获取更多关于这些挑战的信息:"使用 io_uring
的心理实验" 和 "关于 io-uring
的笔记"。
接口
guard_trait
解决这个问题的方法是添加两个简单的特质:Guarded
和 GuardedMut
。对于实现 Deref
、StableDeref
以及 'static
的每个指针类型,Guarded
都会自动实现。同样地,在相同的条件下,如果指针实现了 DerefMut
,则 GuardedMut
也会实现。因此,几乎所有拥有容器类型,如 Arc
、Box
、Vec
等,都实现了这些特质,因此可以与基于完成的接口一起使用。
对于在类型级别无法确保某个指针遵循保护不变式的情况,也存在 AssertSafe
,但它的初始化是不安全的。
缓冲区也可以以自引用的方式映射,类似于 owning-ref
的工作方式,使用 GuardedExt::map
和 GuardedMutExt::map_mut
。当需要切片索引时,这一点尤为重要,因为限制进行 I/O 的字节数的唯一方法是缩短切片。
依赖项
~12KB