From cf54a7b958e1c2d321e945370ca84755195d5816 Mon Sep 17 00:00:00 2001 From: DeathWish5 Date: Wed, 4 Aug 2021 22:44:13 +0800 Subject: [PATCH] ch02-03, basic code --- code/Cargo.toml | 1 + code/ch02-02/src/task/process.rs | 20 +- code/ch02-03/.gitignore | 2 + code/ch02-03/Cargo.toml | 6 + code/ch02-03/kernel-hal-unix/Cargo.toml | 13 + code/ch02-03/kernel-hal-unix/src/lib.rs | 46 ++ code/ch02-03/kernel-hal/Cargo.toml | 11 + code/ch02-03/kernel-hal/src/lib.rs | 35 ++ code/ch02-03/object/Cargo.toml | 19 + code/ch02-03/object/src/error.rs | 232 +++++++ code/ch02-03/object/src/ipc/channel.rs | 176 ++++++ code/ch02-03/object/src/ipc/mod.rs | 4 + code/ch02-03/object/src/lib.rs | 18 + code/ch02-03/object/src/object/handle.rs | 21 + code/ch02-03/object/src/object/mod.rs | 185 ++++++ code/ch02-03/object/src/object/rights.rs | 57 ++ code/ch02-03/object/src/task/job.rs | 409 +++++++++++++ code/ch02-03/object/src/task/job_policy.rs | 108 ++++ code/ch02-03/object/src/task/mod.rs | 24 + code/ch02-03/object/src/task/process.rs | 505 ++++++++++++++++ code/ch02-03/object/src/task/thread.rs | 569 ++++++++++++++++++ .../object/src/task/thread/thread_state.rs | 64 ++ code/ch02-03/object/src/vm/mod.rs | 6 + code/ch02-03/object/src/vm/vmar.rs | 17 + 24 files changed, 2545 insertions(+), 3 deletions(-) create mode 100644 code/ch02-03/.gitignore create mode 100644 code/ch02-03/Cargo.toml create mode 100644 code/ch02-03/kernel-hal-unix/Cargo.toml create mode 100644 code/ch02-03/kernel-hal-unix/src/lib.rs create mode 100644 code/ch02-03/kernel-hal/Cargo.toml create mode 100644 code/ch02-03/kernel-hal/src/lib.rs create mode 100644 code/ch02-03/object/Cargo.toml create mode 100644 code/ch02-03/object/src/error.rs create mode 100644 code/ch02-03/object/src/ipc/channel.rs create mode 100644 code/ch02-03/object/src/ipc/mod.rs create mode 100644 code/ch02-03/object/src/lib.rs create mode 100644 code/ch02-03/object/src/object/handle.rs create mode 100644 code/ch02-03/object/src/object/mod.rs create mode 100644 code/ch02-03/object/src/object/rights.rs create mode 100644 code/ch02-03/object/src/task/job.rs create mode 100644 code/ch02-03/object/src/task/job_policy.rs create mode 100644 code/ch02-03/object/src/task/mod.rs create mode 100644 code/ch02-03/object/src/task/process.rs create mode 100644 code/ch02-03/object/src/task/thread.rs create mode 100644 code/ch02-03/object/src/task/thread/thread_state.rs create mode 100644 code/ch02-03/object/src/vm/mod.rs create mode 100644 code/ch02-03/object/src/vm/vmar.rs diff --git a/code/Cargo.toml b/code/Cargo.toml index 20ef163..94ab6b8 100644 --- a/code/Cargo.toml +++ b/code/Cargo.toml @@ -4,4 +4,5 @@ members = [ "ch01-02", "ch01-03", "ch02-02", + "ch02-03", ] diff --git a/code/ch02-02/src/task/process.rs b/code/ch02-02/src/task/process.rs index 98d896e..cf63855 100644 --- a/code/ch02-02/src/task/process.rs +++ b/code/ch02-02/src/task/process.rs @@ -163,7 +163,15 @@ impl Process { /// The process finally terminates. fn terminate(&self) { - // unimplemented!() + let mut inner = self.inner.lock(); + let retcode = match inner.status { + Status::Exited(retcode) => retcode, + _ => { + inner.status = Status::Exited(0); + 0 + } + }; + self.job.remove_process(self.base.id); } /// Check whether `condition` is allowed in the parent job's policy. @@ -261,11 +269,17 @@ impl Task for Process { } fn suspend(&self) { - // unimplemented!() + // let inner = self.inner.lock(); + // for thread in inner.threads.iter() { + // thread.suspend(); + // } } fn resume(&self) { - // unimplemented!() + // let inner = self.inner.lock(); + // for thread in inner.threads.iter() { + // thread.resume(); + // } } } diff --git a/code/ch02-03/.gitignore b/code/ch02-03/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/code/ch02-03/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/code/ch02-03/Cargo.toml b/code/ch02-03/Cargo.toml new file mode 100644 index 0000000..e90dd79 --- /dev/null +++ b/code/ch02-03/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "object", + "kernel-hal-unix", + "kernel-hal", +] diff --git a/code/ch02-03/kernel-hal-unix/Cargo.toml b/code/ch02-03/kernel-hal-unix/Cargo.toml new file mode 100644 index 0000000..6987fb5 --- /dev/null +++ b/code/ch02-03/kernel-hal-unix/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "kernel-hal-unix" +version = "0.1.0" +authors = ["Runji Wang "] +edition = "2018" +description = "Kernel HAL implementation on Linux and macOS." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +kernel-hal = { path = "../kernel-hal" } +async-std = "1.9" +trapframe = "0.8.0" diff --git a/code/ch02-03/kernel-hal-unix/src/lib.rs b/code/ch02-03/kernel-hal-unix/src/lib.rs new file mode 100644 index 0000000..b1a57f8 --- /dev/null +++ b/code/ch02-03/kernel-hal-unix/src/lib.rs @@ -0,0 +1,46 @@ +#![feature(asm)] +#![feature(linkage)] +#![deny(warnings)] + +extern crate alloc; + +use { + alloc::boxed::Box, + core::time::Duration, + core::{future::Future, pin::Pin}, + std::time::SystemTime, +}; + +pub use trapframe::{GeneralRegs, UserContext}; + +#[repr(C)] +pub struct Thread { + thread: usize, +} + +impl Thread { + #[export_name = "hal_thread_spawn"] + pub fn spawn( + future: Pin + Send + 'static>>, + _vmtoken: usize, + ) -> Self { + async_std::task::spawn(future); + Thread { thread: 0 } + } +} + +/// Get current time. +#[export_name = "hal_timer_now"] +pub fn timer_now() -> Duration { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() +} + +/// Initialize the HAL. +/// +/// This function must be called at the beginning. +pub fn init() { + #[cfg(target_os = "macos")] + unimplemented!() +} diff --git a/code/ch02-03/kernel-hal/Cargo.toml b/code/ch02-03/kernel-hal/Cargo.toml new file mode 100644 index 0000000..54ca0dc --- /dev/null +++ b/code/ch02-03/kernel-hal/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "kernel-hal" +version = "0.1.0" +authors = ["Runji Wang "] +edition = "2018" +description = "Kernel HAL interface definations." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +trapframe = "0.8.0" diff --git a/code/ch02-03/kernel-hal/src/lib.rs b/code/ch02-03/kernel-hal/src/lib.rs new file mode 100644 index 0000000..0458898 --- /dev/null +++ b/code/ch02-03/kernel-hal/src/lib.rs @@ -0,0 +1,35 @@ +#![no_std] +#![feature(linkage)] +#![deny(warnings)] + +extern crate alloc; + +pub use trapframe::{GeneralRegs, UserContext}; + +use { + alloc::boxed::Box, + core::{future::Future, pin::Pin, time::Duration}, +}; + +#[repr(C)] +pub struct Thread { + id: usize, +} + +impl Thread { + /// Spawn a new thread. + #[linkage = "weak"] + #[export_name = "hal_thread_spawn"] + pub fn spawn( + _future: Pin + Send + 'static>>, + _vmtoken: usize, + ) -> Self { + unimplemented!() + } +} + +#[linkage = "weak"] +#[export_name = "hal_timer_now"] +pub fn timer_now() -> Duration { + unimplemented!() +} diff --git a/code/ch02-03/object/Cargo.toml b/code/ch02-03/object/Cargo.toml new file mode 100644 index 0000000..f8b4ec1 --- /dev/null +++ b/code/ch02-03/object/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ch02-03" +version = "0.1.0" +authors = ["Runji Wang "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +spin = "0.7" +downcast-rs = { version = "1.2.0", default-features = false } +bitflags = "1.2" +hashbrown = "0.9" +trapframe = "0.8.0" +futures = { version = "0.3", default-features = false, features = ["alloc", "async-await"] } +async-std = { version = "1.9", features = ["attributes", "unstable"] } +numeric-enum-macro = "0.2" +kernel-hal = { path = "../kernel-hal" } +kernel-hal-unix = { path = "../kernel-hal-unix" } \ No newline at end of file diff --git a/code/ch02-03/object/src/error.rs b/code/ch02-03/object/src/error.rs new file mode 100644 index 0000000..c6e1cbf --- /dev/null +++ b/code/ch02-03/object/src/error.rs @@ -0,0 +1,232 @@ +// ANCHOR: result +/// +pub type ZxResult = Result; +// ANCHOR_END: result + +// ANCHOR: error_begin +/// 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, Eq, PartialEq)] +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, + // ANCHOR_END: error_begin + /// The system was not able to allocate some resource + /// needed for the operation. + NO_RESOURCES = -3, + + /// The system was not able to allocate memory needed + /// for the operation. + NO_MEMORY = -4, + + // -5 used to be ZX_ERR_CALL_FAILED. + /// The system call was interrupted, but should be + /// retried. This should not be seen outside of the VDSO. + INTERNAL_INTR_RETRY = -6, + + // ======= Parameter errors ======= + /// an argument is invalid, ex. null pointer + INVALID_ARGS = -10, + + /// A specified handle value does not refer to a handle. + BAD_HANDLE = -11, + + /// The subject of the operation is the wrong type to + /// perform the operation. + /// Example: Attempting a message_read on a thread handle. + WRONG_TYPE = -12, + + /// The specified syscall number is invalid. + BAD_SYSCALL = -13, + + /// An argument is outside the valid range for this + /// operation. + OUT_OF_RANGE = -14, + + /// A caller provided buffer is too small for + /// this operation. + BUFFER_TOO_SMALL = -15, + + // ======= Precondition or state errors ======= + /// operation failed because the current state of the + /// object does not allow it, or a precondition of the operation is + /// not satisfied + BAD_STATE = -20, + + /// The time limit for the operation elapsed before + /// the operation completed. + TIMED_OUT = -21, + + /// The operation cannot be performed currently but + /// potentially could succeed if the caller waits for a prerequisite + /// to be satisfied, for example waiting for a handle to be readable + /// or writable. + /// Example: Attempting to read from a channel that has no + /// messages waiting but has an open remote will return ZX_ERR_SHOULD_WAIT. + /// Attempting to read from a channel that has no messages waiting + /// and has a closed remote end will return ZX_ERR_PEER_CLOSED. + SHOULD_WAIT = -22, + + /// The in-progress operation (e.g. a wait) has been + /// canceled. + CANCELED = -23, + + /// The operation failed because the remote end of the + /// subject of the operation was closed. + PEER_CLOSED = -24, + + /// The requested entity is not found. + NOT_FOUND = -25, + + /// An object with the specified identifier + /// already exists. + /// Example: Attempting to create a file when a file already exists + /// with that name. + ALREADY_EXISTS = -26, + + /// The operation failed because the named entity + /// is already owned or controlled by another entity. The operation + /// could succeed later if the current owner releases the entity. + ALREADY_BOUND = -27, + + /// The subject of the operation is currently unable + /// to perform the operation. + /// Note: This is used when there's no direct way for the caller to + /// observe when the subject will be able to perform the operation + /// and should thus retry. + UNAVAILABLE = -28, + + // ======= Permission check errors ======= + /// The caller did not have permission to perform + /// the specified operation. + ACCESS_DENIED = -30, + + // ======= Input-output errors ======= + /// Otherwise unspecified error occurred during I/O. + IO = -40, + + /// The entity the I/O operation is being performed on + /// rejected the operation. + /// Example: an I2C device NAK'ing a transaction or a disk controller + /// rejecting an invalid command, or a stalled USB endpoint. + IO_REFUSED = -41, + + /// The data in the operation failed an integrity + /// check and is possibly corrupted. + /// Example: CRC or Parity error. + IO_DATA_INTEGRITY = -42, + + /// The data in the operation is currently unavailable + /// and may be permanently lost. + /// Example: A disk block is irrecoverably damaged. + IO_DATA_LOSS = -43, + + /// The device is no longer available (has been + /// unplugged from the system, powered down, or the driver has been + /// unloaded, + IO_NOT_PRESENT = -44, + + /// More data was received from the device than expected. + /// Example: a USB "babble" error due to a device sending more data than + /// the host queued to receive. + IO_OVERRUN = -45, + + /// An operation did not complete within the required timeframe. + /// Example: A USB isochronous transfer that failed to complete due to an overrun or underrun. + IO_MISSED_DEADLINE = -46, + + /// The data in the operation is invalid parameter or is out of range. + /// Example: A USB transfer that failed to complete with TRB Error + IO_INVALID = -47, + + // ======== Filesystem Errors ======== + /// Path name is too long. + BAD_PATH = -50, + + /// Object is not a directory or does not support + /// directory operations. + /// Example: Attempted to open a file as a directory or + /// attempted to do directory operations on a file. + NOT_DIR = -51, + + /// Object is not a regular file. + NOT_FILE = -52, + + /// This operation would cause a file to exceed a + /// filesystem-specific size limit + FILE_BIG = -53, + + /// Filesystem or device space is exhausted. + NO_SPACE = -54, + + /// Directory is not empty. + NOT_EMPTY = -55, + + // ======== Flow Control ======== + // These are not errors, as such, and will never be returned + // by a syscall or public API. They exist to allow callbacks + // to request changes in operation. + /// Do not call again. + /// Example: A notification callback will be called on every + /// event until it returns something other than ZX_OK. + /// This status allows differentiation between "stop due to + /// an error" and "stop because the work is done." + STOP = -60, + + /// Advance to the next item. + /// Example: A notification callback will use this response + /// to indicate it did not "consume" an item passed to it, + /// but by choice, not due to an error condition. + NEXT = -61, + + /// Ownership of the item has moved to an asynchronous worker. + /// + /// Unlike ZX_ERR_STOP, which implies that iteration on an object + /// should stop, and ZX_ERR_NEXT, which implies that iteration + /// should continue to the next item, ZX_ERR_ASYNC implies + /// that an asynchronous worker is responsible for continuing iteration. + /// + /// Example: A notification callback will be called on every + /// event, but one event needs to handle some work asynchronously + /// before it can continue. ZX_ERR_ASYNC implies the worker is + /// responsible for resuming iteration once its work has completed. + ASYNC = -62, + + // ======== Network-related errors ======== + /// Specified protocol is not + /// supported. + PROTOCOL_NOT_SUPPORTED = -70, + + /// Host is unreachable. + ADDRESS_UNREACHABLE = -71, + + /// Address is being used by someone else. + ADDRESS_IN_USE = -72, + + /// Socket is not connected. + NOT_CONNECTED = -73, + + /// Remote peer rejected the connection. + CONNECTION_REFUSED = -74, + + /// Connection was reset. + CONNECTION_RESET = -75, + // ANCHOR: error_end + /// Connection was aborted. + CONNECTION_ABORTED = -76, +} +// ANCHOR_END: error_end diff --git a/code/ch02-03/object/src/ipc/channel.rs b/code/ch02-03/object/src/ipc/channel.rs new file mode 100644 index 0000000..f8f7d59 --- /dev/null +++ b/code/ch02-03/object/src/ipc/channel.rs @@ -0,0 +1,176 @@ +use { + super::*, + crate::error::*, + crate::object::*, + alloc::collections::VecDeque, + alloc::sync::{Arc, Weak}, + alloc::vec::Vec, + core::sync::atomic::{AtomicU32, Ordering}, + spin::Mutex, +}; + +pub struct Channel { + base: KObjectBase, + peer: Weak, + recv_queue: Mutex>, + next_txid: AtomicU32, +} + +type T = MessagePacket; +type TxID = u32; + +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) + } +); + +impl Channel { + /// Create a channel and return a pair of its endpoints + #[allow(unsafe_code)] + pub fn create() -> (Arc, Arc) { + let mut channel0 = Arc::new(Channel { + base: KObjectBase::default(), + peer: Weak::default(), + recv_queue: Default::default(), + next_txid: AtomicU32::new(0x8000_0000), + }); + let channel1 = Arc::new(Channel { + base: KObjectBase::default(), + peer: Arc::downgrade(&channel0), + recv_queue: Default::default(), + next_txid: AtomicU32::new(0x8000_0000), + }); + // no other reference of `channel0` + unsafe { + Arc::get_mut_unchecked(&mut channel0).peer = Arc::downgrade(&channel1); + } + (channel0, channel1) + } + + /// Read a packet from the channel if check is ok, otherwise the msg will keep. + pub fn read(&self) -> ZxResult { + let mut recv_queue = self.recv_queue.lock(); + if let Some(_) = 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 a packet to the channel + pub fn write(&self, msg: T) -> ZxResult { + let peer = self.peer.upgrade().ok_or(ZxError::PEER_CLOSED)?; + peer.push_general(msg); + Ok(()) + } + + /// Push a message to general queue, called from peer. + fn push_general(&self, msg: T) { + let mut send_queue = self.recv_queue.lock(); + send_queue.push_back(msg); + } + + /// Generate a new transaction ID for `call`. + fn new_txid(&self) -> TxID { + self.next_txid.fetch_add(1, Ordering::SeqCst) + } + + /// Is peer channel closed? + fn peer_closed(&self) -> bool { + self.peer.strong_count() == 0 + } +} + +/// The message transferred in the channel. +/// See [Channel](struct.Channel.html) for details. +#[derive(Default)] +pub struct MessagePacket { + /// The transition id of the message packet + pub txid: TxID, + /// The data carried by the message packet + pub data: Vec, + /// See [Channel](struct.Channel.html) for details. + pub handles: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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); + } + + #[test] + fn read_write() { + let (channel0, channel1) = Channel::create(); + // write a message to each other + let txid0 = channel0.new_txid(); + channel0 + .write(MessagePacket { + txid: txid0, + data: Vec::from("hello 1"), + handles: Vec::new(), + }) + .unwrap(); + let txid1 = channel1.new_txid(); + channel1 + .write(MessagePacket { + txid: txid1, + data: Vec::from("hello 0"), + handles: Vec::new(), + }) + .unwrap(); + + // read message should success + let recv_msg = channel1.read().unwrap(); + assert_eq!(recv_msg.txid, txid0); + 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.txid, txid1); + 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)); + } + + #[test] + fn peer_closed() { + let (channel0, channel1) = Channel::create(); + // write a message from peer, then drop it + channel1.write(MessagePacket::default()).unwrap(); + drop(channel1); + // read the first message should success. + channel0.read().unwrap(); + // read more message should fail. + assert_eq!(channel0.read().err(), Some(ZxError::PEER_CLOSED)); + // write message should fail. + assert_eq!( + channel0.write(MessagePacket::default()), + Err(ZxError::PEER_CLOSED) + ); + } +} diff --git a/code/ch02-03/object/src/ipc/mod.rs b/code/ch02-03/object/src/ipc/mod.rs new file mode 100644 index 0000000..c2632d8 --- /dev/null +++ b/code/ch02-03/object/src/ipc/mod.rs @@ -0,0 +1,4 @@ +use super::*; + +mod channel; +pub use self::channel::*; diff --git a/code/ch02-03/object/src/lib.rs b/code/ch02-03/object/src/lib.rs new file mode 100644 index 0000000..306d8b9 --- /dev/null +++ b/code/ch02-03/object/src/lib.rs @@ -0,0 +1,18 @@ +#![no_std] +#![deny(unused_imports)] +#![allow(dead_code)] +#![feature(get_mut_unchecked)] + +extern crate alloc; + +#[cfg(test)] +#[macro_use] +extern crate std; + +mod error; +mod ipc; +mod object; +mod task; +mod vm; + +pub use self::error::*; diff --git a/code/ch02-03/object/src/object/handle.rs b/code/ch02-03/object/src/object/handle.rs new file mode 100644 index 0000000..bc9b344 --- /dev/null +++ b/code/ch02-03/object/src/object/handle.rs @@ -0,0 +1,21 @@ +// ANCHOR: handle +use super::{KernelObject, Rights}; +use alloc::sync::Arc; + +pub type HandleValue = u32; +pub const INVALID_HANDLE: HandleValue = 0; + +/// 内核对象句柄 +#[derive(Clone)] +pub struct Handle { + pub object: Arc, + pub rights: Rights, +} + +impl Handle { + /// 创建一个新句柄 + pub fn new(object: Arc, rights: Rights) -> Self { + Handle { object, rights } + } +} +// ANCHOR_END: handle diff --git a/code/ch02-03/object/src/object/mod.rs b/code/ch02-03/object/src/object/mod.rs new file mode 100644 index 0000000..487bdf5 --- /dev/null +++ b/code/ch02-03/object/src/object/mod.rs @@ -0,0 +1,185 @@ +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 handle; +mod rights; + +pub use self::handle::*; +pub use self::rights::*; +pub use super::*; + +/// 内核对象公共接口 +pub trait KernelObject: DowncastSync + Debug { + /// 获取对象 ID + fn id(&self) -> KoID; + /// 获取对象类型名 + fn type_name(&self) -> &str; + /// 获取对象名称 + fn name(&self) -> String; + /// 设置对象名称 + fn set_name(&self, name: &str); + /// 尝试获取对象伙伴 + /// + /// 当前该对象必须是 `Channel` + fn peer(&self) -> ZxResult> { + Err(ZxError::NOT_SUPPORTED) + } + /// 尝试获取关联对象 id,否则返回 0 + /// + /// 当前该对象必须是 `Channel` 或者 `Task` + /// + /// 如果该对象是 `Channel`, 将获取伙伴的 id + /// + /// 如果该对象是 `Task`, 将获取其父 `Task` 的 id + fn related_koid(&self) -> KoID { + 0 + } + /// 尝试获取对应 id 的子对象 + /// + /// 当前该对象必须是 `Job` 或者 `Process`. + /// + /// 如果该对象是 `Job`,则其直属子 `Job` 以及 `Process` 必须被获取。 + fn get_child(&self, _id: KoID) -> ZxResult> { + Err(ZxError::WRONG_TYPE) + } +} + +impl_downcast!(sync KernelObject); + +/// 对象 ID 类型 +pub type KoID = u64; + +/// 内核对象核心结构 +pub struct KObjectBase { + /// 对象 ID + pub id: KoID, + inner: Mutex, +} + +/// `KObjectBase` 的内部可变部分 +#[derive(Default)] +struct KObjectBaseInner { + name: String, +} + +impl Default for KObjectBase { + /// 创建一个新 `KObjectBase` + fn default() -> Self { + KObjectBase { + id: Self::new_koid(), + inner: Default::default(), + } + } +} + +impl KObjectBase { + /// Create a new kernel object base. + pub fn new() -> Self { + Self::default() + } + + /// 生成一个唯一的 ID + fn new_koid() -> KoID { + static NEXT_KOID: AtomicU64 = AtomicU64::new(1024); + NEXT_KOID.fetch_add(1, Ordering::SeqCst) + } + /// 获取对象名称 + pub fn name(&self) -> String { + self.inner.lock().name.clone() + } + /// 设置对象名称 + pub fn set_name(&self, name: &str) { + self.inner.lock().name = String::from(name); + } + + /// Create a kernel object base with `name`. + pub fn with_name(name: &str) -> Self { + KObjectBase { + id: Self::new_koid(), + inner: Mutex::new(KObjectBaseInner { + name: String::from(name), + }), + } + } +} + +/// 为内核对象 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() + } + } + }; +} + +/// 空对象 +pub struct DummyObject { + // 其中必须包含一个名为 `base` 的 `KObjectBase` + base: KObjectBase, +} + +// 使用刚才的宏,声明其为内核对象,自动生成必要的代码 +impl_kobject!(DummyObject); + +impl DummyObject { + /// 创建一个新 `DummyObject` + pub fn new() -> Arc { + Arc::new(DummyObject { + base: KObjectBase::default(), + }) + } +} + +#[cfg(test)] +#[test] +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(); +} diff --git a/code/ch02-03/object/src/object/rights.rs b/code/ch02-03/object/src/object/rights.rs new file mode 100644 index 0000000..c69dcaf --- /dev/null +++ b/code/ch02-03/object/src/object/rights.rs @@ -0,0 +1,57 @@ +// ANCHOR: 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; + 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; + + /// GET_PROPERTY | SET_PROPERTY + const PROPERTY = Self::GET_PROPERTY.bits | Self::SET_PROPERTY.bits; + + /// GET_POLICY | SET_POLICY + const POLICY = Self::GET_POLICY.bits | Self::SET_POLICY.bits; + + /// BASIC & !Self::DUPLICATE | IO | SIGNAL | SIGNAL_PEER + const DEFAULT_CHANNEL = Self::BASIC.bits & !Self::DUPLICATE.bits | Self::IO.bits | Self::SIGNAL.bits | Self::SIGNAL_PEER.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; + + /// BASIC | IO | PROPERTY | DESTROY | SIGNAL | MANAGE_THREAD + const DEFAULT_THREAD = Self::BASIC.bits | Self::IO.bits | Self::PROPERTY.bits | Self::DESTROY.bits | Self::SIGNAL.bits | Self::MANAGE_THREAD.bits; + + /// BASIC | WAIT + const DEFAULT_VMAR = Self::BASIC.bits & !Self::WAIT.bits; + + /// BASIC | IO | PROPERTY | POLICY | ENUMERATE | DESTROY | SIGNAL | MANAGE_JOB | MANAGE_PROCESS | MANAGE_THREAD + const DEFAULT_JOB = Self::BASIC.bits | Self::IO.bits | Self::PROPERTY.bits | Self::POLICY.bits | Self::ENUMERATE.bits + | Self::DESTROY.bits | Self::SIGNAL.bits | Self::MANAGE_JOB.bits | Self::MANAGE_PROCESS.bits | Self::MANAGE_THREAD.bits; + + } +} +// ANCHOR_END: rights diff --git a/code/ch02-03/object/src/task/job.rs b/code/ch02-03/object/src/task/job.rs new file mode 100644 index 0000000..9e66dce --- /dev/null +++ b/code/ch02-03/object/src/task/job.rs @@ -0,0 +1,409 @@ +use { + super::job_policy::*, + super::process::Process, + super::*, + crate::error::*, + crate::object::*, + crate::task::Task, + alloc::sync::{Arc, Weak}, + alloc::vec::Vec, + spin::Mutex, +}; + +/// Job 对象 +#[allow(dead_code)] +pub struct Job { + base: KObjectBase, + parent: Option>, + parent_policy: JobPolicy, + inner: Mutex, +} + +impl_kobject!(Job + fn get_child(&self, id: KoID) -> ZxResult> { + let inner = self.inner.lock(); + if let Some(job) = inner.children.iter().filter_map(|o|o.upgrade()).find(|o| o.id() == id) { + return Ok(job); + } + if let Some(proc) = inner.processes.iter().find(|o| o.id() == id) { + return Ok(proc.clone()); + } + Err(ZxError::NOT_FOUND) + } + fn related_koid(&self) -> KoID { + self.parent.as_ref().map(|p| p.id()).unwrap_or(0) + } +); + +#[derive(Default)] +struct JobInner { + policy: JobPolicy, + children: Vec>, + processes: Vec>, + // if the job is killed, no more child creation should works + killed: bool, + self_ref: Weak, +} + +impl Job { + /// Create the root job. + pub fn root() -> Arc { + let job = Arc::new(Job { + base: KObjectBase::new(), + parent: None, + parent_policy: JobPolicy::default(), + inner: Mutex::new(JobInner::default()), + }); + job.inner.lock().self_ref = Arc::downgrade(&job); + job + } + + /// Create a new child job object. + pub fn create_child(self: &Arc) -> ZxResult> { + let mut inner = self.inner.lock(); + if inner.killed { + return Err(ZxError::BAD_STATE); + } + let child = Arc::new(Job { + base: KObjectBase::new(), + parent: Some(self.clone()), + parent_policy: inner.policy.merge(&self.parent_policy), + inner: Mutex::new(JobInner::default()), + }); + let child_weak = Arc::downgrade(&child); + child.inner.lock().self_ref = child_weak.clone(); + inner.children.push(child_weak); + Ok(child) + } + + fn remove_child(&self, to_remove: &Weak) { + let mut inner = self.inner.lock(); + inner.children.retain(|child| !to_remove.ptr_eq(child)); + if inner.killed && inner.processes.is_empty() && inner.children.is_empty() { + drop(inner); + self.terminate() + } + } + + /// Get the policy of the job. + pub fn policy(&self) -> JobPolicy { + self.inner.lock().policy.merge(&self.parent_policy) + } + + /// Get the parent job. + pub fn parent(&self) -> Option> { + self.parent.clone() + } + + /// Sets one or more security and/or resource policies to an empty job. + /// + /// The job's effective policies is the combination of the parent's + /// effective policies and the policies specified in policy. + /// + /// After this call succeeds any new child process or child job will have + /// the new effective policy applied to it. + pub fn set_policy_basic( + &self, + options: SetPolicyOptions, + policies: &[BasicPolicy], + ) -> ZxResult { + let mut inner = self.inner.lock(); + if !inner.is_empty() { + return Err(ZxError::BAD_STATE); + } + for policy in policies { + if self.parent_policy.get_action(policy.condition).is_some() { + match options { + SetPolicyOptions::Absolute => return Err(ZxError::ALREADY_EXISTS), + SetPolicyOptions::Relative => {} + } + } else { + inner.policy.apply(*policy); + } + } + Ok(()) + } + + /// Add a process to the job. + pub(super) fn add_process(&self, process: Arc) -> ZxResult { + let mut inner = self.inner.lock(); + if inner.killed { + return Err(ZxError::BAD_STATE); + } + inner.processes.push(process); + Ok(()) + } + + /// Remove a process from the job. + pub(super) fn remove_process(&self, id: KoID) { + let mut inner = self.inner.lock(); + inner.processes.retain(|proc| proc.id() != id); + if inner.killed && inner.processes.is_empty() && inner.children.is_empty() { + drop(inner); + self.terminate() + } + } + + /// Check whether this job is root job. + pub fn check_root_job(&self) -> ZxResult { + if self.parent.is_some() { + Err(ZxError::ACCESS_DENIED) + } else { + Ok(()) + } + } + + /// Get KoIDs of Processes. + pub fn process_ids(&self) -> Vec { + self.inner.lock().processes.iter().map(|p| p.id()).collect() + } + + /// Get KoIDs of children Jobs. + pub fn children_ids(&self) -> Vec { + self.inner + .lock() + .children + .iter() + .filter_map(|j| j.upgrade()) + .map(|j| j.id()) + .collect() + } + + /// Return true if this job has no processes and no child jobs. + pub fn is_empty(&self) -> bool { + self.inner.lock().is_empty() + } + + /// The job finally terminates. + fn terminate(&self) { + if let Some(parent) = self.parent.as_ref() { + parent.remove_child(&self.inner.lock().self_ref) + } + } +} + +impl Task for Job { + /// Kill the job. The job do not terminate immediately when killed. + /// It will terminate after all its children and processes are terminated. + fn kill(&self) { + let (children, processes) = { + let mut inner = self.inner.lock(); + if inner.killed { + return; + } + inner.killed = true; + (inner.children.clone(), inner.processes.clone()) + }; + if children.is_empty() && processes.is_empty() { + self.terminate(); + return; + } + for child in children { + if let Some(child) = child.upgrade() { + child.kill(); + } + } + for proc in processes { + proc.kill(); + } + } + + fn suspend(&self) { + panic!("job do not support suspend"); + } + + fn resume(&self) { + panic!("job do not support resume"); + } +} + +impl JobInner { + fn is_empty(&self) -> bool { + self.processes.is_empty() && self.children.is_empty() + } +} + +impl Drop for Job { + fn drop(&mut self) { + self.terminate(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::task::TASK_RETCODE_SYSCALL_KILL; + + #[test] + fn create() { + let root_job = Job::root(); + let job = Job::create_child(&root_job).expect("failed to create job"); + + let child = root_job + .get_child(job.id()) + .unwrap() + .downcast_arc() + .unwrap(); + assert!(Arc::ptr_eq(&child, &job)); + assert_eq!(job.related_koid(), root_job.id()); + assert_eq!(root_job.related_koid(), 0); + + root_job.kill(); + assert_eq!(root_job.create_child().err(), Some(ZxError::BAD_STATE)); + } + + #[test] + fn set_policy() { + let root_job = Job::root(); + + // default policy + assert_eq!( + root_job.policy().get_action(PolicyCondition::BadHandle), + None + ); + + // set policy for root job + let policy = &[BasicPolicy { + condition: PolicyCondition::BadHandle, + action: PolicyAction::Deny, + }]; + root_job + .set_policy_basic(SetPolicyOptions::Relative, policy) + .expect("failed to set policy"); + assert_eq!( + root_job.policy().get_action(PolicyCondition::BadHandle), + Some(PolicyAction::Deny) + ); + + // override policy should success + let policy = &[BasicPolicy { + condition: PolicyCondition::BadHandle, + action: PolicyAction::Allow, + }]; + root_job + .set_policy_basic(SetPolicyOptions::Relative, policy) + .expect("failed to set policy"); + assert_eq!( + root_job.policy().get_action(PolicyCondition::BadHandle), + Some(PolicyAction::Allow) + ); + + // create a child job + let job = Job::create_child(&root_job).expect("failed to create job"); + + // should inherit parent's policy. + assert_eq!( + job.policy().get_action(PolicyCondition::BadHandle), + Some(PolicyAction::Allow) + ); + + // setting policy for a non-empty job should fail. + assert_eq!( + root_job.set_policy_basic(SetPolicyOptions::Relative, &[]), + Err(ZxError::BAD_STATE) + ); + + // set new policy should success. + let policy = &[BasicPolicy { + condition: PolicyCondition::WrongObject, + action: PolicyAction::Allow, + }]; + job.set_policy_basic(SetPolicyOptions::Relative, policy) + .expect("failed to set policy"); + assert_eq!( + job.policy().get_action(PolicyCondition::WrongObject), + Some(PolicyAction::Allow) + ); + + // relatively setting existing policy should be ignored. + let policy = &[BasicPolicy { + condition: PolicyCondition::BadHandle, + action: PolicyAction::Deny, + }]; + job.set_policy_basic(SetPolicyOptions::Relative, policy) + .expect("failed to set policy"); + assert_eq!( + job.policy().get_action(PolicyCondition::BadHandle), + Some(PolicyAction::Allow) + ); + + // absolutely setting existing policy should fail. + assert_eq!( + job.set_policy_basic(SetPolicyOptions::Absolute, policy), + Err(ZxError::ALREADY_EXISTS) + ); + } + + #[test] + fn parent_child() { + let root_job = Job::root(); + let job = Job::create_child(&root_job).expect("failed to create job"); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + + assert_eq!(root_job.get_child(job.id()).unwrap().id(), job.id()); + assert_eq!(root_job.get_child(proc.id()).unwrap().id(), proc.id()); + assert_eq!( + root_job.get_child(root_job.id()).err(), + Some(ZxError::NOT_FOUND) + ); + assert!(Arc::ptr_eq(&job.parent().unwrap(), &root_job)); + + let job1 = root_job.create_child().expect("failed to create job"); + let proc1 = Process::create(&root_job, "proc1").expect("failed to create process"); + assert_eq!(root_job.children_ids(), vec![job.id(), job1.id()]); + assert_eq!(root_job.process_ids(), vec![proc.id(), proc1.id()]); + + root_job.kill(); + assert_eq!(root_job.create_child().err(), Some(ZxError::BAD_STATE)); + } + + #[test] + fn check() { + let root_job = Job::root(); + assert!(root_job.is_empty()); + let job = root_job.create_child().expect("failed to create job"); + assert_eq!(root_job.check_root_job(), Ok(())); + assert_eq!(job.check_root_job(), Err(ZxError::ACCESS_DENIED)); + + assert!(!root_job.is_empty()); + assert!(job.is_empty()); + + let _proc = Process::create(&job, "proc").expect("failed to create process"); + assert!(!job.is_empty()); + } + + #[test] + fn kill() { + let root_job = Job::root(); + let job = Job::create_child(&root_job).expect("failed to create job"); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + let current_thread = CurrentThread(thread.clone()); + + root_job.kill(); + assert!(root_job.inner.lock().killed); + assert!(job.inner.lock().killed); + assert_eq!(proc.status(), Status::Exited(TASK_RETCODE_SYSCALL_KILL)); + assert_eq!(thread.state(), ThreadState::Dying); + + std::mem::drop(current_thread); + assert!(root_job.inner.lock().killed); + assert!(job.inner.lock().killed); + assert_eq!(proc.status(), Status::Exited(TASK_RETCODE_SYSCALL_KILL)); + assert_eq!(thread.state(), ThreadState::Dead); + + // The job has no children. + let root_job = Job::root(); + root_job.kill(); + assert!(root_job.inner.lock().killed); + + // The job's process have no threads. + let root_job = Job::root(); + let job = Job::create_child(&root_job).expect("failed to create job"); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + root_job.kill(); + assert!(root_job.inner.lock().killed); + assert!(job.inner.lock().killed); + assert_eq!(proc.status(), Status::Exited(TASK_RETCODE_SYSCALL_KILL)); + } +} diff --git a/code/ch02-03/object/src/task/job_policy.rs b/code/ch02-03/object/src/task/job_policy.rs new file mode 100644 index 0000000..d7ff32d --- /dev/null +++ b/code/ch02-03/object/src/task/job_policy.rs @@ -0,0 +1,108 @@ +/// Security and resource policies of a job. +#[derive(Default, Copy, Clone)] +pub struct JobPolicy { + // TODO: use bitset + action: [Option; 15], +} + +impl JobPolicy { + /// Get the action of a policy `condition`. + pub fn get_action(&self, condition: PolicyCondition) -> Option { + self.action[condition as usize] + } + + /// Apply a basic policy. + pub fn apply(&mut self, policy: BasicPolicy) { + self.action[policy.condition as usize] = Some(policy.action); + } + + /// Merge the policy with `parent`'s. + pub fn merge(&self, parent: &Self) -> Self { + let mut new = *self; + for i in 0..15 { + if parent.action[i].is_some() { + new.action[i] = parent.action[i]; + } + } + new + } +} + +/// Control the effect in the case of conflict between +/// the existing policies and the new policies when setting new policies. +#[derive(Debug, Copy, Clone)] +pub enum SetPolicyOptions { + /// Policy is applied for all conditions in policy or the call fails. + Absolute, + /// Policy is applied for the conditions not specifically overridden by the parent policy. + Relative, +} + +/// The policy type. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct BasicPolicy { + /// Condition when the policy is applied. + pub condition: PolicyCondition, + /// + pub action: PolicyAction, +} + +/// The condition when a policy is applied. +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum PolicyCondition { + /// A process under this job is attempting to issue a syscall with an invalid handle. + /// In this case, `PolicyAction::Allow` and `PolicyAction::Deny` are equivalent: + /// if the syscall returns, it will always return the error ZX_ERR_BAD_HANDLE. + BadHandle = 0, + /// A process under this job is attempting to issue a syscall with a handle that does not support such operation. + WrongObject = 1, + /// A process under this job is attempting to map an address region with write-execute access. + VmarWx = 2, + /// A special condition that stands for all of the above ZX_NEW conditions + /// such as NEW_VMO, NEW_CHANNEL, NEW_EVENT, NEW_EVENTPAIR, NEW_PORT, NEW_SOCKET, NEW_FIFO, + /// And any future ZX_NEW policy. + /// This will include any new kernel objects which do not require a parent object for creation. + NewAny = 3, + /// A process under this job is attempting to create a new vm object. + NewVMO = 4, + /// A process under this job is attempting to create a new channel. + NewChannel = 5, + /// A process under this job is attempting to create a new event. + NewEvent = 6, + /// A process under this job is attempting to create a new event pair. + NewEventPair = 7, + /// A process under this job is attempting to create a new port. + NewPort = 8, + /// A process under this job is attempting to create a new socket. + NewSocket = 9, + /// A process under this job is attempting to create a new fifo. + NewFIFO = 10, + /// A process under this job is attempting to create a new timer. + NewTimer = 11, + /// A process under this job is attempting to create a new process. + NewProcess = 12, + /// A process under this job is attempting to create a new profile. + NewProfile = 13, + /// A process under this job is attempting to use zx_vmo_replace_as_executable() + /// with a ZX_HANDLE_INVALID as the second argument rather than a valid ZX_RSRC_KIND_VMEX. + AmbientMarkVMOExec = 14, +} + +/// The action taken when the condition happens specified by a policy. +#[repr(u32)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum PolicyAction { + /// Allow condition. + Allow = 0, + /// Prevent condition. + Deny = 1, + /// Generate an exception via the debug port. An exception generated this + /// way acts as a breakpoint. The thread may be resumed after the exception. + AllowException = 2, + /// Just like `AllowException`, but after resuming condition is denied. + DenyException = 3, + /// Terminate the process. + Kill = 4, +} diff --git a/code/ch02-03/object/src/task/mod.rs b/code/ch02-03/object/src/task/mod.rs new file mode 100644 index 0000000..afa11d3 --- /dev/null +++ b/code/ch02-03/object/src/task/mod.rs @@ -0,0 +1,24 @@ +use super::*; + +mod job; +mod job_policy; +mod process; +mod thread; + +pub use {self::job::*, self::job_policy::*, self::process::*, self::thread::*}; + +/// Task (Thread, Process, or Job) +pub trait Task: Sync + Send { + /// Kill the task. The task do not terminate immediately when killed. + /// It will terminate after all its children are terminated or some cleanups are finished. + fn kill(&self); + + /// Suspend the task. Currently only thread or process handles may be suspended. + fn suspend(&self); + + /// Resume the task + fn resume(&self); +} + +/// The return code set when a task is killed via zx_task_kill(). +pub const TASK_RETCODE_SYSCALL_KILL: i64 = -1028; diff --git a/code/ch02-03/object/src/task/process.rs b/code/ch02-03/object/src/task/process.rs new file mode 100644 index 0000000..59b4e45 --- /dev/null +++ b/code/ch02-03/object/src/task/process.rs @@ -0,0 +1,505 @@ +use { + super::{job::Job, job_policy::*, thread::*, *}, + crate::{error::*, object::*, vm::*}, + alloc::{sync::Arc, vec::Vec}, + hashbrown::HashMap, + spin::Mutex, +}; + +pub struct Process { + base: KObjectBase, + job: Arc, + policy: JobPolicy, + vmar: Arc, + inner: Mutex, +} + +impl_kobject!(Process + fn get_child(&self, id: KoID) -> ZxResult> { + let inner = self.inner.lock(); + let thread = inner.threads.iter().find(|o| o.id() == id).ok_or(ZxError::NOT_FOUND)?; + Ok(thread.clone()) + } + fn related_koid(&self) -> KoID { + self.job.id() + } +); + +#[derive(Default)] +struct ProcessInner { + max_handle_id: u32, + status: Status, + handles: HashMap, + threads: Vec>, +} + +/// Status of a process. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Status { + /// Initial state, no thread present in process. + Init, + /// First thread has started and is running. + Running, + /// Process has exited with the code. + Exited(i64), +} + +impl Default for Status { + fn default() -> Self { + Status::Init + } +} + +impl Process { + /// Create a new process in the `job`. + pub fn create(job: &Arc, name: &str) -> ZxResult> { + let proc = Arc::new(Process { + base: KObjectBase::with_name(name), + job: job.clone(), + policy: job.policy(), + vmar: VmAddressRegion::new_root(), + inner: Mutex::new(ProcessInner::default()), + }); + job.add_process(proc.clone())?; + Ok(proc) + } + + /// Get a handle from the process + fn get_handle(&self, handle_value: HandleValue) -> ZxResult { + self.inner.lock().get_handle(handle_value) + } + + /// 添加一个新的对象句柄 + pub fn add_handle(&self, handle: Handle) -> HandleValue { + self.inner.lock().add_handle(handle) + } + + /// 删除一个对象句柄 + pub fn remove_handle(&self, handle_value: HandleValue) -> ZxResult { + self.inner.lock().remove_handle(handle_value) + } + + /// Add all handles to the process + pub fn add_handles(&self, handles: Vec) -> Vec { + let mut inner = self.inner.lock(); + handles.into_iter().map(|h| inner.add_handle(h)).collect() + } + + /// Remove all handles from the process. + pub fn remove_handles(&self, handle_values: &[HandleValue]) -> ZxResult> { + let mut inner = self.inner.lock(); + handle_values + .iter() + .map(|h| inner.remove_handle(*h)) + .collect() + } + + /// Get the kernel object corresponding to this `handle_value` + pub fn get_object(&self, handle_value: HandleValue) -> ZxResult> { + let handle = self.get_handle(handle_value)?; + let object = handle + .object + .downcast_arc::() + .map_err(|_| ZxError::WRONG_TYPE)?; + Ok(object) + } + + /// 根据句柄值查找内核对象,并检查权限 + pub fn get_object_with_rights( + &self, + handle_value: HandleValue, + desired_rights: Rights, + ) -> ZxResult> { + let handle = self.get_handle(handle_value)?; + // 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) + } + + /// Get the kernel object corresponding to this `handle_value` and this handle's rights. + pub fn get_object_and_rights( + &self, + handle_value: HandleValue, + ) -> ZxResult<(Arc, Rights)> { + let handle = self.get_handle(handle_value)?; + let object = handle + .object + .downcast_arc::() + .map_err(|_| ZxError::WRONG_TYPE)?; + Ok((object, handle.rights)) + } + + /// Remove a handle referring to a kernel object of the given type from the process. + pub fn remove_object(&self, handle_value: HandleValue) -> ZxResult> { + let handle = self.remove_handle(handle_value)?; + let object = handle + .object + .downcast_arc::() + .map_err(|_| ZxError::WRONG_TYPE)?; + Ok(object) + } + + pub fn start( + &self, + thread: &Arc, + entry: usize, + stack: usize, + arg1: Option, + arg2: usize, + thread_fn: ThreadFn, + ) -> ZxResult { + let handle_value; + { + let mut inner = self.inner.lock(); + if !inner.contains_thread(thread) { + return Err(ZxError::ACCESS_DENIED); + } + if inner.status != Status::Init { + return Err(ZxError::BAD_STATE); + } + inner.status = Status::Running; + handle_value = arg1.map_or(INVALID_HANDLE, |handle| inner.add_handle(handle)); + } + thread.set_first_thread(); + match thread.start(entry, stack, handle_value as usize, arg2, thread_fn) { + Ok(_) => Ok(()), + Err(err) => { + let mut inner = self.inner.lock(); + if handle_value != INVALID_HANDLE { + inner.remove_handle(handle_value).ok(); + } + Err(err) + } + } + } + + /// Exit current process with `retcode`. + /// The process do not terminate immediately when exited. + /// It will terminate after all its child threads are terminated. + pub fn exit(&self, retcode: i64) { + let mut inner = self.inner.lock(); + if let Status::Exited(_) = inner.status { + return; + } + inner.status = Status::Exited(retcode); + if inner.threads.is_empty() { + inner.handles.clear(); + drop(inner); + self.terminate(); + return; + } + for thread in inner.threads.iter() { + thread.kill(); + } + inner.handles.clear(); + } + + /// The process finally terminates. + fn terminate(&self) { + let mut inner = self.inner.lock(); + let _retcode = match inner.status { + Status::Exited(retcode) => retcode, + _ => { + inner.status = Status::Exited(0); + 0 + } + }; + self.job.remove_process(self.base.id); + } + + /// Check whether `condition` is allowed in the parent job's policy. + pub fn check_policy(&self, condition: PolicyCondition) -> ZxResult { + match self + .policy + .get_action(condition) + .unwrap_or(PolicyAction::Allow) + { + PolicyAction::Allow => Ok(()), + PolicyAction::Deny => Err(ZxError::ACCESS_DENIED), + _ => unimplemented!(), + } + } + + /// Get process status. + pub fn status(&self) -> Status { + self.inner.lock().status + } + + /// Get the `VmAddressRegion` of the process. + pub fn vmar(&self) -> Arc { + self.vmar.clone() + } + + /// Get the job of the process. + pub fn job(&self) -> Arc { + self.job.clone() + } + + /// Add a thread to the process. + pub(super) fn add_thread(&self, thread: Arc) -> ZxResult { + let mut inner = self.inner.lock(); + if let Status::Exited(_) = inner.status { + return Err(ZxError::BAD_STATE); + } + inner.threads.push(thread); + Ok(()) + } + + /// Remove a thread from the process. + /// + /// If no more threads left, exit the process. + pub(super) fn remove_thread(&self, tid: KoID) { + let mut inner = self.inner.lock(); + inner.threads.retain(|t| t.id() != tid); + if inner.threads.is_empty() { + drop(inner); + self.terminate(); + } + } + + /// Get KoIDs of Threads. + pub fn thread_ids(&self) -> Vec { + self.inner.lock().threads.iter().map(|t| t.id()).collect() + } + + /// Get information of this process. + pub fn get_info(&self) -> ProcessInfo { + let mut info = ProcessInfo { + ..Default::default() + }; + match self.inner.lock().status { + Status::Init => { + info.started = false; + info.has_exited = false; + } + Status::Running => { + info.started = true; + info.has_exited = false; + } + Status::Exited(ret) => { + info.return_code = ret; + info.has_exited = true; + info.started = true; + } + } + info + } +} + +/// Information of a process. +#[allow(missing_docs)] +#[repr(C)] +#[derive(Default)] +pub struct ProcessInfo { + pub return_code: i64, + pub started: bool, + pub has_exited: bool, +} + +impl Task for Process { + fn kill(&self) { + self.exit(TASK_RETCODE_SYSCALL_KILL); + } + + fn suspend(&self) { + let inner = self.inner.lock(); + for thread in inner.threads.iter() { + thread.suspend(); + } + } + + fn resume(&self) { + let inner = self.inner.lock(); + for thread in inner.threads.iter() { + thread.resume(); + } + } +} + +impl ProcessInner { + /// Add a handle to the process + fn add_handle(&mut self, handle: Handle) -> HandleValue { + let key = (self.max_handle_id << 2) | 0x3u32; + self.max_handle_id += 1; + self.handles.insert(key, handle); + key + } + + fn remove_handle(&mut self, handle_value: HandleValue) -> ZxResult { + let handle = self + .handles + .remove(&handle_value) + .ok_or(ZxError::BAD_HANDLE)?; + Ok(handle) + } + + fn get_handle(&mut self, handle_value: HandleValue) -> ZxResult { + let handle = self.handles.get(&handle_value).ok_or(ZxError::BAD_HANDLE)?; + Ok(handle.clone()) + } + + /// Whether `thread` is in this process. + fn contains_thread(&self, thread: &Arc) -> bool { + self.threads.iter().any(|t| Arc::ptr_eq(t, thread)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + + assert_eq!(proc.related_koid(), root_job.id()); + assert!(Arc::ptr_eq(&root_job, &proc.job())); + } + + #[test] + fn handle() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let handle = Handle::new(proc.clone(), Rights::DEFAULT_PROCESS); + + let handle_value = proc.add_handle(handle); + + // getting object should success + let object: Arc = proc + .get_object_with_rights(handle_value, Rights::DEFAULT_PROCESS) + .expect("failed to get object"); + assert!(Arc::ptr_eq(&object, &proc)); + + let (object, rights) = proc + .get_object_and_rights::(handle_value) + .expect("failed to get object"); + assert!(Arc::ptr_eq(&object, &proc)); + assert_eq!(rights, Rights::DEFAULT_PROCESS); + + // getting object with an extra rights should fail. + assert_eq!( + proc.get_object_with_rights::(handle_value, Rights::MANAGE_JOB) + .err(), + Some(ZxError::ACCESS_DENIED) + ); + + // getting object with invalid type should fail. + assert_eq!( + proc.get_object_with_rights::(handle_value, Rights::DEFAULT_PROCESS) + .err(), + Some(ZxError::WRONG_TYPE) + ); + + proc.remove_handle(handle_value).unwrap(); + + // getting object with invalid handle should fail. + assert_eq!( + proc.get_object_with_rights::(handle_value, Rights::DEFAULT_PROCESS) + .err(), + Some(ZxError::BAD_HANDLE) + ); + + let handle1 = Handle::new(proc.clone(), Rights::DEFAULT_PROCESS); + let handle2 = Handle::new(proc.clone(), Rights::DEFAULT_PROCESS); + + let handle_values = proc.add_handles(vec![handle1, handle2]); + let object1: Arc = proc + .get_object_with_rights(handle_values[0], Rights::DEFAULT_PROCESS) + .expect("failed to get object"); + assert!(Arc::ptr_eq(&object1, &proc)); + + proc.remove_handles(&handle_values).unwrap(); + assert_eq!( + proc.get_object_with_rights::(handle_values[0], Rights::DEFAULT_PROCESS) + .err(), + Some(ZxError::BAD_HANDLE) + ); + } + + #[test] + fn get_child() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + + assert_eq!(proc.get_child(thread.id()).unwrap().id(), thread.id()); + assert_eq!(proc.get_child(proc.id()).err(), Some(ZxError::NOT_FOUND)); + + let thread1 = Thread::create(&proc, "thread1").expect("failed to create thread"); + assert_eq!(proc.thread_ids(), vec![thread.id(), thread1.id()]); + } + + #[test] + fn contains_thread() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + + let proc1 = Process::create(&root_job, "proc1").expect("failed to create process"); + let thread1 = Thread::create(&proc1, "thread1").expect("failed to create thread"); + + let inner = proc.inner.lock(); + assert!(inner.contains_thread(&thread) && !inner.contains_thread(&thread1)); + } + + #[test] + fn check_policy() { + let root_job = Job::root(); + let policy1 = BasicPolicy { + condition: PolicyCondition::BadHandle, + action: PolicyAction::Allow, + }; + let policy2 = BasicPolicy { + condition: PolicyCondition::NewChannel, + action: PolicyAction::Deny, + }; + + assert!(root_job + .set_policy_basic(SetPolicyOptions::Absolute, &[policy1, policy2]) + .is_ok()); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + + assert!(proc.check_policy(PolicyCondition::BadHandle).is_ok()); + assert!(proc.check_policy(PolicyCondition::NewProcess).is_ok()); + assert_eq!( + proc.check_policy(PolicyCondition::NewChannel).err(), + Some(ZxError::ACCESS_DENIED) + ); + + let _job = root_job.create_child().unwrap(); + assert_eq!( + root_job + .set_policy_basic(SetPolicyOptions::Absolute, &[policy1, policy2]) + .err(), + Some(ZxError::BAD_STATE) + ); + } + + #[test] + fn exit() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + + let info = proc.get_info(); + assert!(!info.has_exited && !info.started && info.return_code == 0); + + proc.exit(666); + let info = proc.get_info(); + assert!(info.has_exited && info.started && info.return_code == 666); + assert_eq!(thread.state(), ThreadState::Dying); + // TODO: when is the thread dead? + + assert_eq!( + Thread::create(&proc, "thread1").err(), + Some(ZxError::BAD_STATE) + ); + } +} diff --git a/code/ch02-03/object/src/task/thread.rs b/code/ch02-03/object/src/task/thread.rs new file mode 100644 index 0000000..20edb89 --- /dev/null +++ b/code/ch02-03/object/src/task/thread.rs @@ -0,0 +1,569 @@ +use { + super::process::Process, + super::*, + crate::object::*, + alloc::{boxed::Box, sync::Arc}, + bitflags::bitflags, + core::{ + future::Future, + ops::Deref, + pin::Pin, + task::{Context, Poll, Waker}, + }, + trapframe::{UserContext}, + spin::Mutex, +}; + +pub use self::thread_state::*; + +mod thread_state; + +pub struct Thread { + base: KObjectBase, + proc: Arc, + inner: Mutex, +} + +impl_kobject!(Thread + fn related_koid(&self) -> KoID { + self.proc.id() + } +); + +#[derive(Default)] +struct ThreadInner { + /// Thread context + /// + /// It will be taken away when running this thread. + context: Option>, + /// The number of existing `SuspendToken`. + suspend_count: usize, + /// The waker of task when suspending. + waker: Option, + /// Thread state + /// + /// NOTE: This variable will never be `Suspended`. On suspended, the + /// `suspend_count` is non-zero, and this represents the state before suspended. + state: ThreadState, + /// Should The ProcessStarting exception generated at start of this thread + first_thread: bool, + /// Should The ThreadExiting exception do not block this thread + killed: bool, + /// The time this thread has run on cpu + time: u128, + flags: ThreadFlag, +} + +impl ThreadInner { + fn state(&self) -> ThreadState { + // Dying > Exception > Suspend > Blocked + if self.suspend_count == 0 + || self.context.is_none() + || self.state == ThreadState::BlockedException + || self.state == ThreadState::Dying + || self.state == ThreadState::Dead + { + self.state + } else { + ThreadState::Suspended + } + } + + /// Change state and update signal. + fn change_state(&mut self, state: ThreadState) { + self.state = state; + } +} + +bitflags! { + /// Thread flags. + #[derive(Default)] + pub struct ThreadFlag: usize { + /// The thread currently has a VCPU. + const VCPU = 1 << 3; + } +} + +/// The type of a new thread function. +pub type ThreadFn = fn(thread: CurrentThread) -> Pin + Send + 'static>>; + +impl Thread { + /// Create a new thread. + pub fn create(proc: &Arc, name: &str) -> ZxResult> { + let thread = Arc::new(Thread { + base: KObjectBase::with_name(name), + proc: proc.clone(), + inner: Mutex::new(ThreadInner { + context: Some(Box::new(UserContext::default())), + ..Default::default() + }), + }); + proc.add_thread(thread.clone())?; + Ok(thread) + } + + /// Get the process. + pub fn proc(&self) -> &Arc { + &self.proc + } + + /// Start execution on the thread. + pub fn start( + self: &Arc, + entry: usize, + stack: usize, + arg1: usize, + arg2: usize, + thread_fn: ThreadFn, + ) -> ZxResult { + { + let mut inner = self.inner.lock(); + let context = inner.context.as_mut().ok_or(ZxError::BAD_STATE)?; + context.general.rip = entry; + context.general.rsp = stack; + context.general.rdi = arg1; + context.general.rsi = arg2; + context.general.rflags |= 0x3202; + inner.change_state(ThreadState::Running); + } + kernel_hal::Thread::spawn(thread_fn(CurrentThread(self.clone())), 0); + Ok(()) + } + + /// Stop the thread. Internal implementation of `exit` and `kill`. + /// + /// The thread do not terminate immediately when stopped. It is just made dying. + /// It will terminate after some cleanups (when `terminate` are called **explicitly** by upper layer). + fn stop(&self, killed: bool) { + let mut inner = self.inner.lock(); + if inner.state == ThreadState::Dead { + return; + } + if killed { + inner.killed = true; + } + if inner.state == ThreadState::Dying { + return; + } + inner.change_state(ThreadState::Dying); + if let Some(waker) = inner.waker.take() { + waker.wake(); + } + } + + /// Read one aspect of thread state. + pub fn read_state(&self, kind: ThreadStateKind, buf: &mut [u8]) -> ZxResult { + let inner = self.inner.lock(); + let state = inner.state(); + if state != ThreadState::BlockedException && state != ThreadState::Suspended { + return Err(ZxError::BAD_STATE); + } + let context = inner.context.as_ref().ok_or(ZxError::BAD_STATE)?; + context.read_state(kind, buf) + } + + /// Write one aspect of thread state. + pub fn write_state(&self, kind: ThreadStateKind, buf: &[u8]) -> ZxResult { + let mut inner = self.inner.lock(); + let state = inner.state(); + if state != ThreadState::BlockedException && state != ThreadState::Suspended { + return Err(ZxError::BAD_STATE); + } + let context = inner.context.as_mut().ok_or(ZxError::BAD_STATE)?; + context.write_state(kind, buf) + } + + /// Get the thread's information. + pub fn get_thread_info(&self) -> ThreadInfo { + let inner = self.inner.lock(); + ThreadInfo { + state: inner.state() as u32, + } + } + /// Get the thread state. + pub fn state(&self) -> ThreadState { + self.inner.lock().state() + } + + /// Add the parameter to the time this thread has run on cpu. + pub fn time_add(&self, time: u128) { + self.inner.lock().time += time; + } + + /// Get the time this thread has run on cpu. + pub fn get_time(&self) -> u64 { + self.inner.lock().time as u64 + } + + /// Set this thread as the first thread of a process. + pub(super) fn set_first_thread(&self) { + self.inner.lock().first_thread = true; + } + + /// Whether this thread is the first thread of a process. + pub fn is_first_thread(&self) -> bool { + self.inner.lock().first_thread + } + + /// Get the thread's flags. + pub fn flags(&self) -> ThreadFlag { + self.inner.lock().flags + } + + /// Apply `f` to the thread's flags. + pub fn update_flags(&self, f: impl FnOnce(&mut ThreadFlag)) { + f(&mut self.inner.lock().flags) + } + + /// Set the thread local fsbase register on x86_64. + pub fn set_fsbase(&self, fsbase: usize) -> ZxResult { + let mut inner = self.inner.lock(); + let context = inner.context.as_mut().ok_or(ZxError::BAD_STATE)?; + context.general.fsbase = fsbase; + Ok(()) + } + + /// Set the thread local gsbase register on x86_64. + pub fn set_gsbase(&self, gsbase: usize) -> ZxResult { + let mut inner = self.inner.lock(); + let context = inner.context.as_mut().ok_or(ZxError::BAD_STATE)?; + context.general.gsbase = gsbase; + Ok(()) + } +} + +impl Task for Thread { + fn kill(&self) { + self.stop(true) + } + + fn suspend(&self) { + let mut inner = self.inner.lock(); + inner.suspend_count += 1; + let state = inner.state; + inner.change_state(state); + } + + fn resume(&self) { + let mut inner = self.inner.lock(); + assert_ne!(inner.suspend_count, 0); + inner.suspend_count -= 1; + if inner.suspend_count == 0 { + let state = inner.state; + inner.change_state(state); + if let Some(waker) = inner.waker.take() { + waker.wake(); + } + } + } +} + + +/// A handle to current thread. +/// +/// This is a wrapper of [`Thread`] that provides additional methods for the thread runner. +/// It can only be obtained from the argument of `thread_fn` in a new thread started by [`Thread::start`]. +/// +/// It will terminate current thread on drop. +/// +/// [`Thread`]: crate::task::Thread +/// [`Thread::start`]: crate::task::Thread::start +pub struct CurrentThread(pub(super) Arc); + +impl Deref for CurrentThread { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for CurrentThread { + /// Terminate the current running thread. + fn drop(&mut self) { + let mut inner = self.inner.lock(); + inner.change_state(ThreadState::Dead); + self.proc().remove_thread(self.base.id); + } +} + +impl CurrentThread { + /// Exit the current thread. + /// + /// The thread do not terminate immediately when exited. It is just made dying. + /// It will terminate after some cleanups on this struct drop. + pub fn exit(&self) { + self.stop(false); + } + + /// Wait until the thread is ready to run (not suspended), + /// and then take away its context to run the thread. + pub fn wait_for_run(&self) -> impl Future> { + #[must_use = "wait_for_run does nothing unless polled/`await`-ed"] + struct RunnableChecker { + thread: Arc, + } + impl Future for RunnableChecker { + type Output = Box; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let mut inner = self.thread.inner.lock(); + if inner.state() != ThreadState::Suspended { + // resume: return the context token from thread object + // There is no need to call change_state here + // since take away the context of a non-suspended thread won't change it's state + Poll::Ready(inner.context.take().unwrap()) + } else { + // suspend: put waker into the thread object + inner.waker = Some(cx.waker().clone()); + Poll::Pending + } + } + } + RunnableChecker { + thread: self.0.clone(), + } + } + + /// The thread ends running and takes back the context. + pub fn end_running(&self, context: Box) { + let mut inner = self.inner.lock(); + inner.context = Some(context); + let state = inner.state; + inner.change_state(state); + } + + /// Access saved context of current thread. + /// + /// Will panic if the context is not availiable. + pub fn with_context(&self, f: F) -> T + where + F: FnOnce(&mut UserContext) -> T, + { + let mut inner = self.inner.lock(); + let mut cx = inner.context.as_mut().unwrap(); + f(&mut cx) + } +} + +/// The thread state. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum ThreadState { + /// The thread has been created but it has not started running yet. + New = 0, + /// The thread is running user code normally. + Running = 1, + /// Stopped due to `zx_task_suspend()`. + Suspended = 2, + /// In a syscall or handling an exception. + Blocked = 3, + /// The thread is in the process of being terminated, but it has not been stopped yet. + Dying = 4, + /// The thread has stopped running. + Dead = 5, + /// The thread is stopped in an exception. + BlockedException = 0x103, + /// The thread is stopped in `zx_nanosleep()`. + BlockedSleeping = 0x203, + /// The thread is stopped in `zx_futex_wait()`. + BlockedFutex = 0x303, + /// The thread is stopped in `zx_port_wait()`. + BlockedPort = 0x403, + /// The thread is stopped in `zx_channel_call()`. + BlockedChannel = 0x503, + /// The thread is stopped in `zx_object_wait_one()`. + BlockedWaitOne = 0x603, + /// The thread is stopped in `zx_object_wait_many()`. + BlockedWaitMany = 0x703, + /// The thread is stopped in `zx_interrupt_wait()`. + BlockedInterrupt = 0x803, + /// Pager. + BlockedPager = 0x903, +} + +impl Default for ThreadState { + fn default() -> Self { + ThreadState::New + } +} + +/// The thread information. +#[repr(C)] +pub struct ThreadInfo { + state: u32, +} + +#[cfg(test)] +mod tests { + use super::job::Job; + use super::*; + use kernel_hal::timer_now; + use kernel_hal::GeneralRegs; + use core::time::Duration; + + #[test] + fn create() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + assert_eq!(thread.flags(), ThreadFlag::empty()); + + assert_eq!(thread.related_koid(), proc.id()); + let child = proc.get_child(thread.id()).unwrap().downcast_arc().unwrap(); + assert!(Arc::ptr_eq(&child, &thread)); + } + + #[async_std::test] + async fn start() { + kernel_hal_unix::init(); + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + let thread1 = Thread::create(&proc, "thread1").expect("failed to create thread"); + + // function for new thread + async fn new_thread(thread: CurrentThread) { + let cx = thread.wait_for_run().await; + assert_eq!(cx.general.rip, 1); + assert_eq!(cx.general.rsp, 4); + assert_eq!(cx.general.rdi, 3); + assert_eq!(cx.general.rsi, 2); + async_std::task::sleep(Duration::from_millis(10)).await; + thread.end_running(cx); + } + + // start a new thread + let handle = Handle::new(proc.clone(), Rights::DEFAULT_PROCESS); + proc.start(&thread, 1, 4, Some(handle.clone()), 2, |thread| { + Box::pin(new_thread(thread)) + }) + .expect("failed to start thread"); + + // check info and state + let info = proc.get_info(); + assert!(info.started && !info.has_exited && info.return_code == 0); + assert_eq!(proc.status(), Status::Running); + assert_eq!(thread.state(), ThreadState::Running); + + // start again should fail + assert_eq!( + proc.start(&thread, 1, 4, Some(handle.clone()), 2, |thread| Box::pin( + new_thread(thread) + )), + Err(ZxError::BAD_STATE) + ); + + // start another thread should fail + assert_eq!( + proc.start(&thread1, 1, 4, Some(handle.clone()), 2, |thread| Box::pin( + new_thread(thread) + )), + Err(ZxError::BAD_STATE) + ); + + // wait 100ms for the new thread to exit + async_std::task::sleep(core::time::Duration::from_millis(100)).await; + + // no other references to `Thread` + assert_eq!(Arc::strong_count(&thread), 1); + assert_eq!(thread.state(), ThreadState::Dead); + } + + + #[test] + fn info() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + + let info = thread.get_thread_info(); + assert!(info.state == thread.state() as u32); + } + + #[test] + fn read_write_state() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + + const SIZE: usize = core::mem::size_of::(); + let mut buf = [0; 10]; + assert_eq!( + thread.read_state(ThreadStateKind::General, &mut buf).err(), + Some(ZxError::BAD_STATE) + ); + assert_eq!( + thread.write_state(ThreadStateKind::General, &buf).err(), + Some(ZxError::BAD_STATE) + ); + + thread.suspend(); + + assert_eq!( + thread.read_state(ThreadStateKind::General, &mut buf).err(), + Some(ZxError::BUFFER_TOO_SMALL) + ); + assert_eq!( + thread.write_state(ThreadStateKind::General, &buf).err(), + Some(ZxError::BUFFER_TOO_SMALL) + ); + + let mut buf = [0; SIZE]; + assert!(thread + .read_state(ThreadStateKind::General, &mut buf) + .is_ok()); + assert!(thread.write_state(ThreadStateKind::General, &buf).is_ok()); + // TODO + } + + #[async_std::test] + async fn wait_for_run() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + + assert_eq!(thread.state(), ThreadState::New); + + thread + .start(0, 0, 0, 0, |thread| Box::pin(new_thread(thread))) + .unwrap(); + async fn new_thread(thread: CurrentThread) { + assert_eq!(thread.state(), ThreadState::Running); + + // without suspend + let context = thread.wait_for_run().await; + thread.end_running(context); + + // with suspend + thread.suspend(); + thread.suspend(); + assert_eq!(thread.state(), ThreadState::Suspended); + async_std::task::spawn({ + let thread = (*thread).clone(); + async move { + async_std::task::sleep(Duration::from_millis(10)).await; + thread.resume(); + async_std::task::sleep(Duration::from_millis(10)).await; + thread.resume(); + } + }); + let time = timer_now(); + let _context = thread.wait_for_run().await; + assert!(timer_now() - time >= Duration::from_millis(20)); + } + // FIX ME: wait for thread end + // let thread: Arc = thread; + // thread.wait_signal(Signal::THREAD_TERMINATED).await; + } + + #[test] + fn time() { + let root_job = Job::root(); + let proc = Process::create(&root_job, "proc").expect("failed to create process"); + let thread = Thread::create(&proc, "thread").expect("failed to create thread"); + + assert_eq!(thread.get_time(), 0); + thread.time_add(10); + assert_eq!(thread.get_time(), 10); + } +} diff --git a/code/ch02-03/object/src/task/thread/thread_state.rs b/code/ch02-03/object/src/task/thread/thread_state.rs new file mode 100644 index 0000000..ceb9986 --- /dev/null +++ b/code/ch02-03/object/src/task/thread/thread_state.rs @@ -0,0 +1,64 @@ +use crate::{ZxError, ZxResult}; +use kernel_hal::UserContext; +use numeric_enum_macro::numeric_enum; + +numeric_enum! { + #[repr(u32)] + /// Possible values for "kind" in zx_thread_read_state and zx_thread_write_state. + #[allow(missing_docs)] + #[derive(Debug, Copy, Clone)] + pub enum ThreadStateKind { + General = 0, + FS = 6, + GS = 7, + } +} + +pub(super) trait ContextExt { + fn read_state(&self, kind: ThreadStateKind, buf: &mut [u8]) -> ZxResult; + fn write_state(&mut self, kind: ThreadStateKind, buf: &[u8]) -> ZxResult; +} + +impl ContextExt for UserContext { + fn read_state(&self, kind: ThreadStateKind, buf: &mut [u8]) -> ZxResult { + match kind { + ThreadStateKind::General => buf.write_struct(&self.general), + ThreadStateKind::FS => buf.write_struct(&self.general.fsbase), + ThreadStateKind::GS => buf.write_struct(&self.general.gsbase), + } + } + + fn write_state(&mut self, kind: ThreadStateKind, buf: &[u8]) -> ZxResult { + match kind { + ThreadStateKind::General => self.general = buf.read_struct()?, + ThreadStateKind::FS => self.general.fsbase = buf.read_struct()?, + ThreadStateKind::GS => self.general.gsbase = buf.read_struct()?, + } + Ok(()) + } +} + +trait BufExt { + fn read_struct(&self) -> ZxResult; + fn write_struct(&mut self, value: &T) -> ZxResult; +} + +#[allow(unsafe_code)] +impl BufExt for [u8] { + fn read_struct(&self) -> ZxResult { + if self.len() < core::mem::size_of::() { + return Err(ZxError::BUFFER_TOO_SMALL); + } + Ok(unsafe { (self.as_ptr() as *const T).read() }) + } + + fn write_struct(&mut self, value: &T) -> ZxResult { + if self.len() < core::mem::size_of::() { + return Err(ZxError::BUFFER_TOO_SMALL); + } + unsafe { + *(self.as_mut_ptr() as *mut T) = *value; + } + Ok(core::mem::size_of::()) + } +} diff --git a/code/ch02-03/object/src/vm/mod.rs b/code/ch02-03/object/src/vm/mod.rs new file mode 100644 index 0000000..4739f77 --- /dev/null +++ b/code/ch02-03/object/src/vm/mod.rs @@ -0,0 +1,6 @@ +//! Objects for Virtual Memory Management. +use super::*; + +mod vmar; + +pub use self::vmar::*; diff --git a/code/ch02-03/object/src/vm/vmar.rs b/code/ch02-03/object/src/vm/vmar.rs new file mode 100644 index 0000000..4a78f4f --- /dev/null +++ b/code/ch02-03/object/src/vm/vmar.rs @@ -0,0 +1,17 @@ +use {super::*, crate::object::*, alloc::sync::Arc}; + +/// Virtual Memory Address Regions +pub struct VmAddressRegion { + base: KObjectBase, +} + +impl_kobject!(VmAddressRegion); + +impl VmAddressRegion { + /// Create a new root VMAR. + pub fn new_root() -> Arc { + Arc::new(VmAddressRegion { + base: KObjectBase::new(), + }) + } +}