From 7d3040214bcb4fc799fd197d11288e4de13308f8 Mon Sep 17 00:00:00 2001 From: andream7 <2236407598@qq.com> Date: Wed, 18 Aug 2021 23:20:48 +0800 Subject: [PATCH 1/4] overwrite docs: ch01-02 --- docs/src/ch01-02-process-object.md | 314 ++++++++++++++++++++++++----- 1 file changed, 259 insertions(+), 55 deletions(-) diff --git a/docs/src/ch01-02-process-object.md b/docs/src/ch01-02-process-object.md index ecb805a..9f7f86f 100644 --- a/docs/src/ch01-02-process-object.md +++ b/docs/src/ch01-02-process-object.md @@ -1,108 +1,312 @@ #### 对象管理器:Process 对象 -## 权限 +## 句柄——操作内核对象的桥梁 -[权限]: https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/rights.md +在1.1中我们用Rust语言实现了一个最核心的内核对象,在本小节我们将逐步了解与内核对象相关的三个重要概念中的其他两个:**句柄(Handle)和权限(Rights)**。 -内核对象的“[权限](https://fuchsia.dev/docs/concepts/kernel/rights)”指定允许对内核对象进行哪些操作。权限与句柄相关联,并传达对关联句柄或与句柄关联的对象执行操作的特权。单个进程可能对具有不同权限的同一个内核对象有两个不同的句柄。 +句柄是允许用户程序引用内核对象引用的一种内核结构,它可以被认为是与特定内核对象的会话或连接。 -## 句柄 +通常情况下,多个进程通过不同的句柄同时访问同一个对象。对象可能有多个句柄(在一个或多个进程中)引用它们。但单个句柄只能绑定到单个进程或绑定到内核。 -[句柄]: https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/handles.md +### 定义句柄 +在 object 模块下定义一个子模块: -句柄是允许用户程序引用内核对象引用的一种内核结构,它可以被认为是与特定内核对象的会话或连接。 +```rust +// src/object/mod.rs +mod handle; -通常情况下,多个进程通过不同的句柄同时访问同一个对象。对象可能有多个句柄(在一个或多个进程中)引用它们。但单个句柄只能绑定到单个进程或绑定到内核。 +pub use self::handle::*; +``` + +定义句柄: -当句柄绑定到内核时,我们说它是“在传输中”('in-transit')。 +```rust +// src/object/handle.rs +use super::{KernelObject, Rights}; +use alloc::sync::Arc; + +/// 内核对象句柄 +#[derive(Clone)] +pub struct Handle { + pub object: Arc, + pub rights: Rights, +} +``` -在用户模式下,句柄只是某个系统调用返回的特定数字。只有“不在传输中”的句柄对用户模式可见。 +一个Handle包含object和right两个字段,object是实现了`KernelObject`Trait的内核对象,Rights是该句柄的权限,我们将在下面提到它。 -代表句柄的整数只对其所属的那个进程有意义。另一个进程中的相同数字可能不会映射到任何句柄,或者它可能映射到指向完全不同的内核对象的句柄。 +Arc是一个可以在多线程上使用的引用计数类型,这个计数会随着 `Arc` 的创建或复制而增加,并当 `Arc` 生命周期结束被回收时减少。当这个计数变为零之后,这个计数变量本身以及被引用的变量都会从堆上被回收。 -句柄的整数值是任何 32 位数字,但对应于**ZX_HANDLE_INVALID**的值将始终为 0。除此之外,有效句柄的整数值将始终具有句柄集的两个最低有效位. 可以使用**ZX_HANDLE_FIXED_BITS_MASK**访问代表这些位的掩码。 +我们为什么要在这里使用Arc智能指针呢? -句柄可以从一个进程移动到另一个进程,方法是将它们写入通道(使用[`channel_write()`](https://fuchsia.dev/docs/reference/syscalls/channel_write)),或者使用 [`process_start()`](https://fuchsia.dev/docs/reference/syscalls/process_start)传递一个句柄作为新进程中第一个线程的参数。对于几乎所有的对象,当最后一个打开的引用对象的句柄关闭时,对象要么被销毁,要么被置于可能无法撤消的最终状态。 +绝大多数内核对象的析构都发生在句柄数量为 0 时,也就是最后一个指向内核对象的Handle被关闭,该对象也随之消亡,抑或进入一种无法撤销的最终状态。很明显,这与Arc天然的契合。 +## 控制句柄的权限——Rights +上文的Handle中有一个字段是rights,也就是句柄的权限。顾名思义,权限规定该句柄对引用的对象可以进行何种操作。 -在 `Cargo.toml` 中加入 `bitflags` 库: +当不同的权限和同一个对象绑定在一起时,也就形成了不同的句柄。 -```rust,noplaypen -[dependencies] -{{#include ../../code/ch01-02/Cargo.toml:bitflags}} -``` +### 定义权限 -在 object 模块下定义两个子模块: +在 object 模块下定义一个子模块: -```rust,noplaypen +```` // src/object/mod.rs -{{#include ../../code/ch01-02/src/object/mod.rs:mod}} -``` +mod rights; -定义权限: +pub use self::rights::*; +```` -```rust,noplaypen +权限就是u32的一个数字 + + +``` // src/object/rights.rs -{{#include ../../code/ch01-02/src/object/rights.rs:rights}} +use bitflags::bitflags; + +bitflags! { + /// 句柄权限 + pub struct Rights: u32 { + const DUPLICATE = 1 << 0; + const TRANSFER = 1 << 1; + const READ = 1 << 2; + const WRITE = 1 << 3; + const EXECUTE = 1 << 4; + ... + } + ``` -定义句柄: +[**bitflags**](https://docs.rs/bitflags/1.2.1/bitflags/) 是一个 Rust 中常用来比特标志位的 crate 。它提供了 一个 `bitflags!` 宏,如上面的代码段所展示的那样,借助 `bitflags!` 宏我们将一个 `u32` 的 rights 包装为一个 `Rights` 结构体。注意,在使用之前我们需要引入该 crate 的依赖: -```rust,noplaypen -// src/object/handle.rs -{{#include ../../code/ch01-02/src/object/handle.rs:handle}} ``` +# Cargo.toml -## 存储内核对象句柄 +[dependencies] +bitflags = "1.2" +``` -> 添加成员变量 handles: BTreeMap -> -> 实现 create,add_handle,remove_handle 函数 +定义好权限之后,我们回到句柄相关方法的实现。 -使用上一节的方法,实现一个空的 Process 对象: +首先是最简单的部分,创建一个handle,很显然我们需要提供两个参数,分别是句柄关联的内核对象和句柄的权限。 -```rust,noplaypen -// src/task/process.rs -{{#include ../../code/ch01-02/src/task/process.rs:process}} +``` +impl Handle { + /// 创建一个新句柄 + pub fn new(object: Arc, rights: Rights) -> Self { + Handle { object, rights } + } +} +``` + +### 测试 + +好啦,让我们来测试一下! + +``` +#[cfg(test)] +mod tests { + use super::*; + use crate::object::DummyObject; + + #[test] + fn new_obj_handle() { + let obj = DummyObject::new(); + let handle1 = Handle::new(obj.clone(), Rights::BASIC); + } } ``` -插入、删除句柄函数: +## 句柄存储的载体——Process + +实现完了句柄之后,我们开始考虑,句柄是存储在哪里的呢? + +通过前面的讲解,很明显Process拥有内核对象句柄,也就是说,句柄存储在Process中,所以我们先来实现一个Process: -```rust,noplaypen +### 实现空的process对象 + +``` // src/task/process.rs -impl Process { -{{#include ../../code/ch01-02/src/task/process.rs:add_remove_handle}} +/// 进程对象 +pub struct Process { + base: KObjectBase, + inner: Mutex, +} +// 宏的作用:补充 +impl_kobject!(Process); + +struct ProcessInner { + handles: BTreeMap, } + +pub type HandleValue = u32; ``` -## 定义内核错误及 `Result` 类型 +handles使用BTreeMap存储的key是HandleValue,value就是句柄。通过HandleValue实现对句柄的增删操作。HandleValue实际上就是u32类型是别名。 + +把内部对象ProcessInner用自旋锁Mutex包起来,保证了互斥访问,因为Mutex会帮我们处理好并发问题,这一点已经在1.1节中详细说明。 + +接下来我们实现创建一个Process的方法: + +``` +impl Process { + /// 创建一个新的进程对象 + pub fn new() -> Arc { + Arc::new(Process { + base: KObjectBase::default(), + inner: Mutex::new(ProcessInner { + handles: BTreeMap::default(), + }), + }) + } +} +``` -```rust,noplaypen -// src/error.rs -{{#include ../../code/ch01-02/src/error.rs:error_begin}} +#### 单元测试 - // ...... +我们已经实现了创建一个Process的方法,下面我们写一个单元测试: -{{#include ../../code/ch01-02/src/error.rs:error_end}} ``` +#[test] + fn new_proc() { + let proc = Process::new(); + assert_eq!(proc.type_name(), "Process"); + assert_eq!(proc.name(), ""); + proc.set_name("proc1"); + assert_eq!(proc.name(), "proc1"); + assert_eq!( + format!("{:?}", proc), + format!("Process({}, \"proc1\")", proc.id()) + ); + + let obj: Arc = proc; + assert_eq!(obj.type_name(), "Process"); + assert_eq!(obj.name(), "proc1"); + obj.set_name("proc2"); + assert_eq!(obj.name(), "proc2"); + assert_eq!( + format!("{:?}", obj), + format!("Process({}, \"proc2\")", obj.id()) + ); + } +``` + +### Process相关方法 + +#### 插入句柄 -```rust,noplaypen -// src/error.rs -{{#include ../../code/ch01-02/src/error.rs:result}} +在Process中添加一个新的handle,返回值是一个handleValue,也就是u32: + +``` +pub fn add_handle(&self, handle: Handle) -> HandleValue { + + let mut inner = self.inner.lock(); + let value = (0 as HandleValue..) + .find(|idx| !inner.handles.contains_key(idx)) + .unwrap(); + // 插入BTreeMap + inner.handles.insert(value, handle); + value + } ``` -## 根据句柄查找内核对象 +#### 移除句柄 + +删除Process中的一个句柄: + +``` +pub fn remove_handle(&self, handle_value: HandleValue) { + self.inner.lock().handles.remove(&handle_value); +} +``` -> 实现 get_object_with_rights 等其它相关函数 -> -> 实现 handle 单元测试 +#### 根据句柄查找内核对象 -```rust,noplaypen +``` // src/task/process.rs impl Process { -{{#include ../../code/ch01-02/src/task/process.rs:get_object_with_rights}} + /// 根据句柄值查找内核对象,并检查权限 + pub fn get_object_with_rights( + &self, + handle_value: HandleValue, + desired_rights: Rights, + ) -> ZxResult> { + let handle = self + .inner + .lock() + .handles + .get(&handle_value) + .ok_or(ZxError::BAD_HANDLE)? + .clone(); + // check type before rights + let object = handle + .object + .downcast_arc::() + .map_err(|_| ZxError::WRONG_TYPE)?; + if !handle.rights.contains(desired_rights) { + return Err(ZxError::ACCESS_DENIED); + } + Ok(object) + } +} +``` + +#### ZxResult + +ZxResult是表示Zircon状态的i32值,值空间划分如下: + +- 0:ok +- 负值:由系统定义(也就是这个文件) +- 正值:被保留,用于协议特定的错误值,永远不会被系统定义。 + +``` +pub type ZxResult = Result; + +#[allow(non_camel_case_types, dead_code)] +#[repr(i32)] +#[derive(Debug, Clone, Copy)] +pub enum ZxError { + OK = 0, + ... + + /// 一个不指向handle的特定的handle value + BAD_HANDLE = -11, + + /// 操作主体对于执行这个操作来说是错误的类型 + /// 例如: 尝试执行 message_read 在 thread handle. + WRONG_TYPE = -12, + + // 权限检查错误 + // 调用者没有执行该操作的权限 + ACCESS_DENIED = -30, } ``` + +ZxResult相当于Result,也就相当于我们自己定义了一种错误。 + +### 单元测试 + +目前为止,我们已经实现了Process最基础的方法,下面我们来运行一个单元测试: + +``` +fn proc_handle() { + let proc = Process::new(); + let handle = Handle::new(proc.clone(), Rights::DEFAULT_PROCESS); + let handle_value = proc.add_handle(handle); + + let object1: Arc = proc + .get_object_with_rights(handle_value, Rights::DEFAULT_PROCESS) + .expect("failed to get object"); + assert!(Arc::ptr_eq(&object1, &proc)); + + proc.remove_handle(handle_value); + } +``` + +## 总结 + +在这一节中我们实现了内核对象的两个重要的概念,句柄(Handle)和权限(Rights),同时实现了句柄存储的载体——Process,并且实现了Process的基本方法,这将是我们继续探索zCore的基础。 + +在下一节中,我们将介绍内核对象的传输器——管道(Channel)。 From 9f5aecb54e85dad33f9901c9a04f03b3a2bf8bf9 Mon Sep 17 00:00:00 2001 From: andream7 <2236407598@qq.com> Date: Thu, 19 Aug 2021 21:16:58 +0800 Subject: [PATCH 2/4] overwrite ch1-3 --- docs/src/ch01-03-channel-object.md | 241 ++++++++++++++++++++++++++--- 1 file changed, 223 insertions(+), 18 deletions(-) diff --git a/docs/src/ch01-03-channel-object.md b/docs/src/ch01-03-channel-object.md index 6468b6e..322e719 100644 --- a/docs/src/ch01-03-channel-object.md +++ b/docs/src/ch01-03-channel-object.md @@ -4,36 +4,241 @@ 通道(Channel)是由一定数量的字节数据和一定数量的句柄组成的双向消息传输。 -## 描述 +## 对象传输器——Channel -通道有两个端点(endpoints)。从逻辑上讲,每个端点都维护要读取的有序消息队列。写入一个端点会将消息排入另一个端点的队列中。当端点的最后一个句柄关闭时,该端点队列中的未读消息将被销毁。因为销毁消息会关闭消息包含的所有句柄,关闭通道端点可能会产生递归效果(例如,通道包含一条消息,它包含一个通道,它包含一条消息,等等)。 +## 用于IPC的内核对象 -关闭通道的最后一个句柄对先前写入该通道的消息的生命周期没有影响。这为通道提供了“即发即忘”的语义。 +Zircon中用于IPC的内核对象主要有Channel、Socket和FIFO。这里我们主要介绍一下前两个。 -一条消息由一定数量的数据和一定数量的句柄组成。调用[`channel_write()`](https://fuchsia.dev/docs/reference/syscalls/channel_write)使一条消息入队,调用[`channel_read()`](https://fuchsia.dev/docs/reference/syscalls/channel_read) 使一条消息出列(如果有队列)。线程可以阻塞,直到消息通过[`object_wait_one()`](https://fuchsia.dev/docs/reference/syscalls/object_wait_one)或其他等待机制挂起。 +> **进程间通信**(**IPC**,*Inter-Process Communication*),指至少两个进程或线程间传送数据或信号的一些技术或方法。进程是计算机系统分配资源的最小单位(进程是分配资源最小的单位,而线程是调度的最小单位,线程共用进程资源)。每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。举一个典型的例子,使用进程间通信的两个应用可以被分类为客户端和服务器,客户端进程请求数据,服务端回复客户端的数据请求。有一些应用本身既是服务器又是客户端,这在分布式计算中,时常可以见到。这些进程可以运行在同一计算机上或网络连接的不同计算机上。 -或者,调用[`channel_call()`](https://fuchsia.dev/docs/reference/syscalls/channel_call)在通道的一个方向上将消息入队,等待相应的响应,然后将响应消息出队。在调用模式(call mode)下,相应的响应通过消息的前 4 个字节标识,称为事务 ID(transaction ID)。内核使用[`channel_call()`](https://fuchsia.dev/docs/reference/syscalls/channel_call),为消息提供唯一的事务 ID. +`Socket`和`Channel`都是双向和双端的IPC相关的`Object`。创建`Socket`或`Channel`将返回两个不同的`Handle`,分别指向`Socket`或`Channel`的两端。与channel的不同之处在于,socket仅能传输数据(而不移动句柄),而channel可以传递句柄。 -通过通道发送消息的过程有两个步骤。第一步是原子地将数据写入通道并将消息中所有句柄的所有权移到此通道中。此操作始终消耗句柄:在调用结束时,所有句柄要么全部在通道中,要么全部丢弃。第二步操作,通道读取(channel read),与第一步类似:成功后,下一条消息中的所有句柄都被原子地移动到接收进程的句柄表中。失败时,通道将保留所有权,然后它们将被删除。 +- `Socket`是面向流的对象,可以通过它读取或写入以一个或多个字节为单位的数据。 +- `Channel`是面向数据包的对象,并限制消息的大小最多为64K(如果有改变,可能会更小),以及最多1024个`Handle`挂载到同一消息上(如果有改变,同样可能会更小)。 -与许多其他内核对象类型不同,通道是不可复制的。因此,只有一个句柄与通道端点相关联,持有该句柄的进程被视为所有者(owner)。只有所有者可以读取或写入消息或将通道端点发送到另一个进程。 +当`Handle`被写入到`Channel`中时,在发送端`Process`中将会移除这些`Handle`。同时携带`Handle`的消息从`Channel`中被读取时,该`Handle`也将被加入到接收端`Process`中。在这两个时间点之间时,`Handle`将同时存在于两端(以保证它们指向的`Object`继续存在而不被销毁),除非`Channel`写入方向一端被关闭,这种情况下,指向该端点的正在发送的消息将被丢弃,并且它们包含的任何句柄都将被关闭。 -当通道端点的所有权从一个进程转移到另一个进程时,即使消息正在进行写入,消息也不会被重新排序或截断。转移事件之前的消息属于以前的所有者,转移之后的消息属于新的所有者。如果在传输端点时,正在进行消息读取,则之前描述的所有权转移方式同样适用。 +## Channel -即使最后剩余的句柄被剥夺了**DUPLICATE**权限,也不为其他内核对象提供上述顺序保证。 +Channel是唯一一个能传递handle的IPC,其他只能传递消息。通道有两个端点`endpoints`,对于代码实现来说,**通道是虚拟的,我们实际上是用通道的两个端点来描述一个通道**。两个端点各自要维护一个消息队列,在一个端点写消息,实际上是把消息写入**另一个端点**的消息队列队尾;在一个端点读消息,实际上是从**当前端点**的消息队列的队头读出一个消息。 -## 创建一对内核对象 +消息通常含有`data`和`handles`两部分,我们这里将消息封装为`MessagePacket`结构体,结构体中含有上述两个字段: -## 创建通道 +``` +#[derive(Default)] +pub struct MessagePacket { + /// message packet携带的数据data + pub data: Vec, + /// message packet携带的句柄Handle + pub handles: Vec, +} +``` -创建 Channel 将返回两个句柄,一个指向对象的每个端点。 +### 实现空的Channel对象 -> 实现 Channel::create -> -> 讲一下互相持有对方 Weak 指针的目的,这里有不可避免的 unsafe +在`src`目录下创建一个`ipc`目录,在`ipc`模块下定义一个子模块`channel`: -## 实现数据传输 +``` +// src/ipc/mod.rs +use super::*; -当句柄被写入通道时,它们会从发送进程中删除。当从通道读取带有句柄的消息时,句柄被添加到接收进程中。在这两个事件之间,句柄继续存在(确保它们所指的对象继续存在),除非它们写入的通道的末端关闭——此时发送到该端点的消息被丢弃并且它们包含的任何句柄都已关闭。 +mod channel; +pub use self::channel::*; +``` -> 实现 read, write 函数,read_write 单元测试 +在`ipc.rs`中引入`crate`: + +``` +// src/ipc/channel.rs + +use { + super::*, + crate::error::*, + crate::object::*, + alloc::collections::VecDeque, + alloc::sync::{Arc, Weak}, + spin::Mutex, +}; +``` + +把在上面提到的`MessagePacket`结构体添加到该文件中。 + +下面我们添加Channel结构体: + +``` +// src/ipc/channel.rs +pub struct Channel { + base: KObjectBase, + peer: Weak, + recv_queue: Mutex>, +} + +type T = MessagePacket; +``` + +`peer`代表当前端点所在管道的另一个端点,两端的结构体分别持有对方的`Weak`引用,并且两端的结构体将分别通过`Arc`引用,作为内核对象而被内核中的其他数据结构引用,这一部分我们将在创建Channel实例时提到。 + +`recv_queue`代表当前端点维护的消息队列,它使用`VecDeque`来存放`MessagePacket`,可以通过`pop_front()`、`push_back`等方法在队头弹出数据和在队尾压入数据。 + +用使用宏自动实现 `KernelObject` trait ,使用channel类型名,并添加两个函数。 + +``` +impl_kobject!(Channel + fn peer(&self) -> ZxResult> { + let peer = self.peer.upgrade().ok_or(ZxError::PEER_CLOSED)?; + Ok(peer) + } + fn related_koid(&self) -> KoID { + self.peer.upgrade().map(|p| p.id()).unwrap_or(0) + } +); +``` + +### 实现创建Channel的方法 + +下面我们来实现创建一个`Channel`的方法: + +``` +impl Channel { + + #[allow(unsafe_code)] + pub fn create() -> (Arc, Arc) { + let mut channel0 = Arc::new(Channel { + base: KObjectBase::default(), + peer: Weak::default(), + recv_queue: Default::default(), + }); + let channel1 = Arc::new(Channel { + base: KObjectBase::default(), + peer: Arc::downgrade(&channel0), + recv_queue: Default::default(), + }); + // no other reference of `channel0` + unsafe { + Arc::get_mut_unchecked(&mut channel0).peer = Arc::downgrade(&channel1); + } + (channel0, channel1) +} +``` + +该方法的返回值是两端点结构体(Channel)的`Arc`引用,这将作为内核对象被内核中的其他数据结构引用。两个端点互相持有对方`Weak`指针,这是因为一个端点无需引用计数为0,只要`strong_count`为0就可以被清理掉,即使另一个端点指向它。 + +> rust 语言并没有提供垃圾回收 (GC, Garbage Collection ) 的功能, 不过它提供了最简单的引用计数包装类型 `Rc`,这种引用计数功能也是早期 GC 常用的方法, 但是引用计数不能解决循环引用。那么如何 fix 这个循环引用呢?答案是 `Weak` 指针,只增加引用逻辑,不共享所有权,即不增加 strong reference count。由于 `Weak` 指针指向的对象可能析构了,所以不能直接解引用,要模式匹配,再 upgrade。 + +下面我们来分析一下这个`unsafe`代码块: + +``` +unsafe { + Arc::get_mut_unchecked(&mut channel0).peer = Arc::downgrade(&channel1); + } +``` + +由于两端的结构体将分别通过 `Arc` 引用,作为内核对象而被内核中的其他数据结构使用。因此,在同时初始化两端的同时,将必须对某一端的 Arc 指针进行获取可变引用的操作,即`get_mut_unchecked`接口。当 `Arc` 指针的引用计数不为 `1` 时,这一接口是非常不安全的,但是在当前情境下,我们使用这一接口进行`IPC` 对象的初始化,安全性是可以保证的。 + +### 单元测试 + +下面我们写一个单元测试,来验证我们写的`create`方法: + +``` +#[test] + fn test_basics() { + let (end0, end1) = Channel::create(); + assert!(Arc::ptr_eq( + &end0.peer().unwrap().downcast_arc().unwrap(), + &end1 + )); + assert_eq!(end0.related_koid(), end1.id()); + + drop(end1); + assert_eq!(end0.peer().unwrap_err(), ZxError::PEER_CLOSED); + assert_eq!(end0.related_koid(), 0); + } +``` + +### 实现数据传输 + +Channel中的数据传输,可以理解为`MessagePacket`在两个端点之间的传输,那么谁可以读写消息呢? + +有一个句柄与通道端点相关联,持有该句柄的进程被视为所有者(owner)。所以是(持有与通道端点关联句柄的)进程可以读取或写入消息,或将通道端点发送到另一个进程。 + +当`MessagePacket`被写入通道时,它们会从发送进程中删除。当从通道读取`MessagePacket`时,`MessagePacket`的句柄被添加到接收进程中。 + +#### read + +获取当前端点的`recv_queue`,从队头中读取一条消息,如果能读取到消息,返回`Ok`,否则返回错误信息。 + +``` +pub fn read(&self) -> ZxResult { + let mut recv_queue = self.recv_queue.lock(); + if let Some(_msg) = recv_queue.front() { + let msg = recv_queue.pop_front().unwrap(); + return Ok(msg); + } + if self.peer_closed() { + Err(ZxError::PEER_CLOSED) + } else { + Err(ZxError::SHOULD_WAIT) + } + } +``` + +#### write + +先获取当前端点对应的另一个端点的`Weak`指针,通过`upgrade`接口升级为`Arc`指针,从而获取到对应的结构体对象。在它的`recv_queue`队尾push一个`MessagePacket`。 + +``` +pub fn write(&self, msg: T) -> ZxResult { + let peer = self.peer.upgrade().ok_or(ZxError::PEER_CLOSED)?; + peer.push_general(msg); + Ok(()) + } +fn push_general(&self, msg: T) { + let mut send_queue = self.recv_queue.lock(); + send_queue.push_back(msg); + } +``` + +### 单元测试 + +下面我们写一个单元测试,验证我们上面写的`read`和`write`两个方法: + +``` +#[test] + fn read_write() { + let (channel0, channel1) = Channel::create(); + // write a message to each other + channel0 + .write(MessagePacket { + data: Vec::from("hello 1"), + handles: Vec::new(), + }) + .unwrap(); + + channel1 + .write(MessagePacket { + data: Vec::from("hello 0"), + handles: Vec::new(), + }) + .unwrap(); + + // read message should success + let recv_msg = channel1.read().unwrap(); + assert_eq!(recv_msg.data.as_slice(), b"hello 1"); + assert!(recv_msg.handles.is_empty()); + + let recv_msg = channel0.read().unwrap(); + assert_eq!(recv_msg.data.as_slice(), b"hello 0"); + assert!(recv_msg.handles.is_empty()); + + // read more message should fail. + assert_eq!(channel0.read().err(), Some(ZxError::SHOULD_WAIT)); + assert_eq!(channel1.read().err(), Some(ZxError::SHOULD_WAIT)); + } +``` + +## 总结 + +在这一节中我们实现了唯一一个可以传递句柄的对象传输器——Channel,我们先了解的Zircon中主要的IPC内核对象,再介绍了Channel如何创建和实现read和write函数的细节。 + +本章我们学习了中最核心的几个内核对象,在下一章中,我们将学习`Zircon`的任务管理体系和进程、线程管理的对象。 \ No newline at end of file From 6f41217d39e7a2b9f6aa1205f3c8ec64a33a7475 Mon Sep 17 00:00:00 2001 From: Shukun Zhang <60541766+andream7@users.noreply.github.com> Date: Thu, 19 Aug 2021 21:18:18 +0800 Subject: [PATCH 3/4] Update ch01-03-channel-object.md --- docs/src/ch01-03-channel-object.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/src/ch01-03-channel-object.md b/docs/src/ch01-03-channel-object.md index 322e719..42f315b 100644 --- a/docs/src/ch01-03-channel-object.md +++ b/docs/src/ch01-03-channel-object.md @@ -4,8 +4,6 @@ 通道(Channel)是由一定数量的字节数据和一定数量的句柄组成的双向消息传输。 -## 对象传输器——Channel - ## 用于IPC的内核对象 Zircon中用于IPC的内核对象主要有Channel、Socket和FIFO。这里我们主要介绍一下前两个。 @@ -241,4 +239,4 @@ fn push_general(&self, msg: T) { 在这一节中我们实现了唯一一个可以传递句柄的对象传输器——Channel,我们先了解的Zircon中主要的IPC内核对象,再介绍了Channel如何创建和实现read和write函数的细节。 -本章我们学习了中最核心的几个内核对象,在下一章中,我们将学习`Zircon`的任务管理体系和进程、线程管理的对象。 \ No newline at end of file +本章我们学习了中最核心的几个内核对象,在下一章中,我们将学习`Zircon`的任务管理体系和进程、线程管理的对象。 From 324d7c93a5770239185be5d996309533581092f2 Mon Sep 17 00:00:00 2001 From: andream7 <2236407598@qq.com> Date: Sun, 22 Aug 2021 21:46:20 +0800 Subject: [PATCH 4/4] fix --- docs/src/ch01-02-process-object.md | 32 +++++++++++++++--------------- docs/src/ch01-03-channel-object.md | 22 ++++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/src/ch01-02-process-object.md b/docs/src/ch01-02-process-object.md index 9f7f86f..cca1a60 100644 --- a/docs/src/ch01-02-process-object.md +++ b/docs/src/ch01-02-process-object.md @@ -12,7 +12,7 @@ 在 object 模块下定义一个子模块: -```rust +```rust,noplaypen // src/object/mod.rs mod handle; @@ -21,7 +21,7 @@ pub use self::handle::*; 定义句柄: -```rust +```rust,noplaypen // src/object/handle.rs use super::{KernelObject, Rights}; use alloc::sync::Arc; @@ -52,17 +52,17 @@ Arc是一个可以在多线程上使用的引用计数类型,这个计数 在 object 模块下定义一个子模块: -```` +```rust,noplaypen // src/object/mod.rs mod rights; pub use self::rights::*; -```` +``` 权限就是u32的一个数字 -``` +```rust,noplaypen // src/object/rights.rs use bitflags::bitflags; @@ -81,7 +81,7 @@ bitflags! { [**bitflags**](https://docs.rs/bitflags/1.2.1/bitflags/) 是一个 Rust 中常用来比特标志位的 crate 。它提供了 一个 `bitflags!` 宏,如上面的代码段所展示的那样,借助 `bitflags!` 宏我们将一个 `u32` 的 rights 包装为一个 `Rights` 结构体。注意,在使用之前我们需要引入该 crate 的依赖: -``` +```rust,noplaypen # Cargo.toml [dependencies] @@ -92,7 +92,7 @@ bitflags = "1.2" 首先是最简单的部分,创建一个handle,很显然我们需要提供两个参数,分别是句柄关联的内核对象和句柄的权限。 -``` +```rust,noplaypen impl Handle { /// 创建一个新句柄 pub fn new(object: Arc, rights: Rights) -> Self { @@ -105,7 +105,7 @@ impl Handle { 好啦,让我们来测试一下! -``` +```rust,noplaypen #[cfg(test)] mod tests { use super::*; @@ -127,7 +127,7 @@ mod tests { ### 实现空的process对象 -``` +```rust,noplaypen // src/task/process.rs /// 进程对象 pub struct Process { @@ -150,7 +150,7 @@ handles使用BTreeMap存储的key是HandleValue,value就是句柄。通过Hand 接下来我们实现创建一个Process的方法: -``` +```rust,noplaypen impl Process { /// 创建一个新的进程对象 pub fn new() -> Arc { @@ -168,7 +168,7 @@ impl Process { 我们已经实现了创建一个Process的方法,下面我们写一个单元测试: -``` +```rust,noplaypen #[test] fn new_proc() { let proc = Process::new(); @@ -199,7 +199,7 @@ impl Process { 在Process中添加一个新的handle,返回值是一个handleValue,也就是u32: -``` +```rust,noplaypen pub fn add_handle(&self, handle: Handle) -> HandleValue { let mut inner = self.inner.lock(); @@ -216,7 +216,7 @@ pub fn add_handle(&self, handle: Handle) -> HandleValue { 删除Process中的一个句柄: -``` +```rust,noplaypen pub fn remove_handle(&self, handle_value: HandleValue) { self.inner.lock().handles.remove(&handle_value); } @@ -224,7 +224,7 @@ pub fn remove_handle(&self, handle_value: HandleValue) { #### 根据句柄查找内核对象 -``` +```rust,noplaypen // src/task/process.rs impl Process { /// 根据句柄值查找内核对象,并检查权限 @@ -261,7 +261,7 @@ ZxResult是表示Zircon状态的i32值,值空间划分如下: - 负值:由系统定义(也就是这个文件) - 正值:被保留,用于协议特定的错误值,永远不会被系统定义。 -``` +```rust,noplaypen pub type ZxResult = Result; #[allow(non_camel_case_types, dead_code)] @@ -290,7 +290,7 @@ ZxResult相当于Result,也就相当于我们自己定义了一 目前为止,我们已经实现了Process最基础的方法,下面我们来运行一个单元测试: -``` +```rust,noplaypen fn proc_handle() { let proc = Process::new(); let handle = Handle::new(proc.clone(), Rights::DEFAULT_PROCESS); diff --git a/docs/src/ch01-03-channel-object.md b/docs/src/ch01-03-channel-object.md index 42f315b..6d45d77 100644 --- a/docs/src/ch01-03-channel-object.md +++ b/docs/src/ch01-03-channel-object.md @@ -23,7 +23,7 @@ Channel是唯一一个能传递handle的IPC,其他只能传递消息。通道 消息通常含有`data`和`handles`两部分,我们这里将消息封装为`MessagePacket`结构体,结构体中含有上述两个字段: -``` +```rust,noplaypen #[derive(Default)] pub struct MessagePacket { /// message packet携带的数据data @@ -37,7 +37,7 @@ pub struct MessagePacket { 在`src`目录下创建一个`ipc`目录,在`ipc`模块下定义一个子模块`channel`: -``` +```rust,noplaypen // src/ipc/mod.rs use super::*; @@ -47,7 +47,7 @@ pub use self::channel::*; 在`ipc.rs`中引入`crate`: -``` +```rust,noplaypen // src/ipc/channel.rs use { @@ -64,7 +64,7 @@ use { 下面我们添加Channel结构体: -``` +```rust,noplaypen // src/ipc/channel.rs pub struct Channel { base: KObjectBase, @@ -81,7 +81,7 @@ type T = MessagePacket; 用使用宏自动实现 `KernelObject` trait ,使用channel类型名,并添加两个函数。 -``` +```rust,noplaypen impl_kobject!(Channel fn peer(&self) -> ZxResult> { let peer = self.peer.upgrade().ok_or(ZxError::PEER_CLOSED)?; @@ -97,7 +97,7 @@ impl_kobject!(Channel 下面我们来实现创建一个`Channel`的方法: -``` +```rust,noplaypen impl Channel { #[allow(unsafe_code)] @@ -126,7 +126,7 @@ impl Channel { 下面我们来分析一下这个`unsafe`代码块: -``` +```rust,noplaypen unsafe { Arc::get_mut_unchecked(&mut channel0).peer = Arc::downgrade(&channel1); } @@ -138,7 +138,7 @@ unsafe { 下面我们写一个单元测试,来验证我们写的`create`方法: -``` +```rust,noplaypen #[test] fn test_basics() { let (end0, end1) = Channel::create(); @@ -166,7 +166,7 @@ Channel中的数据传输,可以理解为`MessagePacket`在两个端点之间 获取当前端点的`recv_queue`,从队头中读取一条消息,如果能读取到消息,返回`Ok`,否则返回错误信息。 -``` +```rust,noplaypen pub fn read(&self) -> ZxResult { let mut recv_queue = self.recv_queue.lock(); if let Some(_msg) = recv_queue.front() { @@ -185,7 +185,7 @@ pub fn read(&self) -> ZxResult { 先获取当前端点对应的另一个端点的`Weak`指针,通过`upgrade`接口升级为`Arc`指针,从而获取到对应的结构体对象。在它的`recv_queue`队尾push一个`MessagePacket`。 -``` +```rust,noplaypen pub fn write(&self, msg: T) -> ZxResult { let peer = self.peer.upgrade().ok_or(ZxError::PEER_CLOSED)?; peer.push_general(msg); @@ -201,7 +201,7 @@ fn push_general(&self, msg: T) { 下面我们写一个单元测试,验证我们上面写的`read`和`write`两个方法: -``` +```rust,noplaypen #[test] fn read_write() { let (channel0, channel1) = Channel::create();