|
|
|
|
@ -588,29 +588,44 @@ pub struct DummyObject;
|
|
|
|
|
不过万事开头难,解决这些问题为整个项目打下了坚实基础,后面实现新的内核对象就会变得简单很多。</p>
|
|
|
|
|
<p>在下一节中,我们将介绍内核对象相关的另外两个概念:句柄和权限,并实现内核对象的存储和访问。</p>
|
|
|
|
|
<div style="break-before: page; page-break-before: always;"></div><h4 id="对象管理器process-对象"><a class="header" href="#对象管理器process-对象">对象管理器:Process 对象</a></h4>
|
|
|
|
|
<h2 id="权限"><a class="header" href="#权限">权限</a></h2>
|
|
|
|
|
<p>内核对象的“<a href="https://fuchsia.dev/docs/concepts/kernel/rights">权限</a>”指定允许对内核对象进行哪些操作。权限与句柄相关联,并传达对关联句柄或与句柄关联的对象执行操作的特权。单个进程可能对具有不同权限的同一个内核对象有两个不同的句柄。</p>
|
|
|
|
|
<h2 id="句柄"><a class="header" href="#句柄">句柄</a></h2>
|
|
|
|
|
<h2 id="句柄操作内核对象的桥梁"><a class="header" href="#句柄操作内核对象的桥梁">句柄——操作内核对象的桥梁</a></h2>
|
|
|
|
|
<p>在1.1中我们用Rust语言实现了一个最核心的内核对象,在本小节我们将逐步了解与内核对象相关的三个重要概念中的其他两个:<strong>句柄(Handle)和权限(Rights)</strong>。</p>
|
|
|
|
|
<p>句柄是允许用户程序引用内核对象引用的一种内核结构,它可以被认为是与特定内核对象的会话或连接。</p>
|
|
|
|
|
<p>通常情况下,多个进程通过不同的句柄同时访问同一个对象。对象可能有多个句柄(在一个或多个进程中)引用它们。但单个句柄只能绑定到单个进程或绑定到内核。</p>
|
|
|
|
|
<p>当句柄绑定到内核时,我们说它是“在传输中”('in-transit')。</p>
|
|
|
|
|
<p>在用户模式下,句柄只是某个系统调用返回的特定数字。只有“不在传输中”的句柄对用户模式可见。</p>
|
|
|
|
|
<p>代表句柄的整数只对其所属的那个进程有意义。另一个进程中的相同数字可能不会映射到任何句柄,或者它可能映射到指向完全不同的内核对象的句柄。</p>
|
|
|
|
|
<p>句柄的整数值是任何 32 位数字,但对应于<strong>ZX_HANDLE_INVALID</strong>的值将始终为 0。除此之外,有效句柄的整数值将始终具有句柄集的两个最低有效位. 可以使用<strong>ZX_HANDLE_FIXED_BITS_MASK</strong>访问代表这些位的掩码。</p>
|
|
|
|
|
<p>句柄可以从一个进程移动到另一个进程,方法是将它们写入通道(使用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_write"><code>channel_write()</code></a>),或者使用 <a href="https://fuchsia.dev/docs/reference/syscalls/process_start"><code>process_start()</code></a>传递一个句柄作为新进程中第一个线程的参数。对于几乎所有的对象,当最后一个打开的引用对象的句柄关闭时,对象要么被销毁,要么被置于可能无法撤消的最终状态。</p>
|
|
|
|
|
<p>在 <code>Cargo.toml</code> 中加入 <code>bitflags</code> 库:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">[dependencies]
|
|
|
|
|
bitflags = "1.2"
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>在 object 模块下定义两个子模块:</p>
|
|
|
|
|
<h3 id="定义句柄"><a class="header" href="#定义句柄">定义句柄</a></h3>
|
|
|
|
|
<p>在 object 模块下定义一个子模块:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/object/mod.rs
|
|
|
|
|
mod handle;
|
|
|
|
|
mod rights;
|
|
|
|
|
|
|
|
|
|
pub use self::handle::*;
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>定义句柄:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/object/handle.rs
|
|
|
|
|
use super::{KernelObject, Rights};
|
|
|
|
|
use alloc::sync::Arc;
|
|
|
|
|
|
|
|
|
|
/// 内核对象句柄
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct Handle {
|
|
|
|
|
pub object: Arc<dyn KernelObject>,
|
|
|
|
|
pub rights: Rights,
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>一个Handle包含object和right两个字段,object是实现了<code>KernelObject</code>Trait的内核对象,Rights是该句柄的权限,我们将在下面提到它。</p>
|
|
|
|
|
<p>Arc<T>是一个可以在多线程上使用的引用计数类型,这个计数会随着 <code>Arc<T></code> 的创建或复制而增加,并当 <code>Arc<T></code> 生命周期结束被回收时减少。当这个计数变为零之后,这个计数变量本身以及被引用的变量都会从堆上被回收。</p>
|
|
|
|
|
<p>我们为什么要在这里使用Arc智能指针呢?</p>
|
|
|
|
|
<p>绝大多数内核对象的析构都发生在句柄数量为 0 时,也就是最后一个指向内核对象的Handle被关闭,该对象也随之消亡,抑或进入一种无法撤销的最终状态。很明显,这与Arc<T>天然的契合。</p>
|
|
|
|
|
<h2 id="控制句柄的权限rights"><a class="header" href="#控制句柄的权限rights">控制句柄的权限——Rights</a></h2>
|
|
|
|
|
<p>上文的Handle中有一个字段是rights,也就是句柄的权限。顾名思义,权限规定该句柄对引用的对象可以进行何种操作。</p>
|
|
|
|
|
<p>当不同的权限和同一个对象绑定在一起时,也就形成了不同的句柄。</p>
|
|
|
|
|
<h3 id="定义权限"><a class="header" href="#定义权限">定义权限</a></h3>
|
|
|
|
|
<p>在 object 模块下定义一个子模块:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/object/mod.rs
|
|
|
|
|
mod rights;
|
|
|
|
|
|
|
|
|
|
pub use self::rights::*;
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>定义权限:</p>
|
|
|
|
|
<p>权限就是u32的一个数字</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/object/rights.rs
|
|
|
|
|
use bitflags::bitflags;
|
|
|
|
|
|
|
|
|
|
@ -622,66 +637,50 @@ bitflags! {
|
|
|
|
|
const READ = 1 << 2;
|
|
|
|
|
const WRITE = 1 << 3;
|
|
|
|
|
const EXECUTE = 1 << 4;
|
|
|
|
|
const MAP = 1 << 5;
|
|
|
|
|
const GET_PROPERTY = 1 << 6;
|
|
|
|
|
const SET_PROPERTY = 1 << 7;
|
|
|
|
|
const ENUMERATE = 1 << 8;
|
|
|
|
|
const DESTROY = 1 << 9;
|
|
|
|
|
const SET_POLICY = 1 << 10;
|
|
|
|
|
const GET_POLICY = 1 << 11;
|
|
|
|
|
const SIGNAL = 1 << 12;
|
|
|
|
|
const SIGNAL_PEER = 1 << 13;
|
|
|
|
|
const WAIT = 1 << 14;
|
|
|
|
|
const INSPECT = 1 << 15;
|
|
|
|
|
const MANAGE_JOB = 1 << 16;
|
|
|
|
|
const MANAGE_PROCESS = 1 << 17;
|
|
|
|
|
const MANAGE_THREAD = 1 << 18;
|
|
|
|
|
const APPLY_PROFILE = 1 << 19;
|
|
|
|
|
const SAME_RIGHTS = 1 << 31;
|
|
|
|
|
|
|
|
|
|
const BASIC = Self::TRANSFER.bits | Self::DUPLICATE.bits | Self::WAIT.bits | Self::INSPECT.bits;
|
|
|
|
|
const IO = Self::READ.bits | Self::WRITE.bits;
|
|
|
|
|
|
|
|
|
|
const DEFAULT_CHANNEL = Self::BASIC.bits & !Self::DUPLICATE.bits | Self::IO.bits | Self::SIGNAL.bits | Self::SIGNAL_PEER.bits;
|
|
|
|
|
/// GET_PROPERTY | SET_PROPERTY
|
|
|
|
|
const PROPERTY = Self::GET_PROPERTY.bits | Self::SET_PROPERTY.bits;
|
|
|
|
|
/// BASIC | IO | PROPERTY | ENUMERATE | DESTROY | SIGNAL | MANAGE_PROCESS | MANAGE_THREAD
|
|
|
|
|
const DEFAULT_PROCESS = Self::BASIC.bits | Self::IO.bits | Self::PROPERTY.bits | Self::ENUMERATE.bits | Self::DESTROY.bits | Self::SIGNAL.bits | Self::MANAGE_PROCESS.bits | Self::MANAGE_THREAD.bits;
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>定义句柄:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/object/handle.rs
|
|
|
|
|
use super::{KernelObject, Rights};
|
|
|
|
|
use alloc::sync::Arc;
|
|
|
|
|
|
|
|
|
|
/// 内核对象句柄
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct Handle {
|
|
|
|
|
pub object: Arc<dyn KernelObject>,
|
|
|
|
|
pub rights: Rights,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Handle {
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p><a href="https://docs.rs/bitflags/1.2.1/bitflags/"><strong>bitflags</strong></a> 是一个 Rust 中常用来比特标志位的 crate 。它提供了 一个 <code>bitflags!</code> 宏,如上面的代码段所展示的那样,借助 <code>bitflags!</code> 宏我们将一个 <code>u32</code> 的 rights 包装为一个 <code>Rights</code> 结构体。注意,在使用之前我们需要引入该 crate 的依赖:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen"><span class="boring">Cargo.toml
|
|
|
|
|
</span>
|
|
|
|
|
[dependencies]
|
|
|
|
|
bitflags = "1.2"
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>定义好权限之后,我们回到句柄相关方法的实现。</p>
|
|
|
|
|
<p>首先是最简单的部分,创建一个handle,很显然我们需要提供两个参数,分别是句柄关联的内核对象和句柄的权限。</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">impl Handle {
|
|
|
|
|
/// 创建一个新句柄
|
|
|
|
|
pub fn new(object: Arc<dyn KernelObject>, rights: Rights) -> Self {
|
|
|
|
|
Handle { object, rights }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h2 id="存储内核对象句柄"><a class="header" href="#存储内核对象句柄">存储内核对象句柄</a></h2>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p>添加成员变量 handles: BTreeMap<HandleValue, Handle></p>
|
|
|
|
|
<p>实现 create,add_handle,remove_handle 函数</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
<p>使用上一节的方法,实现一个空的 Process 对象:</p>
|
|
|
|
|
<h3 id="测试"><a class="header" href="#测试">测试</a></h3>
|
|
|
|
|
<p>好啦,让我们来测试一下!</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">#[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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h2 id="句柄存储的载体process"><a class="header" href="#句柄存储的载体process">句柄存储的载体——Process</a></h2>
|
|
|
|
|
<p>实现完了句柄之后,我们开始考虑,句柄是存储在哪里的呢?</p>
|
|
|
|
|
<p>通过前面的讲解,很明显Process拥有内核对象句柄,也就是说,句柄存储在Process中,所以我们先来实现一个Process:</p>
|
|
|
|
|
<h3 id="实现空的process对象"><a class="header" href="#实现空的process对象">实现空的process对象</a></h3>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/task/process.rs
|
|
|
|
|
/// 进程对象
|
|
|
|
|
pub struct Process {
|
|
|
|
|
base: KObjectBase,
|
|
|
|
|
inner: Mutex<ProcessInner>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 宏的作用:补充
|
|
|
|
|
impl_kobject!(Process);
|
|
|
|
|
|
|
|
|
|
struct ProcessInner {
|
|
|
|
|
@ -689,8 +688,11 @@ struct ProcessInner {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub type HandleValue = u32;
|
|
|
|
|
|
|
|
|
|
impl Process {
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>handles使用BTreeMap存储的key是HandleValue,value就是句柄。通过HandleValue实现对句柄的增删操作。HandleValue实际上就是u32类型是别名。</p>
|
|
|
|
|
<p>把内部对象ProcessInner用自旋锁Mutex包起来,保证了互斥访问,因为Mutex会帮我们处理好并发问题,这一点已经在1.1节中详细说明。</p>
|
|
|
|
|
<p>接下来我们实现创建一个Process的方法:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">impl Process {
|
|
|
|
|
/// 创建一个新的进程对象
|
|
|
|
|
pub fn new() -> Arc<Self> {
|
|
|
|
|
Arc::new(Process {
|
|
|
|
|
@ -702,63 +704,52 @@ impl Process {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>插入、删除句柄函数:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/task/process.rs
|
|
|
|
|
impl Process {
|
|
|
|
|
/// 添加一个新的对象句柄
|
|
|
|
|
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();
|
|
|
|
|
inner.handles.insert(value, handle);
|
|
|
|
|
value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 删除一个对象句柄
|
|
|
|
|
pub fn remove_handle(&self, handle_value: HandleValue) {
|
|
|
|
|
self.inner.lock().handles.remove(&handle_value);
|
|
|
|
|
<h4 id="单元测试"><a class="header" href="#单元测试">单元测试</a></h4>
|
|
|
|
|
<p>我们已经实现了创建一个Process的方法,下面我们写一个单元测试:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">#[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<dyn KernelObject> = 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())
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h2 id="定义内核错误及-result-类型"><a class="header" href="#定义内核错误及-result-类型">定义内核错误及 <code>Result</code> 类型</a></h2>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/error.rs
|
|
|
|
|
/// Zircon statuses are signed 32 bit integers. The space of values is
|
|
|
|
|
/// divided as follows:
|
|
|
|
|
/// - The zero value is for the OK status.
|
|
|
|
|
/// - Negative values are defined by the system, in this file.
|
|
|
|
|
/// - Positive values are reserved for protocol-specific error values,
|
|
|
|
|
/// and will never be defined by the system.
|
|
|
|
|
#[allow(non_camel_case_types, dead_code)]
|
|
|
|
|
#[repr(i32)]
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
pub enum ZxError {
|
|
|
|
|
OK = 0,
|
|
|
|
|
|
|
|
|
|
// ======= Internal failures =======
|
|
|
|
|
/// The system encountered an otherwise unspecified error
|
|
|
|
|
/// while performing the operation.
|
|
|
|
|
INTERNAL = -1,
|
|
|
|
|
|
|
|
|
|
/// The operation is not implemented, supported,
|
|
|
|
|
/// or enabled.
|
|
|
|
|
NOT_SUPPORTED = -2,
|
|
|
|
|
|
|
|
|
|
// ......
|
|
|
|
|
|
|
|
|
|
/// Connection was aborted.
|
|
|
|
|
CONNECTION_ABORTED = -76,
|
|
|
|
|
}
|
|
|
|
|
<h3 id="process相关方法"><a class="header" href="#process相关方法">Process相关方法</a></h3>
|
|
|
|
|
<h4 id="插入句柄"><a class="header" href="#插入句柄">插入句柄</a></h4>
|
|
|
|
|
<p>在Process中添加一个新的handle,返回值是一个handleValue,也就是u32:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">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
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/error.rs
|
|
|
|
|
///
|
|
|
|
|
pub type ZxResult<T> = Result<T, ZxError>;
|
|
|
|
|
<h4 id="移除句柄"><a class="header" href="#移除句柄">移除句柄</a></h4>
|
|
|
|
|
<p>删除Process中的一个句柄:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">pub fn remove_handle(&self, handle_value: HandleValue) {
|
|
|
|
|
self.inner.lock().handles.remove(&handle_value);
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h2 id="根据句柄查找内核对象"><a class="header" href="#根据句柄查找内核对象">根据句柄查找内核对象</a></h2>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p>实现 get_object_with_rights 等其它相关函数</p>
|
|
|
|
|
<p>实现 handle 单元测试</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
<h4 id="根据句柄查找内核对象"><a class="header" href="#根据句柄查找内核对象">根据句柄查找内核对象</a></h4>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/task/process.rs
|
|
|
|
|
impl Process {
|
|
|
|
|
/// 根据句柄值查找内核对象,并检查权限
|
|
|
|
|
@ -786,30 +777,239 @@ impl Process {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h4 id="zxresult"><a class="header" href="#zxresult">ZxResult</a></h4>
|
|
|
|
|
<p>ZxResult是表示Zircon状态的i32值,值空间划分如下:</p>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>0:ok</li>
|
|
|
|
|
<li>负值:由系统定义(也就是这个文件)</li>
|
|
|
|
|
<li>正值:被保留,用于协议特定的错误值,永远不会被系统定义。</li>
|
|
|
|
|
</ul>
|
|
|
|
|
<pre><code class="language-rust noplaypen">pub type ZxResult<T> = Result<T, ZxError>;
|
|
|
|
|
|
|
|
|
|
#[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,
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>ZxResult<T>相当于Result<T, ZxError>,也就相当于我们自己定义了一种错误。</p>
|
|
|
|
|
<h3 id="单元测试-1"><a class="header" href="#单元测试-1">单元测试</a></h3>
|
|
|
|
|
<p>目前为止,我们已经实现了Process最基础的方法,下面我们来运行一个单元测试:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">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<Process> = 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);
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h2 id="总结-1"><a class="header" href="#总结-1">总结</a></h2>
|
|
|
|
|
<p>在这一节中我们实现了内核对象的两个重要的概念,句柄(Handle)和权限(Rights),同时实现了句柄存储的载体——Process,并且实现了Process的基本方法,这将是我们继续探索zCore的基础。</p>
|
|
|
|
|
<p>在下一节中,我们将介绍内核对象的传输器——管道(Channel)。</p>
|
|
|
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="对象传送器channel-对象"><a class="header" href="#对象传送器channel-对象">对象传送器:Channel 对象</a></h1>
|
|
|
|
|
<h2 id="概要"><a class="header" href="#概要">概要</a></h2>
|
|
|
|
|
<p>通道(Channel)是由一定数量的字节数据和一定数量的句柄组成的双向消息传输。</p>
|
|
|
|
|
<h2 id="描述"><a class="header" href="#描述">描述</a></h2>
|
|
|
|
|
<p>通道有两个端点(endpoints)。从逻辑上讲,每个端点都维护要读取的有序消息队列。写入一个端点会将消息排入另一个端点的队列中。当端点的最后一个句柄关闭时,该端点队列中的未读消息将被销毁。因为销毁消息会关闭消息包含的所有句柄,关闭通道端点可能会产生递归效果(例如,通道包含一条消息,它包含一个通道,它包含一条消息,等等)。</p>
|
|
|
|
|
<p>关闭通道的最后一个句柄对先前写入该通道的消息的生命周期没有影响。这为通道提供了“即发即忘”的语义。</p>
|
|
|
|
|
<p>一条消息由一定数量的数据和一定数量的句柄组成。调用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_write"><code>channel_write()</code></a>使一条消息入队,调用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_read"><code>channel_read()</code></a> 使一条消息出列(如果有队列)。线程可以阻塞,直到消息通过<a href="https://fuchsia.dev/docs/reference/syscalls/object_wait_one"><code>object_wait_one()</code></a>或其他等待机制挂起。</p>
|
|
|
|
|
<p>或者,调用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_call"><code>channel_call()</code></a>在通道的一个方向上将消息入队,等待相应的响应,然后将响应消息出队。在调用模式(call mode)下,相应的响应通过消息的前 4 个字节标识,称为事务 ID(transaction ID)。内核使用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_call"><code>channel_call()</code></a>,为消息提供唯一的事务 ID.</p>
|
|
|
|
|
<p>通过通道发送消息的过程有两个步骤。第一步是原子地将数据写入通道并将消息中所有句柄的所有权移到此通道中。此操作始终消耗句柄:在调用结束时,所有句柄要么全部在通道中,要么全部丢弃。第二步操作,通道读取(channel read),与第一步类似:成功后,下一条消息中的所有句柄都被原子地移动到接收进程的句柄表中。失败时,通道将保留所有权,然后它们将被删除。</p>
|
|
|
|
|
<p>与许多其他内核对象类型不同,通道是不可复制的。因此,只有一个句柄与通道端点相关联,持有该句柄的进程被视为所有者(owner)。只有所有者可以读取或写入消息或将通道端点发送到另一个进程。</p>
|
|
|
|
|
<p>当通道端点的所有权从一个进程转移到另一个进程时,即使消息正在进行写入,消息也不会被重新排序或截断。转移事件之前的消息属于以前的所有者,转移之后的消息属于新的所有者。如果在传输端点时,正在进行消息读取,则之前描述的所有权转移方式同样适用。</p>
|
|
|
|
|
<p>即使最后剩余的句柄被剥夺了<strong>DUPLICATE</strong>权限,也不为其他内核对象提供上述顺序保证。</p>
|
|
|
|
|
<h2 id="创建一对内核对象"><a class="header" href="#创建一对内核对象">创建一对内核对象</a></h2>
|
|
|
|
|
<h2 id="创建通道"><a class="header" href="#创建通道">创建通道</a></h2>
|
|
|
|
|
<p>创建 Channel 将返回两个句柄,一个指向对象的每个端点。</p>
|
|
|
|
|
<h2 id="用于ipc的内核对象"><a class="header" href="#用于ipc的内核对象">用于IPC的内核对象</a></h2>
|
|
|
|
|
<p>Zircon中用于IPC的内核对象主要有Channel、Socket和FIFO。这里我们主要介绍一下前两个。</p>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p>实现 Channel::create</p>
|
|
|
|
|
<p>讲一下互相持有对方 Weak 指针的目的,这里有不可避免的 unsafe</p>
|
|
|
|
|
<p><strong>进程间通信</strong>(<strong>IPC</strong>,<em>Inter-Process Communication</em>),指至少两个进程或线程间传送数据或信号的一些技术或方法。进程是计算机系统分配资源的最小单位(进程是分配资源最小的单位,而线程是调度的最小单位,线程共用进程资源)。每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。举一个典型的例子,使用进程间通信的两个应用可以被分类为客户端和服务器,客户端进程请求数据,服务端回复客户端的数据请求。有一些应用本身既是服务器又是客户端,这在分布式计算中,时常可以见到。这些进程可以运行在同一计算机上或网络连接的不同计算机上。</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
<h2 id="实现数据传输"><a class="header" href="#实现数据传输">实现数据传输</a></h2>
|
|
|
|
|
<p>当句柄被写入通道时,它们会从发送进程中删除。当从通道读取带有句柄的消息时,句柄被添加到接收进程中。在这两个事件之间,句柄继续存在(确保它们所指的对象继续存在),除非它们写入的通道的末端关闭——此时发送到该端点的消息被丢弃并且它们包含的任何句柄都已关闭。</p>
|
|
|
|
|
<p><code>Socket</code>和<code>Channel</code>都是双向和双端的IPC相关的<code>Object</code>。创建<code>Socket</code>或<code>Channel</code>将返回两个不同的<code>Handle</code>,分别指向<code>Socket</code>或<code>Channel</code>的两端。与channel的不同之处在于,socket仅能传输数据(而不移动句柄),而channel可以传递句柄。</p>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><code>Socket</code>是面向流的对象,可以通过它读取或写入以一个或多个字节为单位的数据。</li>
|
|
|
|
|
<li><code>Channel</code>是面向数据包的对象,并限制消息的大小最多为64K(如果有改变,可能会更小),以及最多1024个<code>Handle</code>挂载到同一消息上(如果有改变,同样可能会更小)。</li>
|
|
|
|
|
</ul>
|
|
|
|
|
<p>当<code>Handle</code>被写入到<code>Channel</code>中时,在发送端<code>Process</code>中将会移除这些<code>Handle</code>。同时携带<code>Handle</code>的消息从<code>Channel</code>中被读取时,该<code>Handle</code>也将被加入到接收端<code>Process</code>中。在这两个时间点之间时,<code>Handle</code>将同时存在于两端(以保证它们指向的<code>Object</code>继续存在而不被销毁),除非<code>Channel</code>写入方向一端被关闭,这种情况下,指向该端点的正在发送的消息将被丢弃,并且它们包含的任何句柄都将被关闭。</p>
|
|
|
|
|
<h2 id="channel"><a class="header" href="#channel">Channel</a></h2>
|
|
|
|
|
<p>Channel是唯一一个能传递handle的IPC,其他只能传递消息。通道有两个端点<code>endpoints</code>,对于代码实现来说,<strong>通道是虚拟的,我们实际上是用通道的两个端点来描述一个通道</strong>。两个端点各自要维护一个消息队列,在一个端点写消息,实际上是把消息写入<strong>另一个端点</strong>的消息队列队尾;在一个端点读消息,实际上是从<strong>当前端点</strong>的消息队列的队头读出一个消息。</p>
|
|
|
|
|
<p>消息通常含有<code>data</code>和<code>handles</code>两部分,我们这里将消息封装为<code>MessagePacket</code>结构体,结构体中含有上述两个字段:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">#[derive(Default)]
|
|
|
|
|
pub struct MessagePacket {
|
|
|
|
|
/// message packet携带的数据data
|
|
|
|
|
pub data: Vec<u8>,
|
|
|
|
|
/// message packet携带的句柄Handle
|
|
|
|
|
pub handles: Vec<Handle>,
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h3 id="实现空的channel对象"><a class="header" href="#实现空的channel对象">实现空的Channel对象</a></h3>
|
|
|
|
|
<p>在<code>src</code>目录下创建一个<code>ipc</code>目录,在<code>ipc</code>模块下定义一个子模块<code>channel</code>:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/ipc/mod.rs
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
mod channel;
|
|
|
|
|
pub use self::channel::*;
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>在<code>ipc.rs</code>中引入<code>crate</code>:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/ipc/channel.rs
|
|
|
|
|
|
|
|
|
|
use {
|
|
|
|
|
super::*,
|
|
|
|
|
crate::error::*,
|
|
|
|
|
crate::object::*,
|
|
|
|
|
alloc::collections::VecDeque,
|
|
|
|
|
alloc::sync::{Arc, Weak},
|
|
|
|
|
spin::Mutex,
|
|
|
|
|
};
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>把在上面提到的<code>MessagePacket</code>结构体添加到该文件中。</p>
|
|
|
|
|
<p>下面我们添加Channel结构体:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">// src/ipc/channel.rs
|
|
|
|
|
pub struct Channel {
|
|
|
|
|
base: KObjectBase,
|
|
|
|
|
peer: Weak<Channel>,
|
|
|
|
|
recv_queue: Mutex<VecDeque<T>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type T = MessagePacket;
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p><code>peer</code>代表当前端点所在管道的另一个端点,两端的结构体分别持有对方的<code>Weak</code>引用,并且两端的结构体将分别通过<code>Arc</code>引用,作为内核对象而被内核中的其他数据结构引用,这一部分我们将在创建Channel实例时提到。</p>
|
|
|
|
|
<p><code>recv_queue</code>代表当前端点维护的消息队列,它使用<code>VecDeque</code>来存放<code>MessagePacket</code>,可以通过<code>pop_front()</code>、<code>push_back</code>等方法在队头弹出数据和在队尾压入数据。</p>
|
|
|
|
|
<p>用使用宏自动实现 <code>KernelObject</code> trait ,使用channel类型名,并添加两个函数。</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">impl_kobject!(Channel
|
|
|
|
|
fn peer(&self) -> ZxResult<Arc<dyn KernelObject>> {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h3 id="实现创建channel的方法"><a class="header" href="#实现创建channel的方法">实现创建Channel的方法</a></h3>
|
|
|
|
|
<p>下面我们来实现创建一个<code>Channel</code>的方法:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">impl Channel {
|
|
|
|
|
|
|
|
|
|
#[allow(unsafe_code)]
|
|
|
|
|
pub fn create() -> (Arc<Self>, Arc<Self>) {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>该方法的返回值是两端点结构体(Channel)的<code>Arc</code>引用,这将作为内核对象被内核中的其他数据结构引用。两个端点互相持有对方<code>Weak</code>指针,这是因为一个端点无需引用计数为0,只要<code>strong_count</code>为0就可以被清理掉,即使另一个端点指向它。</p>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p>实现 read, write 函数,read_write 单元测试</p>
|
|
|
|
|
<p>rust 语言并没有提供垃圾回收 (GC, Garbage Collection ) 的功能, 不过它提供了最简单的引用计数包装类型 <code>Rc</code>,这种引用计数功能也是早期 GC 常用的方法, 但是引用计数不能解决循环引用。那么如何 fix 这个循环引用呢?答案是 <code>Weak</code> 指针,只增加引用逻辑,不共享所有权,即不增加 strong reference count。由于 <code>Weak</code> 指针指向的对象可能析构了,所以不能直接解引用,要模式匹配,再 upgrade。</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
<p>下面我们来分析一下这个<code>unsafe</code>代码块:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">unsafe {
|
|
|
|
|
Arc::get_mut_unchecked(&mut channel0).peer = Arc::downgrade(&channel1);
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>由于两端的结构体将分别通过 <code>Arc</code> 引用,作为内核对象而被内核中的其他数据结构使用。因此,在同时初始化两端的同时,将必须对某一端的 Arc 指针进行获取可变引用的操作,即<code>get_mut_unchecked</code>接口。当 <code>Arc</code> 指针的引用计数不为 <code>1</code> 时,这一接口是非常不安全的,但是在当前情境下,我们使用这一接口进行<code>IPC</code> 对象的初始化,安全性是可以保证的。</p>
|
|
|
|
|
<h3 id="单元测试-2"><a class="header" href="#单元测试-2">单元测试</a></h3>
|
|
|
|
|
<p>下面我们写一个单元测试,来验证我们写的<code>create</code>方法:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">#[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);
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h3 id="实现数据传输"><a class="header" href="#实现数据传输">实现数据传输</a></h3>
|
|
|
|
|
<p>Channel中的数据传输,可以理解为<code>MessagePacket</code>在两个端点之间的传输,那么谁可以读写消息呢?</p>
|
|
|
|
|
<p>有一个句柄与通道端点相关联,持有该句柄的进程被视为所有者(owner)。所以是(持有与通道端点关联句柄的)进程可以读取或写入消息,或将通道端点发送到另一个进程。</p>
|
|
|
|
|
<p>当<code>MessagePacket</code>被写入通道时,它们会从发送进程中删除。当从通道读取<code>MessagePacket</code>时,<code>MessagePacket</code>的句柄被添加到接收进程中。</p>
|
|
|
|
|
<h4 id="read"><a class="header" href="#read">read</a></h4>
|
|
|
|
|
<p>获取当前端点的<code>recv_queue</code>,从队头中读取一条消息,如果能读取到消息,返回<code>Ok</code>,否则返回错误信息。</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">pub fn read(&self) -> ZxResult<T> {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h4 id="write"><a class="header" href="#write">write</a></h4>
|
|
|
|
|
<p>先获取当前端点对应的另一个端点的<code>Weak</code>指针,通过<code>upgrade</code>接口升级为<code>Arc</code>指针,从而获取到对应的结构体对象。在它的<code>recv_queue</code>队尾push一个<code>MessagePacket</code>。</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">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);
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h3 id="单元测试-3"><a class="header" href="#单元测试-3">单元测试</a></h3>
|
|
|
|
|
<p>下面我们写一个单元测试,验证我们上面写的<code>read</code>和<code>write</code>两个方法:</p>
|
|
|
|
|
<pre><code class="language-rust noplaypen">#[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));
|
|
|
|
|
}
|
|
|
|
|
</code></pre>
|
|
|
|
|
<h2 id="总结-2"><a class="header" href="#总结-2">总结</a></h2>
|
|
|
|
|
<p>在这一节中我们实现了唯一一个可以传递句柄的对象传输器——Channel,我们先了解的Zircon中主要的IPC内核对象,再介绍了Channel如何创建和实现read和write函数的细节。</p>
|
|
|
|
|
<p>本章我们学习了中最核心的几个内核对象,在下一章中,我们将学习<code>Zircon</code>的任务管理体系和进程、线程管理的对象。</p>
|
|
|
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="任务管理"><a class="header" href="#任务管理">任务管理</a></h1>
|
|
|
|
|
<p>本章我们来实现第一类内核对象:任务管理(Tasks)。</p>
|
|
|
|
|
<p>任务对象主要包括:线程 <code>Thread</code>,进程 <code>Process</code>,作业 <code>Job</code>。以及一些辅助性的对象,例如负责暂停任务执行的 <code>SuspendToken</code> 和负责处理异常的 <code>Exception</code>。</p>
|
|
|
|
|
@ -832,7 +1032,7 @@ impl Process {
|
|
|
|
|
<h2 id="作业job"><a class="header" href="#作业job">作业Job</a></h2>
|
|
|
|
|
<h3 id="概要-1"><a class="header" href="#概要-1">概要</a></h3>
|
|
|
|
|
<p>作业是一组进程,可能还包括其他(子)作业。作业用于跟踪执行内核操作的特权(即使用各种选项进行各种syscall),以及跟踪和限制基本资源(例如内存,CPU)的消耗。每个进程都属于一个作业。作业也可以嵌套,并且除根作业外的每个作业都属于一个(父)作业。</p>
|
|
|
|
|
<h3 id="描述-1"><a class="header" href="#描述-1">描述</a></h3>
|
|
|
|
|
<h3 id="描述"><a class="header" href="#描述">描述</a></h3>
|
|
|
|
|
<p>作业是包含以下内容的对象:</p>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>对父作业的引用</li>
|
|
|
|
|
@ -2130,7 +2330,7 @@ impl PageTableTrait for PageTable {
|
|
|
|
|
<p>进入用户态前,将内核栈指针保存在内核 glibc 的 TLS 区域中。为此我们需要查看 glibc 源码,找到一个空闲位置。</p>
|
|
|
|
|
<p>Linux 和 macOS 下如何分别通过系统调用设置 fsbase / gsbase</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
<h2 id="测试"><a class="header" href="#测试">测试</a></h2>
|
|
|
|
|
<h2 id="测试-1"><a class="header" href="#测试-1">测试</a></h2>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p>编写单元测试验证上述过程</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
|