From 14f836fc778f2c55a3440084a1980a103d3e63db Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 20 Jul 2020 14:43:50 +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=E6=8E=A5=E5=8F=A3=E5=92=8C=E7=A9=BA=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/ch01-01-kernel-object.md | 106 ++++++++++++++++++++++++++-- zcore/Cargo.toml | 6 ++ zcore/src/lib.rs | 10 +-- zcore/src/object/mod.rs | 111 ++++++++++++++++++++++++++++++ 4 files changed, 222 insertions(+), 11 deletions(-) create mode 100644 zcore/src/object/mod.rs diff --git a/docs/src/ch01-01-kernel-object.md b/docs/src/ch01-01-kernel-object.md index 346e044..1dcee0d 100644 --- a/docs/src/ch01-01-kernel-object.md +++ b/docs/src/ch01-01-kernel-object.md @@ -40,17 +40,17 @@ $ cd zcore ``` 这个程序库目前是在你的 Linux 或 macOS 上运行,但有朝一日它会成为一个真正的 OS 在裸机上运行。 -为此我们需要移除对标准库的依赖,使其成为一个不依赖当前 OS 功能的库。在 `lib.rs` 的第一行添加一个声明: +为此我们需要移除对标准库的依赖,使其成为一个不依赖当前 OS 功能的库。在 `lib.rs` 的第一行添加声明: -```rust,no_run,noplaypen +```rust,noplaypen #![no_std] +extern crate alloc; ``` 现在我们可以尝试运行一下自带的单元测试,编译器可能会自动下载并安装工具链: ```sh $ cargo test -... Finished test [unoptimized + debuginfo] target(s) in 0.52s Running target/debug/deps/zcore-dc6d43637bc5df7a @@ -60,7 +60,105 @@ test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` -## 实现 KernelObject 接口 +## 内核对象 KernelObject + +### 实现 KernelObject 接口 + +所有的内核对象有一系列共同的属性和方法,我们称这些方法为对象的公共**接口(Interface)**。 +同一种方法在不同类型的对象中可能会有不同的行为,在面向对象语言中我们称其为**多态(Polymorphism)**。 + +Rust 是一门部分面向对象的语言,我们通常用它的 trait 实现接口和多态。 + +首先创建一个 `KernelObject` trait 作为内核对象的公共接口: + +```rust +{{#include ../../zcore/src/object/mod.rs:object}} + +{{#include ../../zcore/src/object/mod.rs:koid}} +``` + +这里的 [`Send + Sync`] 是一个约束所有 `KernelObject` 都要满足的前提条件,即它必须是一个**并发对象**。 +所谓并发对象指的是**可以安全地被多线程共享访问**。事实上我们的内核本身就是一个共享地址空间的多线程程序,在裸机上每个 CPU 核都可以被视为一个并发执行的线程。 +由于内核对象可能被多个线程同时访问,因此它必须是并发对象。 + +[`Send + Sync`]: https://kaisery.github.io/trpl-zh-cn/ch16-04-extensible-concurrency-sync-and-send.html + +### 实现一个空对象 + +接下来我们实现一个最简单的空对象 `DummyObject`,并为它实现 `KernelObject` 接口: + +```rust,noplaypen +{{#include ../../zcore/src/object/mod.rs:dummy_def}} +``` + +这里我们采用一种[**内部可变性**]的设计模式:将对象的所有可变的部分封装到一个内部对象 `DummyObjectInner` 中,并在原对象中用自旋锁 [`Mutex`] 把它包起来,剩下的其它字段都是不可变的。 +`Mutex` 会用最简单的方式帮我们处理好并发访问问题:如果有其他人正在访问,我就在这里死等。 +数据被 `Mutex` 包起来之后需要首先使用 [`lock()`] 拿到锁之后才能访问。此时并发访问已经安全,因此被包起来的结构自动具有了 `Send + Sync` 特性。 + +[`Mutex`]: https://docs.rs/spin/0.5.2/spin/struct.Mutex.html +[`lock()`]: https://docs.rs/spin/0.5.2/spin/struct.Mutex.html#method.lock +[**内部可变性**]: https://kaisery.github.io/trpl-zh-cn/ch15-05-interior-mutability.html + +使用自旋锁引入了新的依赖库 [`spin`] ,需要在 `Cargo.toml` 中加入以下声明: + +[`spin`]: https://docs.rs/spin/0.5.2/spin/index.html + +```toml +[dependencies] +{{#include ../../zcore/Cargo.toml:spin}} +``` + +然后我们为新对象实现构造函数: + +```rust,noplaypen +{{#include ../../zcore/src/object/mod.rs:dummy_new}} +``` + +根据文档描述,每个内核对象都有唯一的 ID。为此我们需要实现一个全局的 ID 分配方法。这里采用的方法是用一个静态变量存放下一个待分配 ID 值,每次分配就原子地 +1。 +ID 类型使用 `u64`,保证了数值空间足够大,在有生之年都不用担心溢出问题。在 Zircon 中 ID 从 1024 开始分配,1024 以下保留作内核内部使用。 + +另外注意这里 `new` 函数返回类型不是 `Self` 而是 `Arc`,这是为了以后方便而做的统一约定。 + +最后我们为它实现 `KernelObject` 接口: + +```rust,noplaypen +{{#include ../../zcore/src/object/mod.rs:dummy_impl}} +``` + +到此为止,我们已经迈出了万里长征第一步,实现了一个最简单的功能。有实现,就要有测试!即使最简单的代码也要保证它的行为符合我们预期。 +只有对现有代码进行充分测试,在未来做添加和修改的时候,我们才有信心不会把事情搞砸。俗话讲"万丈高楼平地起",把地基打好才能盖摩天大楼。 + +为了证明上面代码的正确性,我们写一个简单的单元测试,替换掉自带的 `it_works` 函数: + +```rust,noplaypen +{{#include ../../zcore/src/object/mod.rs:dummy_test}} +``` + +```sh +$ cargo test + Finished test [unoptimized + debuginfo] target(s) in 0.53s + Running target/debug/deps/zcore-ae1be84852989b13 + +running 1 test +test tests::dummy_object ... ok +``` + +大功告成!让我们用 `cargo fmt` 命令格式化一下代码,然后记得 `git commit` 及时保存进展。 + +### 实现接口到具体类型的向下转换 + +```toml +[dependencies] +{{#include ../../zcore/Cargo.toml:downcast}} +``` + +```rust,noplaypen +{{#include ../../zcore/src/object/mod.rs:base}} +``` + +关于 Rust 的面向对象特性,可以参考[官方文档]。 + +[官方文档]: https://kaisery.github.io/trpl-zh-cn/ch17-00-oop.html ## 句柄 Handle diff --git a/zcore/Cargo.toml b/zcore/Cargo.toml index e781a6b..c833992 100644 --- a/zcore/Cargo.toml +++ b/zcore/Cargo.toml @@ -7,3 +7,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +# ANCHOR: spin +spin = "0.5" +# ANCHOR_END: spin +# ANCHOR: downcast +downcast-rs = { version = "1.2.0", default-features = false } +# ANCHOR_END: downcast diff --git a/zcore/src/lib.rs b/zcore/src/lib.rs index ecc21c6..c2ee9b5 100644 --- a/zcore/src/lib.rs +++ b/zcore/src/lib.rs @@ -1,9 +1,5 @@ #![no_std] -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} +extern crate alloc; + +mod object; diff --git a/zcore/src/object/mod.rs b/zcore/src/object/mod.rs new file mode 100644 index 0000000..afe9699 --- /dev/null +++ b/zcore/src/object/mod.rs @@ -0,0 +1,111 @@ +use alloc::string::String; +// use core::fmt::Debug; +// use downcast_rs::{impl_downcast, DowncastSync}; + +// ANCHOR: object +/// 内核对象公共接口 +pub trait KernelObject: Send + Sync { + /// 获取对象 ID + fn id(&self) -> KoID; + /// 获取对象类型名 + fn type_name(&self) -> &str; + /// 获取对象名称 + fn name(&self) -> String; + /// 设置对象名称 + fn set_name(&self, name: &str); +} +// ANCHOR_END: object + +// ANCHOR: koid +/// 对象 ID 类型 +pub type KoID = u64; +// ANCHOR_END: koid + +// ANCHOR: dummy_def +use spin::Mutex; + +/// 空对象 +pub struct DummyObject { + id: KoID, + inner: Mutex, +} + +/// `DummyObject` 的内部可变部分 +#[derive(Default)] +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_eq!(o1.id(), 1024); + assert_eq!(o2.id(), 1025); + assert_eq!(o1.type_name(), "DummyObject"); + assert_eq!(o1.name(), ""); + o1.set_name("object1"); + assert_eq!(o1.name(), "object1"); + } +} +// ANCHOR_END: dummy_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);