diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8b4a56e..e08ebf9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,10 +4,11 @@ on: push: branches: - master + - dev jobs: deploy: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Setup mdBook diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..000332a --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,74 @@ +name: CI + +on: + push: + branches: + - master + - dev + +jobs: + check: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-07-27 + override: true + components: rustfmt, clippy + - name: Check code format + run: | + cd code + cargo fmt --all -- --check +# uses: actions-rs/cargo@v1 +# with: +# command: fmt +# args: --all -- --check + - name: Clippy + run: | + cd code + cargo clippy +# uses: actions-rs/cargo@v1 +# with: +# command: clippy + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04, macos-latest] + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-07-27 + components: rust-src + - name: Build + run: | + cd code + cargo build +# uses: actions-rs/cargo@v1 +# with: +# command: build + + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04, macos-latest] + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-07-27 + components: rust-src + - name: Test + run: | + cd code + cargo test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2b92bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +## Editor +*.swp +*.swo +Session.vim +.cproject +.idea +*.iml +.vscode +.project +.favorites.json +.settings/ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +## File system +.DS_Store +desktop.ini diff --git a/README.md b/README.md index 05c2959..8fdc113 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,8 @@ cargo install mdbook mdbook serve docs ``` +## code +`code`目录下的`rust-toolchain`内容为`nightly-2021-07-27`。原则上,我们会采用`rustc`最新的版本。目前的版本信息如下: +``` +rustc 1.56.0-nightly (08095fc1f 2021-07-26) +``` diff --git a/code/.gitignore b/code/.gitignore index a9d37c5..64f40ab 100644 --- a/code/.gitignore +++ b/code/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +.idea diff --git a/code/ch01-02/src/object/handle.rs b/code/ch01-02/src/object/handle.rs index 614cd70..592282d 100644 --- a/code/ch01-02/src/object/handle.rs +++ b/code/ch01-02/src/object/handle.rs @@ -1,5 +1,5 @@ // ANCHOR: handle -use super::{KernelObject, Rights}; +use super::{DummyObject, KernelObject, Rights}; use alloc::sync::Arc; /// 内核对象句柄 @@ -16,3 +16,14 @@ impl Handle { } } // ANCHOR_END: handle + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_obj_handle() { + let obj = DummyObject::new(); + let handle1 = Handle::new(obj.clone(), Rights::BASIC); + } +} diff --git a/code/ch01-02/src/object/rights.rs b/code/ch01-02/src/object/rights.rs index 49c6842..974a2d2 100644 --- a/code/ch01-02/src/object/rights.rs +++ b/code/ch01-02/src/object/rights.rs @@ -30,6 +30,10 @@ bitflags! { 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; } } // ANCHOR_END: rights diff --git a/code/ch01-02/src/task/process.rs b/code/ch01-02/src/task/process.rs index 80988f5..f1adf1d 100644 --- a/code/ch01-02/src/task/process.rs +++ b/code/ch01-02/src/task/process.rs @@ -75,3 +75,45 @@ impl Process { } // ANCHOR_END: get_object_with_rights } + +#[cfg(test)] +mod tests { + use super::*; + use alloc::format; + + #[test] + fn new_proc() { + let proc = Process::new(); + assert_eq!(proc.type_name(), "Process"); + assert_eq!(proc.name(), ""); + proc.set_name("proc1"); + assert_eq!(proc.name(), "proc1"); + assert_eq!( + format!("{:?}", proc), + format!("Process({}, \"proc1\")", proc.id()) + ); + + let obj: Arc = proc; + assert_eq!(obj.type_name(), "Process"); + assert_eq!(obj.name(), "proc1"); + obj.set_name("proc2"); + assert_eq!(obj.name(), "proc2"); + assert_eq!( + format!("{:?}", obj), + format!("Process({}, \"proc2\")", obj.id()) + ); + } + + fn proc_handle() { + let proc = Process::new(); + let handle = Handle::new(proc.clone(), Rights::DEFAULT_PROCESS); + let handle_value = proc.add_handle(handle); + + let object1: Arc = proc + .get_object_with_rights(handle_value, Rights::DEFAULT_PROCESS) + .expect("failed to get object"); + assert!(Arc::ptr_eq(&object1, &proc)); + + proc.remove_handle(handle_value); + } +} diff --git a/code/ch01-03/src/object/handle.rs b/code/ch01-03/src/object/handle.rs index 614cd70..592282d 100644 --- a/code/ch01-03/src/object/handle.rs +++ b/code/ch01-03/src/object/handle.rs @@ -1,5 +1,5 @@ // ANCHOR: handle -use super::{KernelObject, Rights}; +use super::{DummyObject, KernelObject, Rights}; use alloc::sync::Arc; /// 内核对象句柄 @@ -16,3 +16,14 @@ impl Handle { } } // ANCHOR_END: handle + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_obj_handle() { + let obj = DummyObject::new(); + let handle1 = Handle::new(obj.clone(), Rights::BASIC); + } +} diff --git a/code/ch01-03/src/object/rights.rs b/code/ch01-03/src/object/rights.rs index 49c6842..974a2d2 100644 --- a/code/ch01-03/src/object/rights.rs +++ b/code/ch01-03/src/object/rights.rs @@ -30,6 +30,10 @@ bitflags! { 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; } } // ANCHOR_END: rights diff --git a/code/ch01-03/src/task/process.rs b/code/ch01-03/src/task/process.rs index 80988f5..f1adf1d 100644 --- a/code/ch01-03/src/task/process.rs +++ b/code/ch01-03/src/task/process.rs @@ -75,3 +75,45 @@ impl Process { } // ANCHOR_END: get_object_with_rights } + +#[cfg(test)] +mod tests { + use super::*; + use alloc::format; + + #[test] + fn new_proc() { + let proc = Process::new(); + assert_eq!(proc.type_name(), "Process"); + assert_eq!(proc.name(), ""); + proc.set_name("proc1"); + assert_eq!(proc.name(), "proc1"); + assert_eq!( + format!("{:?}", proc), + format!("Process({}, \"proc1\")", proc.id()) + ); + + let obj: Arc = proc; + assert_eq!(obj.type_name(), "Process"); + assert_eq!(obj.name(), "proc1"); + obj.set_name("proc2"); + assert_eq!(obj.name(), "proc2"); + assert_eq!( + format!("{:?}", obj), + format!("Process({}, \"proc2\")", obj.id()) + ); + } + + fn proc_handle() { + let proc = Process::new(); + let handle = Handle::new(proc.clone(), Rights::DEFAULT_PROCESS); + let handle_value = proc.add_handle(handle); + + let object1: Arc = proc + .get_object_with_rights(handle_value, Rights::DEFAULT_PROCESS) + .expect("failed to get object"); + assert!(Arc::ptr_eq(&object1, &proc)); + + proc.remove_handle(handle_value); + } +} diff --git a/code/rust-toolchain b/code/rust-toolchain index c3912e1..2c14cd6 100644 --- a/code/rust-toolchain +++ b/code/rust-toolchain @@ -1 +1 @@ -nightly-2021-03-01 \ No newline at end of file +nightly-2021-07-27 diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index c2f9a28..7b2de24 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -30,3 +30,6 @@ - [🚧 同时等待多个信号:Port 对象](ch05-02-port-object.md) - [🚧 实现更多:EventPair, Timer 对象](ch05-03-more-signal-objects.md) - [🚧 用户态同步互斥:Futex 对象](ch05-04-futex-object.md) + +- [硬件抽象层](ch06-00-hal.md) + - [✅ UNIX硬件抽象层](ch06-01-zcore-hal-unix.md) \ No newline at end of file diff --git a/docs/src/ch01-01-kernel-object.md b/docs/src/ch01-01-kernel-object.md index 927cbd4..c55ec83 100644 --- a/docs/src/ch01-01-kernel-object.md +++ b/docs/src/ch01-01-kernel-object.md @@ -1,4 +1,4 @@ -# 初识内核对象 +# 初识内核对象 ## 内核对象简介 @@ -14,7 +14,13 @@ ![](img/ch01-01-kernel-object.png) -简单来说: +这三个重要概念的定义如下: + +- **对象(Object)**: 具备属性和行为的客体。客体之间可有各种联系。从简单的**整数**到复杂的**操作系统进程**等都可看做对象,它不仅仅表示具体的事物,还能表示抽象的规则、计划或事件。 +- **句柄(Handle)**:标识对象的符号,也可看成是一种指向对象的变量(也可称为标识符、引用、ID等)。 +- **权限(Rights)**:是指对象的访问者被允许在对象上执行的操作,即对象的访问权限。当对象访问者打开对象的句柄,该句柄具有对其对象的访问权限的某种组合。 + +对于Zircon与对象、句柄、权限的关系,可简单地表述为: * Zircon是一个基于对象的内核,内核资源被抽象封装在不同的 **对象** 中。 * 用户程序通过 **句柄** 与内核交互。句柄是对某一对象的引用,并且附加了特定的 **权限**。 @@ -105,7 +111,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ## 实现 KernelObject 接口 -所有的内核对象有一系列共同的属性和方法,我们称这些方法为对象的公共**接口(Interface)**。 +所有的内核对象有一系列共同的属性和方法,我们称对象的方法为对象的公共**接口(Interface)**。 同一种方法在不同类型的对象中可能会有不同的行为,在面向对象语言中我们称其为**多态(Polymorphism)**。 Rust 是一门部分面向对象的语言,我们通常用它的 trait 实现接口和多态。 @@ -137,8 +143,8 @@ pub trait KernelObject: Send + Sync { {{#include ../../code/ch01-01/src/object/object_v1.rs:dummy_def}} ``` -这里我们采用一种[**内部可变性**]的设计模式:将对象的所有可变的部分封装到一个内部对象 `DummyObjectInner` 中,并在原对象中用自旋锁 [`Mutex`] 把它包起来,剩下的其它字段都是不可变的。 -`Mutex` 会用最简单的方式帮我们处理好并发访问问题:如果有其他人正在访问,我就在这里死等。 +为了有效地支持操作系统中的并行和并发处理,我们这里采用了一种[**内部可变性**]的设计模式:将对象的所有可变的部分封装到一个内部对象 `DummyObjectInner` 中,并在原对象中用可保证互斥访问的自旋锁 [`Mutex`] 把它包起来,剩下的其它字段都是不可变的。 +`Mutex` 会用最简单的方式帮我们处理好并发访问问题:如果有其他人正在访问,我就在这里忙等。 数据被 `Mutex` 包起来之后需要首先使用 [`lock()`] 拿到锁之后才能访问。此时并发访问已经安全,因此被包起来的结构自动具有了 `Send + Sync` 特性。 [`Mutex`]: https://docs.rs/spin/0.5.2/spin/struct.Mutex.html @@ -161,12 +167,14 @@ pub trait KernelObject: Send + Sync { {{#include ../../code/ch01-01/src/object/object_v1.rs:dummy_new}} ``` -根据文档描述,每个内核对象都有唯一的 ID。为此我们需要实现一个全局的 ID 分配方法。这里采用的方法是用一个静态变量存放下一个待分配 ID 值,每次分配就原子地 +1。 +根据文档描述,每个内核对象都有唯一的 ID。为此我们需要实现一个全局的 ID 分配方法。这里采用的方法是用一个静态变量存放下一个待分配 ID 值,每次分配就原子地 加`1`。 ID 类型使用 `u64`,保证了数值空间足够大,在有生之年都不用担心溢出问题。在 Zircon 中 ID 从 1024 开始分配,1024 以下保留作内核内部使用。 -另外注意这里 `new` 函数返回类型不是 `Self` 而是 `Arc`,这是为了以后方便而做的统一约定。 +另外注意这里 `new` 函数返回类型不是 `Self` 而是 `Arc`,这是的[ `Arc` ]为了以后方便并行处理而做的统一约定。 + +[ `Arc` ]: https://doc.rust-lang.org/std/sync/struct.Arc.html -最后我们为它实现 `KernelObject` 接口: +最后我们为它实现 `KernelObject` 的基本接口: ```rust,noplaypen // src/object/object.rs @@ -245,7 +253,7 @@ fn downcast_v2(object: Arc) -> Arc { (题外话:这个库原来是不支持 `no_std` 的,zCore 有这个需求,于是就顺便帮他实现了一把) -按照它文档的描述,我们要为自己的接口实现向下转换,只需以下修改: +按照[`downcast-rs`] 文档的描述,我们要为自己的接口实现向下转换,只需以下修改: ```rust,noplaypen // src/object/mod.rs diff --git a/docs/src/ch01-02-process-object.md b/docs/src/ch01-02-process-object.md index e4c7925..ecb805a 100644 --- a/docs/src/ch01-02-process-object.md +++ b/docs/src/ch01-02-process-object.md @@ -1,11 +1,31 @@ -# 对象管理器:Process 对象 +#### 对象管理器:Process 对象 -## 句柄和权限 +## 权限 -[句柄]: https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/handles.md [权限]: https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/rights.md -> 介绍并实现 Handle,Rights +内核对象的“[权限](https://fuchsia.dev/docs/concepts/kernel/rights)”指定允许对内核对象进行哪些操作。权限与句柄相关联,并传达对关联句柄或与句柄关联的对象执行操作的特权。单个进程可能对具有不同权限的同一个内核对象有两个不同的句柄。 + +## 句柄 + +[句柄]: https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/handles.md + + +句柄是允许用户程序引用内核对象引用的一种内核结构,它可以被认为是与特定内核对象的会话或连接。 + +通常情况下,多个进程通过不同的句柄同时访问同一个对象。对象可能有多个句柄(在一个或多个进程中)引用它们。但单个句柄只能绑定到单个进程或绑定到内核。 + +当句柄绑定到内核时,我们说它是“在传输中”('in-transit')。 + +在用户模式下,句柄只是某个系统调用返回的特定数字。只有“不在传输中”的句柄对用户模式可见。 + +代表句柄的整数只对其所属的那个进程有意义。另一个进程中的相同数字可能不会映射到任何句柄,或者它可能映射到指向完全不同的内核对象的句柄。 + +句柄的整数值是任何 32 位数字,但对应于**ZX_HANDLE_INVALID**的值将始终为 0。除此之外,有效句柄的整数值将始终具有句柄集的两个最低有效位. 可以使用**ZX_HANDLE_FIXED_BITS_MASK**访问代表这些位的掩码。 + +句柄可以从一个进程移动到另一个进程,方法是将它们写入通道(使用[`channel_write()`](https://fuchsia.dev/docs/reference/syscalls/channel_write)),或者使用 [`process_start()`](https://fuchsia.dev/docs/reference/syscalls/process_start)传递一个句柄作为新进程中第一个线程的参数。对于几乎所有的对象,当最后一个打开的引用对象的句柄关闭时,对象要么被销毁,要么被置于可能无法撤消的最终状态。 + + 在 `Cargo.toml` 中加入 `bitflags` 库: diff --git a/docs/src/ch01-03-channel-object.md b/docs/src/ch01-03-channel-object.md index b0586f1..6468b6e 100644 --- a/docs/src/ch01-03-channel-object.md +++ b/docs/src/ch01-03-channel-object.md @@ -1,8 +1,32 @@ # 对象传送器:Channel 对象 +## 概要 + +通道(Channel)是由一定数量的字节数据和一定数量的句柄组成的双向消息传输。 + +## 描述 + +通道有两个端点(endpoints)。从逻辑上讲,每个端点都维护要读取的有序消息队列。写入一个端点会将消息排入另一个端点的队列中。当端点的最后一个句柄关闭时,该端点队列中的未读消息将被销毁。因为销毁消息会关闭消息包含的所有句柄,关闭通道端点可能会产生递归效果(例如,通道包含一条消息,它包含一个通道,它包含一条消息,等等)。 + +关闭通道的最后一个句柄对先前写入该通道的消息的生命周期没有影响。这为通道提供了“即发即忘”的语义。 + +一条消息由一定数量的数据和一定数量的句柄组成。调用[`channel_write()`](https://fuchsia.dev/docs/reference/syscalls/channel_write)使一条消息入队,调用[`channel_read()`](https://fuchsia.dev/docs/reference/syscalls/channel_read) 使一条消息出列(如果有队列)。线程可以阻塞,直到消息通过[`object_wait_one()`](https://fuchsia.dev/docs/reference/syscalls/object_wait_one)或其他等待机制挂起。 + +或者,调用[`channel_call()`](https://fuchsia.dev/docs/reference/syscalls/channel_call)在通道的一个方向上将消息入队,等待相应的响应,然后将响应消息出队。在调用模式(call mode)下,相应的响应通过消息的前 4 个字节标识,称为事务 ID(transaction ID)。内核使用[`channel_call()`](https://fuchsia.dev/docs/reference/syscalls/channel_call),为消息提供唯一的事务 ID. + +通过通道发送消息的过程有两个步骤。第一步是原子地将数据写入通道并将消息中所有句柄的所有权移到此通道中。此操作始终消耗句柄:在调用结束时,所有句柄要么全部在通道中,要么全部丢弃。第二步操作,通道读取(channel read),与第一步类似:成功后,下一条消息中的所有句柄都被原子地移动到接收进程的句柄表中。失败时,通道将保留所有权,然后它们将被删除。 + +与许多其他内核对象类型不同,通道是不可复制的。因此,只有一个句柄与通道端点相关联,持有该句柄的进程被视为所有者(owner)。只有所有者可以读取或写入消息或将通道端点发送到另一个进程。 + +当通道端点的所有权从一个进程转移到另一个进程时,即使消息正在进行写入,消息也不会被重新排序或截断。转移事件之前的消息属于以前的所有者,转移之后的消息属于新的所有者。如果在传输端点时,正在进行消息读取,则之前描述的所有权转移方式同样适用。 + +即使最后剩余的句柄被剥夺了**DUPLICATE**权限,也不为其他内核对象提供上述顺序保证。 + ## 创建一对内核对象 +## 创建通道 +创建 Channel 将返回两个句柄,一个指向对象的每个端点。 > 实现 Channel::create > @@ -10,4 +34,6 @@ ## 实现数据传输 +当句柄被写入通道时,它们会从发送进程中删除。当从通道读取带有句柄的消息时,句柄被添加到接收进程中。在这两个事件之间,句柄继续存在(确保它们所指的对象继续存在),除非它们写入的通道的末端关闭——此时发送到该端点的消息被丢弃并且它们包含的任何句柄都已关闭。 + > 实现 read, write 函数,read_write 单元测试 diff --git a/docs/src/ch02-01-zircon-task.md b/docs/src/ch02-01-zircon-task.md index 173b95f..fd07172 100644 --- a/docs/src/ch02-01-zircon-task.md +++ b/docs/src/ch02-01-zircon-task.md @@ -1 +1,11 @@ # Zircon 任务管理体系 + +线程(Thread)表示包含进程(Proess)所拥有的地址空间中的多个执行控制流(CPU寄存器,堆栈等)。进程属于作业(Job),作业定义了各种资源限制。作业一直由父级作业(parent Jobs)拥有,一直到根作业(Root Job)为止,根作业是内核在启动时创建并传递给[`userboot`(第一个开始执行的用户进程)](https://fuchsia.dev/docs/concepts/booting/userboot)。 + +如果没有作业句柄(Job Handle),则进程中的线程无法创建另一个进程或另一个作业。 + +[程序加载](https://fuchsia.dev/docs/concepts/booting/program_loading)由内核层以上的用户空间工具和协议提供。 + +一些相关的系统调用: + + [`zx_process_create()`](https://fuchsia.dev/docs/reference/syscalls/process_create), [`zx_process_start()`](https://fuchsia.dev/docs/reference/syscalls/process_start), [`zx_thread_create()`](https://fuchsia.dev/docs/reference/syscalls/thread_create), [`zx_thread_start()`](https://fuchsia.dev/docs/reference/syscalls/thread_start) \ No newline at end of file diff --git a/docs/src/ch02-02-process-job-object.md b/docs/src/ch02-02-process-job-object.md index 37e23e5..74b1afd 100644 --- a/docs/src/ch02-02-process-job-object.md +++ b/docs/src/ch02-02-process-job-object.md @@ -1,11 +1,79 @@ # 进程管理:Process 与 Job 对象 -## Process 与 Job + > 介绍 Process 与 Job 的整体设计 > > 实现 Process 和 Job 对象的基本框架,支持树状结构 +> + +## 作业Job +### 概要 +作业是一组进程,可能还包括其他(子)作业。作业用于跟踪执行内核操作的特权(即使用各种选项进行各种syscall),以及跟踪和限制基本资源(例如内存,CPU)的消耗。每个进程都属于一个作业。作业也可以嵌套,并且除根作业外的每个作业都属于一个(父)作业。 +### 描述 + +作业是包含以下内容的对象: + +- 对父作业的引用 +- 一组子作业(每个子作业的父作业既是这个作业) +- 一组成员进程 +- 一套策略(Policy) + +由多个进程组成的“应用程序”可作为单个实体,被作业基于一套策略进行控制。 + +### 作业策略Job Policy + +[策略policy](https://fuchsia.dev/fuchsia-src/concepts/settings/policy/policy_concepts?hl=en) 可在Kernel运行时动态修改系统的各种配置(setting)。作业策略主要涉及作业安全性和资源使用的条件(Condition)限制。 + +#### 策略的行为PolicyAction + +策略的行为包括: + +- Allow 允许条件 +- Deny 拒绝条件 +- AllowException 通过 debugt port 生成异常,异常处理完毕后可恢复执行且运行条件 +- DenyException 通过 debugt port 生成异常,异常处理完毕后可恢复执行 +- Kill 杀死进程 + +#### 应用策略时的条件 PolicyCondition + +应用策略时的条件包括: + +- BadHandle: 此作业下的某个进程正在尝试发出带有无效句柄的syscall。在这种情况下,`PolicyAction::Allow`并且`PolicyAction::Deny`是等效的:如果syscall返回,它将始终返回错误ZX_ERR_BAD_HANDLE。 +- WrongObject:此作业下的某个进程正在尝试发出带有不支持该操作的句柄的syscall。 +- VmarWx:此作业下的进程正在尝试映射具有写执行访问权限的地址区域。 +- NewAny:代表上述所有ZX_NEW条件的特殊条件,例如NEW_VMO,NEW_CHANNEL,NEW_EVENT,NEW_EVENTPAIR,NEW_PORT,NEW_SOCKET,NEW_FIFO和任何将来的ZX_NEW策略。这将包括不需要父对象来创建的所有新内核对象。 +- NewVMO:此作业下的某个进程正在尝试创建新的vm对象。 +- NewChannel:此作业下的某个进程正在尝试创建新通道。 +- NewEvent:此作业下的一个进程正在尝试创建一个新事件。 +- NewEventPair:此作业下的某个进程正在尝试创建新的事件对。 +- NewPort:此作业下的进程正在尝试创建新端口。 +- NewSocket:此作业下的进程正在尝试创建新的套接字。 +- NewFIFO:此工作下的一个进程正在尝试创建一个新的FIFO。 +- NewTimer:此作业下的某个进程正在尝试创建新的计时器。 +- NewProcess:此作业下的进程正在尝试创建新进程。 +- NewProfile:此作业下的一个进程正在尝试创建新的配置文件。 +- AmbientMarkVMOExec:此作业下的某个进程正在尝试使用带有ZX_HANDLE_INVALID的zx_vmo_replace_as_executable()作为第二个参数,而不是有效的ZX_RSRC_KIND_VMEX。 + +## 进程Process + +进程是传统意义上程序的一个运行实例,包含一组指令和数据,这些指令将由一个或多个线程执行,并拥有一组资源。在具体实现上,进程包括如下内容: + +- Handles :大部分是进程用到的资源对象的句柄 +- Virtual Memory Address Regions:进程所在的内存地址空间 +- Threads:进程包含的线程组 + + + +进程包含在作业(Job)的管理范畴之中。从资源和权限限制以及生命周期控制的角度来看,允许将由多个进程组成的应用程序视为一个实体(即作业)。 + +### 生命周期(lifetime) +进程有自己的生命周期,从开始创建到直到被强制终止或程序退出为止。可通过调用`Process::create()`创建一个进程,并调用`Process::start()`开始执行 。该进程在以下情况下停止执行: + +- 最后一个线程终止或退出 +- 进程调用 `Process::exit()` +- 父作业(parent job)终止了该过程 +- 父作业(parent job)被销毁(destroied) + +注:`Process::start()`不能被调用两次。新线程不能被添加到已启动的进程。 -## Job Policy 策略 - -> 实现 JobPolicy diff --git a/docs/src/ch02-03-thread-object.md b/docs/src/ch02-03-thread-object.md index 8bc0ca3..f94438a 100644 --- a/docs/src/ch02-03-thread-object.md +++ b/docs/src/ch02-03-thread-object.md @@ -1,15 +1,80 @@ # 线程管理:Thread 对象 -## 线程状态 +线程对象是代表分时CPU的执行上下文的一种结构。线程对象与特定的进程对象相关联,该进程对象为线程对象执行中涉及的I/O和计算提供提供必要的内存和其他对象的句柄。 + + +## 生命期 + +线程是通过调用Thread::create()创建的,但只有在调用Thread::create()或Process::start()时才开始执行。这两个系统调用的参数都是要执行的初始例程的入口。 + +传递给Process::start()的线程应该是在一个进程上开始执行的第一个线程。 + + +下列情况都可导致一个线程终止执行: + +- 通过调用 `CurrentThread::exit()` +- 当父进程终止时 +- 通过调用 `Task::kill()` +- 在生成没有处理程序或处理程序决定终止线程的异常之后。 + +从入口例程返回并不终止执行。入口点的最后一个动作应该是调用CurrentThread::exit()。 + + + +关闭一个线程的最后一个句柄并不终止执行。为了强行杀死一个没有可用句柄的线程,可以使用KernelObject::get_child()来获得该线程的句柄。但这种方法是非常不可取的。杀死一个正在执行的线程可能会使进程处于损坏的状态。 + +本地线程总是分离的(*detached*)。也就是说,不需要join()操作来做一个干净的终止(clean termination)。但一些内核之上的运行系统,如C11或POSIX可能需要线程被连接(be joined)。 + + + +## 信号 + +线程提供以下信号: + +- THREAD_TERMINATED +- THREAD_SUSPENDED +- THREAD_RUNNING + +当一个线程启动执行时,THREAD_RUNNING被设定。当它被暂停时,THREAD_RUNNING被取消,THREAD_SUSPENDED被设定。当线程恢复时,THREAD_SUSPENDED被取消,THREAD_RUNNING被设定。当线程终止时,THREAD_RUNNING和THREAD_SUSPENDED都被置位,THREAD_TERMINATED也被置位。 + +注意,信号经过“或”运算后进入KernelObject::wait_signal()函数系列所保持的状态 ,因此当它们返回时,你可能会看到所要求的信号的任何组合。 + + + +## 线程状态(ThreadState) > 状态转移:创建 -> 运行 -> 暂停 -> 退出,最好有个状态机的图 > > 实现 ThreadState,最好能加一个单元测试来验证转移过程 +```rust +pub enum ThreadState { + New, \\该线程已经创建,但还没有开始运行 + Running, \\该线程正在正常运行用户代码 + Suspended, \\由于zx_task_suspend()而暂停 + Blocked, \\在一个系统调用中或处理一个异常而阻塞 + Dying, \\线程正在被终止的过程中,但还没有停止运行 + Dead, \\该线程已停止运行 + BlockedException, \\该线程在一个异常中被阻塞 + BlockedSleeping, \\该线程在zx_nanosleep()中被阻塞 + BlockedFutex, \\该线程在zx_futex_wait()中被阻塞 + BlockedPort, \\该线程在zx_port_wait()中被被阻塞 + BlockedChannel, \\该线程在zx_channel_call()中被阻塞 + BlockedWaitOne, \\该线程在zx_object_wait_one()中被阻塞 + BlockedWaitMany, \\该线程在zx_object_wait_many()中被阻塞 + BlockedInterrupt, \\该线程在zx_interrupt_wait()中被阻塞 + BlockedPager, \\被Pager阻塞 (目前没用到???) +} +``` + + + ## 线程寄存器上下文 > 定义 ThreadState,实现 read_state,write_state + + ## Async 运行时和 HAL 硬件抽象层 > 简单介绍 async-std 的异步机制 diff --git a/docs/src/ch03-02-vmo.md b/docs/src/ch03-02-vmo.md index a8ee1a7..9da0eae 100644 --- a/docs/src/ch03-02-vmo.md +++ b/docs/src/ch03-02-vmo.md @@ -4,6 +4,12 @@ > 根据文档梳理 VMO 的主要特性 +虚拟拟内存对象(Virtual Memory Objects, VMO)代表一组物理内存页面,或 潜在的页面(将根据需要延迟创建/填充)。 + +它们可以通过 [`zx_vmar_map()`](https://fuchsia.dev/docs/reference/syscalls/vmar_map)被映射到一个进程(Process)的地址空间,也可通过 [`zx_vmar_unmap()`](https://fuchsia.dev/docs/reference/syscalls/vmar_unmap)来解除映射。可以使用[`zx_vmar_protect()`](https://fuchsia.dev/docs/reference/syscalls/vmar_protect)来调整映射页面的权限。 + +也可以直接使用[`zx_vmo_read()`](https://fuchsia.dev/docs/reference/syscalls/vmo_read)来读取VMO和通过使用 [`zx_vmo_write()`](https://fuchsia.dev/docs/reference/syscalls/vmo_write)来写入 VMO。因此,通过诸如“创建 VMO,将数据集写入其中,然后将其交给另一个进程使用”等一次性(one-shot )操作,可以避免将它们映射到地址空间的开销。 + ## 实现 VMO 对象框架 > 实现 VmObject 结构,其中定义 VmObjectTrait 接口,并提供三个具体实现 Paged, Physical, Slice diff --git a/docs/src/ch03-04-vmar.md b/docs/src/ch03-04-vmar.md index 09e94a9..5cf4539 100644 --- a/docs/src/ch03-04-vmar.md +++ b/docs/src/ch03-04-vmar.md @@ -2,6 +2,8 @@ ## VMAR 简介 +虚拟内存地址区域(Virtual Memory Address Regions ,VMARs)为管理进程的地址空间提供了一种抽象。在进程创建时,将Root VMAR 的句柄提供给进程创建者。该句柄指的是跨越整个地址空间的 VMAR。这个空间可以通过[`zx_vmar_map()`](https://fuchsia.dev/docs/reference/syscalls/vmar_map)和 [`zx_vmar_allocate()`](https://fuchsia.dev/docs/reference/syscalls/vmar_allocate)接口来划分 。 [`zx_vmar_allocate()`](https://fuchsia.dev/docs/reference/syscalls/vmar_allocate)可用于生成新的 VMAR(称为子区域或子区域),可用于将地址空间的各个部分组合在一起。 + ## 实现 VMAR 对象框架 > 定义 VmAddressRange,VmMapping diff --git a/docs/src/ch05-00-signal-and-waiting.md b/docs/src/ch05-00-signal-and-waiting.md index 7cd30d0..00e5cff 100644 --- a/docs/src/ch05-00-signal-and-waiting.md +++ b/docs/src/ch05-00-signal-and-waiting.md @@ -1 +1,23 @@ # 信号和等待 + +## 信号 + +对象可能有多达 32 个信号(由 zx_signals *t 类型和 ZX* **SIGNAL** 定义表示),它们表示有关其当前状态的一条信息。例如,通道和套接字可能是 READABLE 或 WRITABLE 的。进程或线程可能会被终止。等等。 + +线程可以等待信号在一个或多个对象上变为活动状态。 + +## 等待 + +线程可用于[`zx_object_wait_one()`](https://fuchsia.dev/docs/reference/syscalls/object_wait_one) 等待单个句柄上的信号处于活动状态或 [`zx_object_wait_many()`](https://fuchsia.dev/docs/reference/syscalls/object_wait_many)等待多个句柄上的信号。两个调用都允许超时,即使没有信号挂起,它们也会返回。 + +超时可能会偏离指定的截止时间,具体取决于计时器的余量。 + +如果线程要等待大量句柄,使用端口(Port)会更有效,它是一个对象,其他对象可能会绑定到这样的对象,当信号在它们上被断言时,端口会收到一个包含信息的数据包关于未决信号。 + +## 事件与事件对 + +事件(Event)是最简单的对象,除了它的活动信号集合之外没有其他状态。 + +事件对(Event Pair)是可以相互发出信号的一对事件中的一个。事件对的一个有用属性是,当一对的一侧消失时(它的所有句柄都已关闭),PEER_CLOSED 信号在另一侧被断言。 + +见:[`zx_event_create()`](https://fuchsia.dev/docs/reference/syscalls/event_create), 和[`zx_eventpair_create()`](https://fuchsia.dev/docs/reference/syscalls/eventpair_create)。 \ No newline at end of file diff --git a/docs/src/ch05-04-futex-object.md b/docs/src/ch05-04-futex-object.md index 1fdbbac..76bd64e 100644 --- a/docs/src/ch05-04-futex-object.md +++ b/docs/src/ch05-04-futex-object.md @@ -6,6 +6,8 @@ > > 为什么快:利用共享内存中的原子变量,避免进入内核 +Futexes 是内核原语,与用户空间原子操作一起使用以实现高效的同步原语(如Mutexes, Condition Variables等),它只需要在竞争情况(contended case)下才进行系统调用。通常它们实现在标准库中。 + ## 实现基础元语:wait 和 wake > 实现 wait 和 wake 函数,并做单元测试 diff --git a/docs/src/ch06-00-hal.md b/docs/src/ch06-00-hal.md new file mode 100644 index 0000000..42ac729 --- /dev/null +++ b/docs/src/ch06-00-hal.md @@ -0,0 +1 @@ +# 硬件抽象层 diff --git a/docs/src/ch06-01-zcore-hal-unix.md b/docs/src/ch06-01-zcore-hal-unix.md new file mode 100644 index 0000000..916a69e --- /dev/null +++ b/docs/src/ch06-01-zcore-hal-unix.md @@ -0,0 +1,168 @@ +# zCore 的用户态运行支持 + +libos 版 zCore(简称uzCore) 的开发与裸机版 zCore (简称bzCore)同步进行,两个版本的 zCore 共用除了HAL 层之外的所有代码。为了支持 uzCore 的正常运行,zCore 在地址空间划分方面对 Zircon /Linux的原有设计进行了一定的修改,并为此对 Fuchsia 的源码进行了简单的修改、重新编译;另外,uzCore 需要的硬件相关层(HAL)将完全由宿主 OS 提供支持,一个合理的 HAL 层接口划分也是为支持 uzCore做出的重要考虑。 + +## HAL 层接口设计 + +HAL 层的设计是在bzCore 和 uzCore 的开发过程中逐渐演进形成的,在开发过程中将硬件实现相关的接口,比如页表、物理内存分配等进行封装,暴露给上层的内核对象层使用。在 kernel­-hal 模块中,给出空的弱链接实现,由 bzCore 或 uzCore 的开发者对相应的接口进行相应的实现,并用设定函数链接名称的方式,替换掉预设的弱链接的空函数。在整个开发过程中,不断对 HAL 层提出需求并实现,目前形成了第一版 HAL 层接口,在设计上能够满足现有的内核对象实现所需要的功能。 + +对内核对象层而言,所依赖的硬件环境不再是真实硬件环境中能够看到的物理内存、CPU、MMU 等,而是 HAL 暴露给上层的一整套接口。这一点从设计上来说,是 zCore 与 Zircon 存在差异的一点。Zircon 将 x86_64 、ARM64 的硬件架构进行底层封装,但是没有给出一套统一的硬件 API 供上层的内核对象直接使用,在部分内核对象的实现中,仍然需要通过宏等手段对代码进行条件编译,从而支持同时面向两套硬件架构进行开发。而在 zCore 的内核对象层实现中,可以完全不考虑底层硬件接口的实现,使一套内核对象的模块代码可以同时在 bzCore和 uzCore 上运行,之后如果 zCore 进一步支持 RISC-V 64 架构(已初步实现),只需要新增一套 HAL的实现,无需修改上层代码。下面将列出目前的uzCore的HAL层,即kernel-hal-unix的接口。 + + +### **HAL接口名称  功能描述** + +* 线程相关 + * hal_thread_spawn Thread::spawn创建一个新线程并加入调度 + * hal_thread_set_tid Thread::set_tid  设定当前线程的 id + * hal_thread_get_tid Thread::get_tid  获取当前线程的 id +* future + * yield_now暂时让出 CPU,回到async runtime中 + * sleep_until 休眠直到定时到达 + * YieldFuture 放弃执行的future + * SleepFuture 睡眠且等待被唤醒的future + * SerialFuture 通过serial_read获得字符的future +* 上下文切换相关 + * VectorRegs  x86相关 + * hal_context_run context_run 进入“用户态”运行 +* 用户指针相关 + * UserPtr  对用户指针的操作:读/写/解引用/访问数组/访问字符串 + * IoVec 非连续buffer集合(Vec结构):读/写 +* 页表相关 + * hal_pt_currentPageTable::current  获取当前页表 + * hal_pt_newPageTable::new  新建一个页表 + * hal_pt_map PageTable::map  将一个物理页帧映射到一个虚拟地址中 + * hal_pt_unmap PageTable::unmap  解映射某个虚拟地址 + * hal_pt_protect PageTable::protect 修改vaddr对应的页表项的flags + * hal_pt_query PageTable::query  查询某个虚拟地址对应的页表项状态 + * hal_pt_table_phys PageTable::table_phys  获取对应页表的根目录表物理地址 + * hal_pt_activate PageTable::activate 激活当前页表 + * PageTable::map_many  同时映射多个物理页帧到连续虚拟内存空间 + * PageTable::map_cont  同时映射连续的多个物理页帧到虚拟内存空间 + * hal_pt_unmap_cont PageTable::unmap_cont  解映射某个虚拟地址开始的一片范围 + * MMUFlags  页表项的属性位 +* 物理页帧相关 + * hal_frame_alloc PhysFrame::alloc  分配一个物理页帧 + * hal_frame_alloc_contiguous PhysFrame::alloc_contiguous_base 分配一块连续的物理内存 + * PhysFrame::addr 返回物理页帧对应的物理地址 + * PhysFrame::alloc_contiguous  分配一块连续的物理内存 + * PhysFrame::zero_frame_addr  返回零页的物理地址(一个特殊页,内容永远为全0) + * PhysFrame::drop  Drop trait 回收该物理页帧 + * hal_pmem_read pmem_read  读取某特定物理页帧的内容到缓冲区 + * hal_pmem_write pmem_write  将缓冲区中的内容写入某特定物理页帧 + * hal_frame_copy frame_copy  复制物理页帧的内容 + * hal_frame_zero frame_zero_in_range 物理页帧清零 + * hal_frame_flush frame_flush将物理页帧的数据从 Cache 刷回内存 +* 基本I/O外设 + * hal_serial_read serial_read 字符串输入 + * hal_serial_write serial_write 字符串输出 + * hal_timer_now timer_now 获取当前时间 + * hal_timer_set timer_set 设置一个时钟,当到达deadline时,会调用 callback 函数 + * hal_timer_set_next timer_set_next 设置下一个时钟 + * hal_timer_tick timer_tick当时钟中断产生时会调用的时钟函数,触发所有已到时间的 callback +* 中断处理 + * hal_irq_handle handle 中断处理例程 + * hal_ioapic_set_handle set_ioapic_handle x86相关,对高级中断控制器设置处理例程 + * hal_irq_add_handle add_handle 对某中断添加中断处理例程 + * hal_ioapic_reset_handle reset_ioapic_handle 重置级中断控制器并设置处理例程 + * hal_irq_remove_handle remove_handle 移除某中断的中断处理例程 + * hal_irq_allocate_block allocate_block 给某中断分配连续区域 + * hal_irq_free_block free_block 给某中断释放连续区域 + * hal_irq_overwrite_handler overwrite_handler 覆盖某中断的中断处理例程 + * hal_irq_enable enable 使能某中断 + * hal_irq_disable disable 屏蔽某中断 + * hal_irq_maxinstr maxinstr x86相关,获得IOAPIC的maxinstr??? + * hal_irq_configure configure 对某中断进行配置??? + * hal_irq_isvalid is_valid 查询某中断是否有效 +* 硬件平台相关 + * hal_vdso_constants vdso_constants 得到平台相关常量参数 + * struct VdsoConstants  平台相关常量: + +max_num_cpus features dcache_line_size ticks_per_second  ticks_to_mono_numerator ticks_to_mono_denominator physmem version_string_len  version_string + + * fetch_fault_vaddr fetch_fault_vaddr 取得出错的地址 ???好像缺了hal_* + * fetch_trap_num fetch_trap_num 取得中断号 + * hal_pc_firmware_tables pc_firmware_tables x86相关,取得`acpi_rsdp` 和 `smbios` 的物理地址 + * hal_acpi_table get_acpi_table 得到acpi table + * hal_outpd outpd x86相关,对IO Port进行写访问 + * hal_inpd inpd x86相关,对IO Port进行读访问 + * hal_apic_local_id apic_local_id 得到本地(local) APIC ID + * fill_random 产生随机数,并写入到buffer中 + +在上述“线程相关”的列表中,列出了 HAL 层的部分接口设计,覆盖线程调度方面。在线程调度方面,Thread 结构体相关的接口主要用于将一个线程加入调度等基本操作。在 zCore 的相关实现中,线程调度的各接口使用 naive­-executor 给出的接口以及 trapframe­ 给出的接口来进行实现,二者都是我们为裸机环境的协程调度与上下文切换所封装的 Rust 库。uzCore 中,线程调度的相关接口依赖于 Rust 的用户态协程支持以及 uzCore 开发者实现的用户态上下文切换。 + +在内存管理方面,HAL 层将内存管理分为页表操作与物理页帧管理两方面,并以此设计接口。在 zCore 实现中,物理页帧的分配与回收由于需要设计物理页帧分配器,且可分配范围大小与内核刚启动时的内存探测密切相关,我们将其直接在总控模块 zCore 中进行实现。而在 uzCore 中,页表对应操作依赖 mmap 进行模拟,物理页帧的相关操作则直接使用用户态物理内存分配器进行模拟。 + +在 Zircon 的设计中,内存的初始状态应该设置为全 0,为了在内核对象层满足该要求,我们为 HAL 层设计了零页接口,要求 HAL 层保留一个内容为全 0 的物理页帧,供上层使用。上层负责保证该零页内容不被修改。 + + +## 修改 VDSO + +VDSO 是由内核提供、并只读映射到用户态的动态链接库,以函数接口形式提供系统调用接口。原始的 VDSO 中将会最终使用 syscall 指令从用户态进入内核态。但在 uzCore 环境下,内核和用户程序都运行在用户态,因此需要将 syscall 指令修改为函数调用,也就是将 sysall 指令修改为 call 指令。为此我们修改了 VDSO 汇编代码,将其中的 syscall 替换为 call,提供给 uzCore 使用。在 uzCore 内核初始化环节中,向其中填入 call 指令要跳转的目标地址,重定向到内核中处理 syscall 的特定函数,从而实现模拟系统调用的效果。 + + + + + +## 调整地址空间范围 + +在 uzCore 中,使用 mmap 来模拟页表,所有进程共用一个 64 位地址空间。因此,从地址空间范围这一角度来说,运行在 uzCore 上的用户程序所在的用户进程地址空间无法像 Zircon 要求的一样大。对于这一点,我们在为每一个用户进程设置地址空间时,手动进行分配,规定每一个用户进程地址空间的大小为 0x100_0000_0000,从 0x2_0000_0000 开始依次排布。0x0 开始至 0x2_0000_0000 规定为 uzCore 内核所在地址空间,不用于 mmap。图 3.3给出了 uzCore 在运行时若干个用户进程的地址空间分布。 + +与 uzCore 兼容,zCore 对于用户进程的地址空间划分也遵循同样的设计,但在裸机环境下,一定程度上摆脱了限制,能够将不同用户地址空间分隔在不同的页表中。如图 3.4所示,zCore 中将三个用户进程的地址空间在不同的页表中映射,但是为了兼容 uzCore 的运行,每一个用户进程地址空间中用户程序能够真正访问到的部分都仅有 0x100_0000_0000 大小。 + + +## LibOS源代码分析记录 + +### zCore on riscv64的LibOS支持 + +* LibOS unix模式的入口在linux-loader main.rs:main() + +初始化包括kernel_hal_unix,Host文件系统,其中载入elf应用程序的过程与zcore bare模式一样; + +重点工作应该在kernel_hal_unix中的**内核态与用户态互相切换**的处理。 + +kernel_hal_unix初始化主要包括了,构建Segmentation Fault时SIGSEGV信号的处理函数,当代码尝试使用fs寄存器时会触发信号; + +* 为什么要注册这个信号处理函数呢? + +根据wrj的说明:由于 macOS 用户程序无法修改 fs 寄存器,当运行相关指令时会访问非法内存地址触发Segmentation Fault。故实现段错误信号处理函数,并在其中动态修改用户程序指令,将 fs 改为 gs + +kernel_hal_unix还构造了**进入用户态**所需的run_fncall() -> syscall_fn_return(); + +而用户程序需要调用syscall_fn_entry()来**返回内核态**; + +Linux-x86_64平台运行时,用户态和内核态之间的切换运用了 fs base 寄存器; + +* Linux 和 macOS 下如何分别通过系统调用设置 fsbase / gsbase 。 + +这个转换过程调用到了trapframe库,x86_64和aarch64有对应实现,而riscv则需要自己手动实现; + +* 关于fs寄存器 + +查找了下,fs寄存器一般会用于寻址TLS,每个线程有它自己的fs base地址; + +fs寄存器被glibc定义为存放tls信息,结构体tcbhead_t就是用来描述tls; + +进入用户态前,将内核栈指针保存在内核 glibc 的 TLS 区域中。 + +可参考一个运行时程序的代码转换工具:[https://github.com/DynamoRIO/dynamorio/issues/1568#issuecomment-239819506](https://github.com/DynamoRIO/dynamorio/issues/1568#issuecomment-239819506?fileGuid=VMAPV7ERl7HbpNqg) + +* **LibOS内核态与用户态的切换** + +Linux x86_64中,fs寄存器是用户态程序无法设置的,只能通过系统调用进行设置; + +例如clone系统调用,通过arch_prctl来设置fs寄存器;指向的struct pthread,glibc中,其中的首个结构是tcbhead_t + +计算tls结构体偏移: + +经过试验,x86_64平台,int型:4节,指针类型:8节,无符号长整型:8节; + +riscv64平台,int型: 4节,指针类型:8节,无符号长整型:8节; + +计算tls偏移量时,注意下,在musl中,aarch64和riscv64架构有#define TLS_ABOVE_TP,而x86_64无此定义 + +* 关于Linux user mode (UML) + +"No, UML works only on x86 and x86_64." + +[https://sourceforge.net/p/user-mode-linux/mailman/message/32782012/](https://sourceforge.net/p/user-mode-linux/mailman/message/32782012/?fileGuid=VMAPV7ERl7HbpNqg) +