Rust for Linux 源码导读 | Ref 引用计数容器

Posted Rust语言中文社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Rust for Linux 源码导读 | Ref 引用计数容器相关的知识,希望对你有一定的参考价值。

try_reserve 这几个之前未稳定的特性。alloc。为 alloc 添加了更加模块化的选项,以便禁用一些他们不需要的功能:no_rcno_sync,主要是为上游 Rust 项目添加。
  • 更严格的代码、文档和新的  lint
  • 抽象和驱动程序更新。添加了序列锁、电源管理回调的抽象,io 内存(readX/writeX)、irq 芯片和高级流处理程序,gpio 芯片(包括 irq 芯片)、设备、amba 设备和驱动程序以及证书。此外,也改进并简化了 Refrefcount_t 支持)对象并用它替换了 Rust 的 Arc 的所有实例。完全地从 alloc crate 中删除了 ArcRc
  • 从现在开始,Rust for linux 团队将开始定期提交补丁,每两周左右。

    除了来自 Arm、Google 和 Microsoft 的支持外,这次该团队又收到一封来自红帽的信:红帽对 Rust 用于内核的工作也非常感兴趣(There is interest in using Rust for kernel work that Red Hat  is considering)。

  • 来代替 Arc

    Rust for Linux 中这个 kernel crate 中之前使用的是 Arc ,但是现在换成了 Ref。通过查看相关PR ,可以了解其中主要有两点原因:

    1. 最大化利用现有的 C 代码 和 消除恐慌(Panic)。内核中已经有了引用计数的实现 refcount_t,而且它超过引用计数的阈值时,不是 Panic(abort) 而是返回最大值(饱和加法)。因为这个原因,也使用 `RBTree`(红黑树)[6] 大小,引用计数加法超过该大小就会溢出然后发生Panic(abort)。

      所以最终实现的 RefArc的区别在于:

      1. Ref 是基于内核的 refcount_t 来支持的
      2. 它不支持 弱引用,所以大小减少了一半
      3. 当它超过阈值时,它使得引用计数饱和(saturating)而非中止(abort)
      4. 它不提供 get_mut 方法,所以引用计数对象是 Pin 的。
      结构体结构体定义如下:

      总是一个非零的一个实例,并且被 Ref引用的对象总是 Pin 的(不可移动)。

      该结构体中使用 NonNull<T>,而非 *mut T,这里需要协变(covariant),而非不变(invariant)。可以参考下面示例:

      *mut T的协变版本,并且也代表了非空指针,代表了引用计数对象总是非空的,因为当计数为零就会释放。

      而这里使用 PhatomData 则是为了 Drop 检查,此处表示 Ref 类型拥有 RefInner<T>,当 Ref 被 Drop 的时候,RefInner<T>也能跟着被 Drop 。

      结构体结构体:

      内部包含了内核中 C 语言实现的引用计数结构体 refcount_t,这里就是为了复用 C 代码。

      其中 kernel crate 内置的专门为了和 C 打交道提供的一个包装类型,定义如下:

      结构体定义如下:

      API的目标是为实现对象的引用计数器提供一个最小的API。虽然内部使用了原子操作,但一些 refcount_*()atomic_*() 函数在内存顺序保证方面有很多不同。

      refcount_t 在2018年曾经发生过 引用计数溢出的安全漏洞,即,当引用计数达到最大值时,如果再加一,则引用计数就会归零。所以,此时引用的对象就会被错误释放。这样就变成了一个 UAF(use-after-free) 漏洞,容易被人利用。

      所以现在 refcount_t 被增加了引用计数检测:

      实现的一些 trait拥有一些类似于 Arc<T> 的行为,所以为其实现一些内置 trait。

      :是一个未稳定特性(receiver_trait features),它表示一个结构体可以作为方法接收者,不需要arbitrary_self_types 特性。标准库中一些智能指针实现了该trait,比如 Box<T>/ Arc<T> / Rc<T> / &T / Pin<P> 等。
    2. :也是一个未稳定特性(coerce_unsized features),它表示将 Size 类型转换为 DST 类型。
    3. features),它用于对象安全(动态安全 dyn safe)的检查。实现 DispatchFromDyn 的类型可以安全地用作对象安全方法中的 self 类型。
    4. Send/Sync,是Rust 中稳定的特性,用于标记线程间可安全传递和共享的类型。
    5. 现在为 Ref<T> 实现了这些 trait,那么 Ref<T> 也就拥有了相应的行为。基本上 Ref<T> 的行为和 Arc<T> 类似了,除了上面所说的那些区别。

      是复用内核 C 代码,所以对于引用计数的管理,只需要实现相应的 trait 即可。

      比如,Clone 时应该自增引用计数,而 Drop 时应该自减引用计数。所以,分别来看一下这两个实现。

      trait 很简单,直接通过 bindings::refcount_inc 来调用内核中 refcount_t 的自增方法 refcount_inc即可。

      因为 refcount_inc 已经是有了引用计数溢出检测,使用饱和加法,所以不用担心归零。

      trait,同样直接通过 bindings::refcount_dec_and_test 调用内核 refcount_dec_and_test 函数即可,该函数也包含了引用计数溢出检查。但是在引用计数归零的时候,需要释放内存。

      注意上面 CloneDrop 这两个 trait 的实现,是 Unsafe Rust 抽象为 Safe Rust 的一个经典范例,主要是其中的Safety注释,考虑了安全边界,并且加以说明。

      如何创建新的引用计数对象。

      方法中使用 core::alloc::Layout 结构体来定义内存布局。

      通过 NonNull::new 和 自定义的 core::alloc::alloc 函数 来分配新的内存,并转换为 RefInner<T>> 类型,并通过bindings::REFCOUNT_INIT调用内核 C 函数对其初始化为 1 。其中 自定义的 core::alloc 模块将来都会同步到 rust core 中。

      其中 Error::ENOMEM代表 OOM 错误。在 中定义了很多内核错误码对应的错误。

      Linux 内核中使用整数定义了很多错误码,在 kernel crate 中,使用了 NewType 模式对其进行封装,而非直接使用整数错误码:

      构造 Ref<T>方法中看到,最后一步使用 from_inner 方法将一个裸指针构造为最终的 Ref<T>。并且它是一个内部方法,不是公开的 API。

      注意,它是一个 unsafe 的方法,因为需要调用者来确保 inner 的指针是有效且非空的,对于这一点其文档注释也写的比较清楚。

      结构体使用 PhantomData<&\'a ()> 来持有生命周期参数,并为其实现 Copy trait,其行为和普通的不可变引用类似。

      然后为 Ref<T> 实现一个 as_ref_borrow 方法即可从 Ref<T> 得到 RefBorrow<T>

      改为 as_ref 更好。但是这里其实 as_ref 另有用处:

      方法从 Ref<T> 得到 &T

      然后为 RefBorrow<T> 实现 Deref trait,也可以从 RefBorrow<T> 拿到 &T

      之外,还实现了一个 UniqueRef<T> 类型。顾名思义,该类型表示只有唯一一个引用计数的情况。

      Drop 这两个 trait,所以它只能持有唯一一个引用。引入该类型也许可以为内核开发提供更多便利。

      还实现了其他 trait,比如 From/TryFrom ,可以从裸指针和 Ref<T>之间相互转换。

      一个值得注意的地方是:

      转换为裸指针时,注意使用 core::mem::forget(obj) 避免调用 obj 的 Drop ,否则会让引用计数减少而引起问题。

      类型: https://github.com/Rust-for-Linux/linux/blob/rust/rust/kernel/types.rs#L277

      [10]

      core::ops::Receiver: https://github.com/rust-lang/rust/blob/master/library/core/src/ops/deref.rs#L191

      [11]

      core::ops::CoerceUnsized: https://github.com/rust-lang/rust/blob/master/library/core/src/ops/unsize.rs#L36

      [12]

      core::ops::DispatchFromDyn: https://github.com/rust-lang/rust/blob/master/library/core/src/ops/unsize.rs#L117

      [13]

      kernel/error.rs: https://github.com/Rust-for-Linux/linux/blob/rust/rust/kernel/error.rs#L64


  • 【RUST_BASIC】Rust for Linux环境搭建

    参考技术A

    Rust 提供简单的一键安装,命令如下:

    rustup 是 Rust 官方的版本管理工具,安装前首先配置国内镜像加速更新工具链:

    运行以下命令进行安装:

    安装后工具链会被安装到 $HOME/.cargo/bin 目录,.cargo/bin 目录会被添加到系统的 $PATH 环境变量,重新登录后即可使用 rustc,cargo 等命令。

    使用国内镜像加速更新 crate 拉取,将如下配置写入 $HOME/.cargo/config 文件:

    Rust 有三个 发布通道 (release channel):

    使用 nightly 版本:

    安装 RLS 组件:

    安装 WASM:

    安装 racer:


    https://rustcc.gitbooks.io/rustprimer/content/install/install_rust_on_linux.html

    以上是关于Rust for Linux 源码导读 | Ref 引用计数容器的主要内容,如果未能解决你的问题,请参考以下文章

    【RUST_BASIC】Rust for Linux环境搭建

    Rust 社区 RFC 导读 | 构建安全的 I/O

    FileCoin 挖矿教程 基本源码分析1

    Rust 中的命名中断 for 循环

    Rust GUI编程

    Rust GUI编程