From c63a5c1c05b215114639701cd650ac056f793b96 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Tue, 21 Jul 2020 01:00:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=86=85=E6=A0=B8=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E4=B8=80=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/ch01-01-kernel-object.md | 88 ++++++++++++++-- zcore/src/object/mod.rs | 170 ++++++++++++++++++------------ zcore/src/object/object_v1.rs | 83 +++++++++++++++ 3 files changed, 262 insertions(+), 79 deletions(-) create mode 100644 zcore/src/object/object_v1.rs diff --git a/docs/src/ch01-01-kernel-object.md b/docs/src/ch01-01-kernel-object.md index d25fe94..b8e76f1 100644 --- a/docs/src/ch01-01-kernel-object.md +++ b/docs/src/ch01-01-kernel-object.md @@ -86,7 +86,7 @@ pub trait KernelObject: Send + Sync { 接下来我们实现一个最简单的空对象 `DummyObject`,并为它实现 `KernelObject` 接口: ```rust,noplaypen -{{#include ../../zcore/src/object/mod.rs:dummy_def}} +{{#include ../../zcore/src/object/object_v1.rs:dummy_def}} ``` 这里我们采用一种[**内部可变性**]的设计模式:将对象的所有可变的部分封装到一个内部对象 `DummyObjectInner` 中,并在原对象中用自旋锁 [`Mutex`] 把它包起来,剩下的其它字段都是不可变的。 @@ -109,7 +109,7 @@ pub trait KernelObject: Send + Sync { 然后我们为新对象实现构造函数: ```rust,noplaypen -{{#include ../../zcore/src/object/mod.rs:dummy_new}} +{{#include ../../zcore/src/object/object_v1.rs:dummy_new}} ``` 根据文档描述,每个内核对象都有唯一的 ID。为此我们需要实现一个全局的 ID 分配方法。这里采用的方法是用一个静态变量存放下一个待分配 ID 值,每次分配就原子地 +1。 @@ -120,7 +120,7 @@ ID 类型使用 `u64`,保证了数值空间足够大,在有生之年都不 最后我们为它实现 `KernelObject` 接口: ```rust,noplaypen -{{#include ../../zcore/src/object/mod.rs:dummy_impl}} +{{#include ../../zcore/src/object/object_v1.rs:dummy_impl}} ``` 到此为止,我们已经迈出了万里长征第一步,实现了一个最简单的功能。有实现,就要有测试!即使最简单的代码也要保证它的行为符合我们预期。 @@ -129,7 +129,7 @@ ID 类型使用 `u64`,保证了数值空间足够大,在有生之年都不 为了证明上面代码的正确性,我们写一个简单的单元测试,替换掉自带的 `it_works` 函数: ```rust,noplaypen -{{#include ../../zcore/src/object/mod.rs:dummy_test}} +{{#include ../../zcore/src/object/object_v1.rs:dummy_test}} ``` ```sh @@ -206,7 +206,7 @@ impl_downcast!(sync KernelObject); `impl_downcast!` 宏用来帮我们自动生成转换函数,然后就可以用 `downcast_arc` 来对 `Arc` 做向下转换了。我们直接来测试一把: ```rust,noplaypen -{{#include ../../zcore/src/object/mod.rs:downcast_test}} +{{#include ../../zcore/src/object/object_v1.rs:downcast_test}} ``` ```sh @@ -219,18 +219,86 @@ test object::downcast ... ok test object::tests::dummy_object ... ok ``` -## 用宏自动生成 `impl KernelObject` 模板代码 +## 模拟继承:用宏自动生成接口实现代码 + +上面我们已经完整实现了一个内核对象,代码看起来很简洁。但当我们要实现更多对象的时候,就会发现一个问题: +这些对象拥有一些公共属性,接口方法也有共同的实现。 +在传统 OOP 语言中,我们通常使用 **继承(inheritance)** 来复用这些公共代码:子类 B 可以继承父类 A,然后自动拥有父类的所有字段和方法。 -传统 OOP 语言都支持 **继承(inheritance)** 功能:子类 B 可以继承父类 A,然后自动拥有父类的所有字段和方法。 +继承是一个很强大的功能,但在长期实践中人们也逐渐发现了它的弊端。有兴趣的读者可以看一看知乎上的探讨:[*面向对象编程的弊端是什么?*]。 +经典著作《设计模式》中就鼓励大家**使用组合代替继承**。而一些现代的编程语言,如 Go 和 Rust,甚至直接抛弃了继承。在 Rust 中,通常使用组合结构和 [`Deref`] trait 来部分模拟继承。 + +[*面向对象编程的弊端是什么?*]: https://www.zhihu.com/question/20275578/answer/26577791 +[`Deref`]: https://kaisery.github.io/trpl-zh-cn/ch15-02-deref.html > 继承野蛮,trait 文明。 —— 某 Rust 爱好者 +接下来我们模仿 `downcast-rs` 库的做法,使用一种基于宏的代码生成方案,来实现 `KernelObject` 的继承。 +当然这只是抛砖引玉,如果读者自己实现了,或者了解到社区中有更好的解决方案,也欢迎指出。 + +具体做法是这样的: + +- 使用一个 struct 来提供所有的公共属性和方法,作为所有子类的第一个成员。 +- 为子类实现 trait 接口,所有方法直接委托给内部 struct 完成。这部分使用宏来自动生成模板代码。 + +而所谓的内部 struct,其实就是我们上面实现的 `DummyObject`。为了更好地体现它的功能,我们给他改个名叫 `KObjectBase`: + +```rust,noplaypen +{{#include ../../zcore/src/object/mod.rs:base_def}} +``` + +接下来我们把它的构造函数改为实现 `Default` trait,并且公共属性和方法都指定为 `pub`: + +```rust,noplaypen +{{#include ../../zcore/src/object/mod.rs:base_default}} +impl KObjectBase { + /// 生成一个唯一的 ID + fn new_koid() -> KoID {...} + /// 获取对象名称 + pub fn name(&self) -> String {...} + /// 设置对象名称 + pub fn set_name(&self, name: &str) {...} +} +``` + +最后来写一个魔法的宏! + ```rust,noplaypen -{{#include ../../zcore/src/object/mod.rs:base}} +{{#include ../../zcore/src/object/mod.rs:impl_kobject}} +``` + +轮子已经造好了!让我们看看如何用它方便地实现一个内核对象,仍以 `DummyObject` 为例: + +```rust,noplaypen +{{#include ../../zcore/src/object/mod.rs:dummy}} +``` + +是不是方便了很多?最后按照惯例,用单元测试检验实现的正确性: + +```rust,noplaypen +{{#include ../../zcore/src/object/mod.rs:dummy_test}} +``` + +有兴趣的读者可以继续探索使用功能更强大的 **过程宏(proc_macro)**,进一步简化实现新内核对象所需的模板代码。 +如果能把上面的代码块缩小成下面这两行,就更加完美了: + +```rust,noplaypen +#[KernelObject] +pub struct DummyObject; ``` ## 总结 -关于 Rust 的面向对象特性,可以参考[官方文档]。 +在这一节中我们用 Rust 语言实现了 Zircon 最核心的**内核对象**概念。在此过程中涉及到 Rust 的一系列语言特性和设计模式: + +- 使用 **trait** 实现接口 +- 使用 **内部可变性** 模式实现并发对象 +- 基于社区解决方案实现 trait 到 struct 的 **向下转换** +- 使用组合模拟继承,并使用 **宏** 实现模板代码的自动生成 + +由于 Rust 独特的[面向对象编程特性],我们在实现内核对象的过程中遇到了一定的挑战。 +不过万事开头难,解决这些问题为整个项目打下了坚实基础,后面实现新的内核对象就会变得简单很多。 + +[面向对象编程特性]: https://kaisery.github.io/trpl-zh-cn/ch17-00-oop.html -[官方文档]: https://kaisery.github.io/trpl-zh-cn/ch17-00-oop.html +在下一节中,我们将介绍内核对象相关的另外两个概念:句柄和权限,并实现内核对象的存储和访问。 diff --git a/zcore/src/object/mod.rs b/zcore/src/object/mod.rs index 2ba9be4..1c114c2 100644 --- a/zcore/src/object/mod.rs +++ b/zcore/src/object/mod.rs @@ -1,6 +1,11 @@ use alloc::string::String; +use alloc::sync::Arc; use core::fmt::Debug; +use core::sync::atomic::*; use downcast_rs::{impl_downcast, DowncastSync}; +use spin::Mutex; + +mod object_v1; /// 内核对象公共接口 pub trait KernelObject: DowncastSync + Debug { @@ -23,101 +28,128 @@ impl_downcast!(sync KernelObject); pub type KoID = u64; // ANCHOR_END: koid -// ANCHOR: dummy_def -use spin::Mutex; - -/// 空对象 -#[derive(Debug)] -pub struct DummyObject { - id: KoID, - inner: Mutex, +// ANCHOR: base_def +/// 内核对象核心结构 +pub struct KObjectBase { + /// 对象 ID + pub id: KoID, + inner: Mutex, } -/// `DummyObject` 的内部可变部分 -#[derive(Default, Debug)] -struct DummyObjectInner { +/// `KObjectBase` 的内部可变部分 +#[derive(Default)] +struct KObjectBaseInner { name: String, } -// ANCHOR_END: dummy_def +// ANCHOR_END: base_def -// ANCHOR: dummy_new -use alloc::sync::Arc; -use core::sync::atomic::*; - -impl DummyObject { - /// 创建一个新 `DummyObject` - pub fn new() -> Arc { - Arc::new(DummyObject { +// ANCHOR: base_default +impl Default for KObjectBase { + /// 创建一个新 `KObjectBase` + fn default() -> Self { + KObjectBase { id: Self::new_koid(), inner: Default::default(), - }) + } } +} +// ANCHOR_END: base_default +impl KObjectBase { /// 生成一个唯一的 ID fn new_koid() -> KoID { static NEXT_KOID: AtomicU64 = AtomicU64::new(1024); NEXT_KOID.fetch_add(1, Ordering::SeqCst) } -} -// ANCHOR_END: dummy_new - -// ANCHOR: dummy_impl -impl KernelObject for DummyObject { - fn id(&self) -> KoID { - self.id - } - fn type_name(&self) -> &str { - "DummyObject" - } - fn name(&self) -> String { + /// 获取对象名称 + pub fn name(&self) -> String { self.inner.lock().name.clone() } - fn set_name(&self, name: &str) { + /// 设置对象名称 + pub fn set_name(&self, name: &str) { self.inner.lock().name = String::from(name); } } -// ANCHOR_END: dummy_impl -// ANCHOR: dummy_test -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn dummy_object() { - let o1 = DummyObject::new(); - let o2 = DummyObject::new(); - assert_ne!(o1.id(), o2.id()); - assert_eq!(o1.type_name(), "DummyObject"); - assert_eq!(o1.name(), ""); - o1.set_name("object1"); - assert_eq!(o1.name(), "object1"); +// ANCHOR: impl_kobject +/// 为内核对象 struct 自动实现 `KernelObject` trait 的宏。 +#[macro_export] // 导出宏,可在 crate 外部使用 +macro_rules! impl_kobject { + // 匹配类型名,并可以提供函数覆盖默认实现 + ($class:ident $( $fn:tt )*) => { + // 为对象实现 KernelObject trait,方法直接转发到内部 struct + impl KernelObject for $class { + fn id(&self) -> KoID { + // 直接访问内部的 pub 属性 + self.base.id + } + fn type_name(&self) -> &str { + // 用 stringify! 宏将输入转成字符串 + stringify!($class) + } + // 注意宏里面的类型要写完整路径,例如:alloc::string::String + fn name(&self) -> alloc::string::String { + self.base.name() + } + fn set_name(&self, name: &str){ + // 直接访问内部的 pub 方法 + self.base.set_name(name) + } + // 可以传入任意数量的函数,覆盖 trait 的默认实现 + $( $fn )* + } + // 为对象实现 Debug trait + impl core::fmt::Debug for $class { + fn fmt( + &self, + f: &mut core::fmt::Formatter<'_>, + ) -> core::result::Result<(), core::fmt::Error> { + // 输出对象类型、ID 和名称 + f.debug_tuple(&stringify!($class)) + .field(&self.id()) + .field(&self.name()) + .finish() + } + } + }; +} +// ANCHOR_END: impl_kobject + +// ANCHOR: dummy +/// 空对象 +pub struct DummyObject { + // 其中必须包含一个名为 `base` 的 `KObjectBase` + base: KObjectBase, +} + +// 使用刚才的宏,声明其为内核对象,自动生成必要的代码 +impl_kobject!(DummyObject); + +impl DummyObject { + /// 创建一个新 `DummyObject` + pub fn new() -> Arc { + Arc::new(DummyObject { + base: KObjectBase::default(), + }) } } -// ANCHOR_END: dummy_test +// ANCHOR_END: dummy #[cfg(test)] -// ANCHOR: downcast_test +// ANCHOR: dummy_test #[test] -fn downcast() { +fn impl_kobject() { + use alloc::format; let dummy = DummyObject::new(); let object: Arc = dummy; + assert_eq!(object.type_name(), "DummyObject"); + assert_eq!(object.name(), ""); + object.set_name("dummy"); + assert_eq!(object.name(), "dummy"); + assert_eq!( + format!("{:?}", object), + format!("DummyObject({}, \"dummy\")", object.id()) + ); let _result: Arc = object.downcast_arc::().unwrap(); } -// ANCHOR_END: downcast_test - -// ANCHOR: base -/// 内核对象核心结构 -pub struct KObjectBase { - /// 对象 ID - pub id: KoID, - inner: Mutex, -} - -/// `KObjectBase` 的内部可变部分 -#[derive(Default)] -struct KObjectBaseInner { - name: String, -} -// ANCHOR_END: base - -// impl_downcast!(sync KernelObject); +// ANCHOR_END: dummy_test diff --git a/zcore/src/object/object_v1.rs b/zcore/src/object/object_v1.rs new file mode 100644 index 0000000..6b9c0ed --- /dev/null +++ b/zcore/src/object/object_v1.rs @@ -0,0 +1,83 @@ +use super::*; + +// ANCHOR: dummy_def +use spin::Mutex; + +/// 空对象 +#[derive(Debug)] +pub struct DummyObject { + id: KoID, + inner: Mutex, +} + +/// `DummyObject` 的内部可变部分 +#[derive(Default, Debug)] +struct DummyObjectInner { + name: String, +} +// ANCHOR_END: dummy_def + +// ANCHOR: dummy_new +use alloc::sync::Arc; +use core::sync::atomic::*; + +impl DummyObject { + /// 创建一个新 `DummyObject` + pub fn new() -> Arc { + Arc::new(DummyObject { + id: Self::new_koid(), + inner: Default::default(), + }) + } + + /// 生成一个唯一的 ID + fn new_koid() -> KoID { + static NEXT_KOID: AtomicU64 = AtomicU64::new(1024); + NEXT_KOID.fetch_add(1, Ordering::SeqCst) + } +} +// ANCHOR_END: dummy_new + +// ANCHOR: dummy_impl +impl KernelObject for DummyObject { + fn id(&self) -> KoID { + self.id + } + fn type_name(&self) -> &str { + "DummyObject" + } + fn name(&self) -> String { + self.inner.lock().name.clone() + } + fn set_name(&self, name: &str) { + self.inner.lock().name = String::from(name); + } +} +// ANCHOR_END: dummy_impl + +// ANCHOR: dummy_test +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn dummy_object() { + let o1 = DummyObject::new(); + let o2 = DummyObject::new(); + assert_ne!(o1.id(), o2.id()); + assert_eq!(o1.type_name(), "DummyObject"); + assert_eq!(o1.name(), ""); + o1.set_name("object1"); + assert_eq!(o1.name(), "object1"); + } +} +// ANCHOR_END: dummy_test + +#[cfg(test)] +// ANCHOR: downcast_test +#[test] +fn downcast() { + let dummy = DummyObject::new(); + let object: Arc = dummy; + let _result: Arc = object.downcast_arc::().unwrap(); +} +// ANCHOR_END: downcast_test