0
点赞
收藏
分享

微信扫一扫

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

alonwang 2022-02-11 阅读 64

引子
2022 年,我们很可能会看到 Linux 内核中的实验性 Rust 编程语言支持成为主流。2021.12.6 早上发出了更新的补丁,介绍了在内核中处理 Rust 的初始支持和基础设施。

这次更新的内容包括:

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

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

v2 补丁:https://lore.kernel.org/lkml/20211206140313.5653-1-ojeda@kernel.org/
www.phoronix.com/scan.php?pa…
kernel crate 文档
为什么需要引入 Ref 来代替 Arc
Rust for Linux 中这个 kernel crate 中之前使用的是 Arc ,但是现在换成了 Ref。 通过查看相关PR rust: update Ref to use the kernel’s refcount_t ,可以了解其中主要有两点原因:

最大化利用现有的 C 代码 和 消除恐慌(Panic)。内核中已经有了引用计数的实现 refcount_t,而且它超过引用计数的阈值时,不是 Panic(abort) 而是返回最大值(饱和加法)。因为这个原因,也使用 RBTree(红黑树) 替代了 BTreeMap。
不需要弱引用。
Arc 有一个 MAX_REFCOUNT 的限制,是 isize::MAX as usize 大小,引用计数加法超过该大小就会溢出然后发生Panic(abort)。

所以最终实现的 Ref 与Arc的区别在于:

Ref 是基于内核的 refcount_t 来支持的
它不支持 弱引用,所以大小减少了一半
当它超过阈值时,它使得引用计数饱和(saturating)而非中止(abort)
它不提供 get_mut 方法,所以引用计数对象是 Pin 的。
Ref源码分析
接下来分析一下Ref的实现。

Ref 结构体
Ref 结构体定义如下:

/// A reference-counted pointer to an instance of `T`.
///
/// The reference count is incremented when new instances of [`Ref`] are created, and decremented
/// when they are dropped. When the count reaches zero, the underlying `T` is also dropped.
///
/// # Invariants
///
/// The reference count on an instance of [`Ref`] is always non-zero.
/// The object pointed to by [`Ref`] is always pinned.
pub struct Ref<T: ?Sized> {
    ptr: NonNull<RefInner<T>>,
    _p: PhantomData<RefInner<T>>,
}
复制代码

它维护一个不变量(Invariants):引用计数 Ref 总是一个非零的一个实例,并且被 Ref引用的对象总是 Pin 的(不可移动)。

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

use std::ptr::NonNull;

struct Ref<T: ?Sized> {
    x: NonNull<T>,
    // x: *mut T, // 如果换成 *mut T,编译将不会通过
}

fn take<'a>(r: Ref<&'a u32>, y: &'a u32) {}

fn give() -> Ref<&'static u32> { todo!() }

fn test() {
    let y = 1;
    // 协变,能传入 Ref<&'a u32> 的函数take,也能接收 Ref<&'static u32> 类型的参数,因为 'static: 'a ,能接受子类型,也能接受父类型
    take(give(), &y); 
}
复制代码

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

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

RefInner 结构体
再来看 RefInner 结构体:

#[repr(C)]
struct RefInner<T: ?Sized> {
    refcount: Opaque<bindings::refcount_t>,
    data: T,
}
复制代码

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

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

pub struct Opaque<T>(MaybeUninit<UnsafeCell<T>>);

impl<T> Opaque<T> {
    /// Creates a new opaque value.
    pub fn new(value: T) -> Self {
        Self(MaybeUninit::new(UnsafeCell::new(value)))
    }

    /// Creates an uninitialised value.
    pub fn uninit() -> Self {
        Self(MaybeUninit::uninit())
    }

    /// Returns a raw pointer to the opaque data.
    pub fn get(&self) -> *mut T {
        UnsafeCell::raw_get(self.0.as_ptr())
    }
}
复制代码

Opaque 类型意味着永远都不需要 Rust 代码来解释的 FFi 对象。所以,为了使用内核中已经存在的引用计数结构体,这里用 Opaquebindings::refcount_t类型。

关于 refcount_t
Linux 内核中定义的 refcount_t 结构体定义如下:

// from: https://github.com/torvalds/linux/blob/master/tools/include/linux/refcount.h
typedef struct refcount_struct {
	atomic_t refs;
} refcount_t;
复制代码

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

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

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

// from: https://github.com/torvalds/linux/blob/master/tools/include/linux/refcount.h#L69

static inline __refcount_check
bool refcount_inc_not_zero(refcount_t *r)
{
	unsigned int old, new, val = atomic_read(&r->refs);

	for (;;) {
		new = val + 1;

		if (!val)
			return false;

		if (unlikely(!new))
			return true;

		old = atomic_cmpxchg_relaxed(&r->refs, val, new);
		if (old == val)
			break;

		val = old;
	}

	REFCOUNT_WARN(new == UINT_MAX, "refcount_t: saturated; leaking memory.\n");

	return true;
}
复制代码

在达到引用计数最大时采用饱和(saturated)加法,即,返回最大的值,而非零。注意,这里使用了compare-and-swap原子操作且并未提供内存顺序(使用relaxed)。

为 Ref 实现的一些 trait
为了让 Ref 拥有一些类似于 Arc 的行为,所以为其实现一些内置 trait。

// This is to allow [`Ref`] (and variants) to be used as the type of `self`.
impl<T: ?Sized> core::ops::Receiver for Ref<T> {}

// This is to allow [`RefBorrow`] (and variants) to be used as the type of `self`.
impl<T: ?Sized> core::ops::Receiver for RefBorrow<'_, T> {}

// This is to allow coercion from `Ref<T>` to `Ref<U>` if `T` can be converted to the
// dynamically-sized type (DST) `U`.
impl<T: ?Sized + Unsize<U>, U: ?Sized> core::ops::CoerceUnsized<Ref<U>> for Ref<T> {}

// This is to allow `Ref<U>` to be dispatched on when `Ref<T>` can be coerced into `Ref<U>`.
impl<T: ?Sized + Unsize<U>, U: ?Sized> core::ops::DispatchFromDyn<Ref<U>> for Ref<T> {}

// SAFETY: It is safe to send `Ref<T>` to another thread when the underlying `T` is `Sync` because
// it effectively means sharing `&T` (which is safe because `T` is `Sync`); additionally, it needs
// `T` to be `Send` because any thread that has a `Ref<T>` may ultimately access `T` directly, for
// example, when the reference count reaches zero and `T` is dropped.
unsafe impl<T: ?Sized + Sync + Send> Send for Ref<T> {}

// SAFETY: It is safe to send `&Ref<T>` to another thread when the underlying `T` is `Sync` for
// the same reason as above. `T` needs to be `Send` as well because a thread can clone a `&Ref<T>`
// into a `Ref<T>`, which may lead to `T` being accessed by the same reasoning as above.
unsafe impl<T: ?Sized + Sync + Send> Sync for Ref<T> {}
复制代码

从上面代码里看得出来,用到的 trait 有:

core::ops::Receiver : 是一个未稳定特性(receiver_trait features),它表示一个结构体可以作为方法接收者,不需要arbitrary_self_types 特性。标准库中一些智能指针实现了该trait,比如 Box/ Arc / Rc / &T / Pin

等。
core::ops::CoerceUnsized :也是一个未稳定特性(coerce_unsized features),它表示将 Size 类型转换为 DST 类型。
core::ops::DispatchFromDyn: 同样是一个未稳定的特性(dispatch_from_dyn features),它用于对象安全(动态安全 dyn safe)的检查。实现 DispatchFromDyn 的类型可以安全地用作对象安全方法中的 self 类型。
Send/Sync,是Rust 中稳定的特性,用于标记线程间可安全传递和共享的类型。
现在为 Ref 实现了这些 trait,那么 Ref 也就拥有了相应的行为。基本上 Ref 的行为和 Arc 类似了,除了上面所说的那些区别。

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

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

// 实现 Clone trait
impl<T: ?Sized> Clone for Ref<T> {
    fn clone(&self) -> Self {
        // INVARIANT: C `refcount_inc` saturates the refcount, so it cannot overflow to zero.
        // SAFETY: By the type invariant, there is necessarily a reference to the object, so it is
        // safe to increment the refcount.
        unsafe { bindings::refcount_inc(self.ptr.as_ref().refcount.get()) };

        // SAFETY: We just incremented the refcount. This increment is now owned by the new `Ref`.
        unsafe { Self::from_inner(self.ptr) }
    }
}
复制代码

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

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

// 实现 Drop trait
impl<T: ?Sized> Drop for Ref<T> {
    fn drop(&mut self) {
        // SAFETY: By the type invariant, there is necessarily a reference to the object. We cannot
        // touch `refcount` after it's decremented to a non-zero value because another thread/CPU
        // may concurrently decrement it to zero and free it. It is ok to have a raw pointer to
        // freed/invalid memory as long as it is never dereferenced.
        let refcount = unsafe { self.ptr.as_ref() }.refcount.get();

        // INVARIANT: If the refcount reaches zero, there are no other instances of `Ref`, and
        // this instance is being dropped, so the broken invariant is not observable.
        // SAFETY: Also by the type invariant, we are allowed to decrement the refcount.
        let is_zero = unsafe { bindings::refcount_dec_and_test(refcount) };
        if is_zero {
            // The count reached zero, we must free the memory.

            // SAFETY: This thread holds the only remaining reference to `self`, so it is safe to
            // get a mutable reference to it.
            let inner = unsafe { self.ptr.as_mut() };
            let layout = Layout::for_value(inner);
            // SAFETY: The value stored in inner is valid.
            unsafe { core::ptr::drop_in_place(inner) };
            // SAFETY: The pointer was initialised from the result of a call to `alloc`.
            unsafe { dealloc(self.ptr.cast().as_ptr(), layout) };
        }
    }
}
复制代码

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

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

创建新的引用计数对象
接下来需要关注 Ref 如何创建新的引用计数对象。

impl<T> Ref<T> {
    /// Constructs a new reference counted instance of `T`.
    pub fn try_new(contents: T) -> Result<Self> {
        let layout = Layout::new::<RefInner<T>>();
        // SAFETY: The layout size is guaranteed to be non-zero because `RefInner` contains the
        // reference count.
        let inner = NonNull::new(unsafe { alloc(layout) })
            .ok_or(Error::ENOMEM)?
            .cast::<RefInner<T>>();

        // INVARIANT: The refcount is initialised to a non-zero value.
        let value = RefInner {
            // SAFETY: Just an FFI call that returns a `refcount_t` initialised to 1.
            refcount: Opaque::new(unsafe { bindings::REFCOUNT_INIT(1) }),
            data: contents,
        };
        // SAFETY: `inner` is writable and properly aligned.
        unsafe { inner.as_ptr().write(value) };

        // SAFETY: We just created `inner` with a reference count of 1, which is owned by the new
        // `Ref` object.
        Ok(unsafe { Self::from_inner(inner) })
    }
}

复制代码

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

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

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

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

macro_rules! declare_err {
    ($err:tt) => {
        pub const $err: Self = Error(-(bindings::$err as i32));
    };
    ($err:tt, $($doc:expr),+) => {
        $(
        #[doc = $doc]
        )*
        pub const $err: Self = Error(-(bindings::$err as i32));
    };
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Error(c_types::c_int);

impl Error {
    declare_err!(EPERM, "Operation not permitted.");

    declare_err!(ENOENT, "No such file or directory.");

    declare_err!(ESRCH, "No such process.");

    declare_err!(ENOMEM, "Out of memory.");

    // ...

}
复制代码

从已经存在的 RefInner 构造 Ref
在上面的 try_new 方法中看到,最后一步使用 from_inner 方法将一个裸指针构造为最终的 Ref。并且它是一个内部方法,不是公开的 API。

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

impl<T: ?Sized> Ref<T> {
    /// Constructs a new [`Ref`] from an existing [`RefInner`].
    ///
    /// # Safety
    ///
    /// The caller must ensure that `inner` points to a valid location and has a non-zero reference
    /// count, one of which will be owned by the new [`Ref`] instance.
    unsafe fn from_inner(inner: NonNull<RefInner<T>>) -> Self {
        // INVARIANT: By the safety requirements, the invariants hold.
        Ref {
            ptr: inner,
            _p: PhantomData,
        }
    }

}
复制代码

RefBorrow
不存在对底层引用计数结构体的可变借用,但是存在一个不可变的借用,并且需要手动维护生命周期。

/// A borrowed [`Ref`] with manually-managed lifetime.
///
/// # Invariants
///
/// There are no mutable references to the underlying [`Ref`], and it remains valid for the lifetime
/// of the [`RefBorrow`] instance.
pub struct RefBorrow<'a, T: ?Sized + 'a> {
    inner: NonNull<RefInner<T>>,
    _p: PhantomData<&'a ()>,
}

impl<T: ?Sized> Clone for RefBorrow<'_, T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T: ?Sized> Copy for RefBorrow<'_, T> {}
复制代码

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

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

impl<T> Ref<T> {

    /// Returns a [`RefBorrow`] from the given [`Ref`].
    ///
    /// This is useful when the argument of a function call is a [`RefBorrow`] (e.g., in a method
    /// receiver), but we have a [`Ref`] instead. Getting a [`RefBorrow`] is free when optimised.
    #[inline]
    pub fn as_ref_borrow(&self) -> RefBorrow<'_, T> {
        // SAFETY: The constraint that lifetime of the shared reference must outlive that of
        // the returned `RefBorrow` ensures that the object remains alive.
        unsafe { RefBorrow::new(self.ptr) }
    }

}

复制代码

其实按 Rust 命名规范,此处 as_ref_borrow 改为 as_ref 更好。但是这里其实 as_ref 另有用处:

impl<T: ?Sized> AsRef<T> for Ref<T> {
    fn as_ref(&self) -> &T {
        // SAFETY: By the type invariant, there is necessarily a reference to the object, so it is
        // safe to dereference it.
        unsafe { &self.ptr.as_ref().data }
    }
}
复制代码

要通过 as_ref 方法从 Ref 得到 &T。

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

impl<T: ?Sized> Deref for RefBorrow<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        // SAFETY: By the type invariant, the underlying object is still alive with no mutable
        // references to it, so it is safe to create a shared reference.
        unsafe { &self.inner.as_ref().data }
    }
}
复制代码

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

pub struct UniqueRef<T: ?Sized> {
    inner: Ref<T>,
}

impl<T> UniqueRef<T> {
    /// Tries to allocate a new [`UniqueRef`] instance.
    pub fn try_new(value: T) -> Result<Self> {
        Ok(Self {
            // INVARIANT: The newly-created object has a ref-count of 1.
            inner: Ref::try_new(value)?,
        })
    }

    /// Tries to allocate a new [`UniqueRef`] instance whose contents are not initialised yet.
    pub fn try_new_uninit() -> Result<UniqueRef<MaybeUninit<T>>> {
        Ok(UniqueRef::<MaybeUninit<T>> {
            // INVARIANT: The newly-created object has a ref-count of 1.
            inner: Ref::try_new(MaybeUninit::uninit())?,
        })
    }
}
复制代码

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

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

一个值得注意的地方是:

impl<T> Ref<T> {
    /// Deconstructs a [`Ref`] object into a raw pointer.
    ///
    /// It can be reconstructed once via [`Ref::from_raw`].
    pub fn into_raw(obj: Self) -> *const T {
        let ret = &*obj as *const T;
        core::mem::forget(obj);
        ret
    }
}
复制代码

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

小结
从 Rust for Linux 源码中可以学习很多 Unsafe Rust 的相关技巧,尤其是和 C 语言打交道的一些比较好的实践。如果你感兴趣,还能学习 Linux 内核相关的一些内容,为将来是要 Rust 编写 Linux 内核驱动做一些准备。

最后
如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163相互学习,我们会有专业的技术答疑解惑

如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star:http://github.crmeb.net/u/defu不胜感激 !

PHP学习手册:https://doc.crmeb.com
技术交流论坛:https://q.crmeb.com

举报

相关推荐

0 条评论