From 7c2aa2f91850ccb5e10661acbf451659104705e0 Mon Sep 17 00:00:00 2001 From: yck <2998770265@qq.com> Date: Mon, 2 Dec 2024 02:12:27 +0800 Subject: [PATCH 1/2] add yck's three-stage-blog --- ...46\256\265\346\200\273\347\273\223-yck.md" | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 "source/_posts/2024-\347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\346\200\273\347\273\223-yck.md" diff --git "a/source/_posts/2024-\347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\346\200\273\347\273\223-yck.md" "b/source/_posts/2024-\347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\346\200\273\347\273\223-yck.md" new file mode 100644 index 0000000000..6105617511 --- /dev/null +++ "b/source/_posts/2024-\347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\346\200\273\347\273\223-yck.md" @@ -0,0 +1,205 @@ +--- +title: 2024-秋冬季开源操作系统训练营第三阶段总结-yck +date: 2024-12-02 02:00:28 +tags: + - author: AoligeiY +--- + +# Areos引导机制 + +- 首先进入`axhal`中平台架构的boot程序的`_start`函数,参数为OpenSBI传入的两个参数,一个是`hartid`用于识别CPU,一个是`dtb_ptr`传入DTB的指针。 +- 然后会建立栈用于函数调用,准备页表启动MMU分页机制,后即可进入Rust。 + +然后进入axruntime初始化应用运行时的环境,即根据feature条件编译各组件框架 + + + +# 启用页表机制 + +SBI配置FW_TEXT_START,BIOS负责把SBI加载到内存的起始位置 + +0x8000 0000存放SBI,SBI会跳转到以0x8020 0000起始的内核代码,存放.bss、.data、.rodata、.text段。 + +axhal中的linker_riscv64-qemu-virt.lds指导rust的链接器来实现段布局 + + +一阶段:即上面提到`_start`函数中的初始化MMU,将物理地址空间**恒等映射**,再添偏移量。 + +二阶段:重建映射,一阶段的映射并**没有添加权限**进行管理。若启用"paging"的feature,首先会调用axmm中的`init_memory_management()`,进行创建内核空间(`new_kernel_aspace()`),将根页表地址写入stap寄存器(`axhal::paging::set_kernel_page_table_root`)。 + + +# 多任务 + +启用"multitask"的feature,执行`axtask::init_scheduler()`,会进行就绪队列的初始化,以及系统timers的初始化。 + +- 就绪队列的初始化:初始化系统任务idle、main、gc + - main为主任务,其他任务均为其子任务 + - idle,其他任务都阻塞时会执行它 + - gc,除main之外的任务退出后,将由gc负责回收清理 + + + +## 调度算法 + +### CFS + +"init_vruntime"在os启动调度的时候决定调度顺序,"nice"则是优先级,优先级越大"vruntime"增长越慢,从而达到高优先。 + +当时间片结束后,当前任务将被为设为可抢占,当前任务释放控制权,加入就绪队列。并且如果外部条件满足抢占,则会将就绪队列中的队首弹出并切换到该任务 + + +# 宏内核 + +### m_1_0启动 + +- 首先为应用创建独立的用户地址空间。 + +`axmm::new_user_aspace` + +- 将应用程序代码加载到地址空间。 + +`load_user_app` + +- 初始化用户栈 + +`init_user_stack` + +- spawn创建用户任务,以用户地址空间及应用入口、用户栈为参数 + +`task::spawn_user_task` + +- 当前任务释放CPU,使用户任务运行 + +`user_task.join` + + + +### 用户空间 + + +页表管理内核地址空间和用户地址空间 + +内核地址空间只有高端(高地址)存放内核代码数据 + +用户地址空间高端存放内核,但这部分的页表项设为用户不可见,只有陷入内核态之后才能访问。低端地址存放应用 + +每个用户地址空间高端的内核均相同,只有存放应用的区域不同 + + + + +地址空间后端Backend: + +Linear:目标物理地址空间已经存在,直接建立映射关系,物理页帧必须连续 + +Alloc:仅建立空映射,当真正被访问时将会触发缺页异常,然后在缺页响应函数内部完成物理页帧的申请和补齐映射,也就是Lazy方式。物理页帧通常情况下不连续 + + + +### ELF格式应用加载 + + +代码段加载无偏移。 + +数据段加载到虚拟地址空间分为.data、.bss。但.bss在ELF文件中为节省空间紧凑存储不做存放,仅做标记位置和长度。内核直接预留空间并清零 + + + +### 缺页异常 + +当发生缺页异常时,由aspace的`handle_page_fault`来完成对物理页帧的申请与映射 + +首先检查发生页面错误的虚拟地址 `vaddr` 是否在当前虚拟地址范围 `va_range` 内。 + +然后找到`vaddr`的区域area,进入area的后端,缺页异常通常由后端Alloc的Lazy策略造成,故进入Alloc分支调用`handle_page_fault_alloc`对页表`remap`完成页表映射 + + + +# Hypervisor + +虚拟化基于RISCV64的S特权级的H扩展,Host将在HS特权级下进行`VM_ENTRY`以及`VM_EXIT` + + + +S特权模式进行H扩展后,原有的s[xxx]寄存器组作用不变,将新增hs[xxx]和vs[xxx] + +- hs[xxx]寄存器组的作用:面向Guest进行路径控制 +- vs[xxx]寄存器组的作用:直接操纵Guset域中的VS,为其准备或设置状态 + + + +设置hstatus的SPV位(指示特权级模式的来源,1为VS,0为U)SPVP位(指示HS对V模式下地址空间是否由操作权限) + + + +启动Guest之前,设置Guest的sstatus,设置初始特权级为Supervisor + +设置sepc为OS启动入口地址VM_ENTRY,地址为0x8020 0000 + + + +run_guest切换:保存Host上下文,将Guest上下文(第一次切换为伪造的上下文)载入寄存器组 + + + +VM_EXIT返回Host:保存Guest上下文,将Host上下文载入寄存器组 + + + +### Guest和Host地址空间 + +Hypervisor负责基于Host物理地址空间HPA面向Guest映射Guest物理地址空间GPA + +Guest会认为GPA是实际的物理地址空间,它基于satp映射内部的GVA虚拟空间 + + +启用RISC64指令集的G扩展: + +- 引入vsatp用于第一阶段的页表翻译,即将Guest的虚拟地址空间映射为Guest的物理地址空间 +- 引入hgatp用于第二阶段的页表翻译,即将Guest的物理地址空间映射到Host的物理地址空间 + + + +### 虚拟机物理地址空间布局 + + +低端区域留给设备空间和DMA + +0x8000 0000在Host中是存放SBI的,但是虚拟机没有M模式,是无法访问SBI,所以这部分进行保留 + +0x8020 0000存放内核 + +高于内核的地址用作物理内存区 + + + +### Hypervisor主逻辑 + +- 准备VM的资源:VM地址空间和单个vCPU +- 切换到进入Guest的代码 +- 响应VM_EXIT各种原因的代码 + + +实现流程: + +1. 创建地址空间 为目标虚拟机创建物理地址空间 axmm +2. 建立区域布局 在地址空间中为Guest内核和设备建立区域 axmm +3. 加载内核 把Guest内核Image加载到内核区域 axmm, axfs +4. 初始化vCPU 把vCPU的启动入口设置为Guest内核入口,同时设置EPT页表地址 + +最后在Host和Guest环境循环切换,支持虚拟机持续运行 + + + +VCPU的准备: + +- 通过`set_entry(VM_ENTRY)`设置sepc来配置入口地址0x8020 0000 +- 通过`set_ept_root`向hgatp设置模式(SV39)和根页表的页帧 + + + + +### axruntime和axhal时钟中断处理 + +`axhal::irq::register_handler` 通过`update_timer`在中断向量表中注册对应的中断响应函数,更新时钟。然后`axtask::on_timer_tick()`触发定时任务 + From 3446fb00cb2d92d209bcddd447ecbbd127eff30e Mon Sep 17 00:00:00 2001 From: yck <2998770265@qq.com> Date: Sun, 22 Dec 2024 03:34:57 +0800 Subject: [PATCH 2/2] add yck's four-stage-blog --- ...46\256\265\346\200\273\347\273\223-yck.md" | 464 ++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 "source/_posts/2024-\347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\345\233\233\351\230\266\346\256\265\346\200\273\347\273\223-yck.md" diff --git "a/source/_posts/2024-\347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\345\233\233\351\230\266\346\256\265\346\200\273\347\273\223-yck.md" "b/source/_posts/2024-\347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\345\233\233\351\230\266\346\256\265\346\200\273\347\273\223-yck.md" new file mode 100644 index 0000000000..6c302166b2 --- /dev/null +++ "b/source/_posts/2024-\347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\345\233\233\351\230\266\346\256\265\346\200\273\347\273\223-yck.md" @@ -0,0 +1,464 @@ +--- +title: 2024-秋冬季开源操作系统训练营第四阶段总结-yck +date: 2024-12-22 03:27:10 +tags: + - author: AoligeiY +--- + +## 总结 + +最后这一阶段,鄙人主要围绕rust的异步编程async/await及经典运行时库tokio展开阅读研究。但并没有深入理解其中原理及实现,更多的是比较浅显地了解,像tokio的有些模块并没有看,还又比较多的疑问在里面。如果后续有时间的话,希望能继续阅读并深入。代码实现上,仅在rcore上实现了一个极为简单的用户态运行时,跑了几个异步任务,有点协程的意思了(。但是并没有实现waker机制,后续考虑进一步实现完善 + + + +## 协程 + +### 有栈协程 + +函数运行在调用栈上,把函数作为一个协程,那么协程的上下文就是这个函数及其嵌套函数的栈帧存储的值,以及此时寄存器存储的值。如果我们调度协程,也就是保存当前正在运行的协程上下文,然后恢复下个将要运行的协程的上下文。这样我们就轻松的完成了协程调度。并且因为保存的上下文和普通函数执行的上下文是一样的,所以有栈协程可以在任意嵌套函数中挂起(无栈协程不行)。 + +有栈协程的优点在易用性上,通常只需要调用对应的方法,就可以切换上下文挂起协程。在有栈协程调度时,需要频繁的切换上下文,开销较大。单从实现上看,有栈协程更接近于内核级线程,都需要为每个线程保存单独的上下文(寄存器、栈等),区别在于有栈协程的调度由应用程序自行实现,对内核是透明的,而内核级线程的调度由系统内核完成,是抢占式的。 + +### 无栈协程 + +相比于有栈协程直接切换栈帧的思路,无栈协程在不改变函数调用栈的情况下,采用类似生成器的思路实现了上下文切换。通过编译器将生成器改写为对应的迭代器类型(内部实现是一个状态机)。 + +而无栈协程需要在编译器将代码编译为对应的状态机代码,挂起的位置在编译器确定。无栈协程的优点在性能上,不需要保存单独的上下文,内存占用低,切换成本低,性能高。缺点是需要编译器提供语义支持,无栈协程的实现是通过编译器对语法糖做支持,rust的aysnc\await就是语法糖,编译器将带有这些关键字的方法编译为生成器,以及对应的类型作为状态机。 + +只有状态机的支持才能进行协程调度,例如Rust中的tokio,基于Future的用户态线程,根据poll方法获取Future状态,它不可以在任意嵌套函数中挂起(同步代码未实现状态机)。 + + + +## tokio + +我主要从tokio::main宏出发,一步步分析其中调用关系。主要围绕runtime库的build及Runtime结构体的方法及函数 + + + + +使用`#[tokio::main]`宏生成的代码 + +```rust +fn main() { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + // ... + }) +} +``` + +结构体`Builder`用作运行时`runtime`配置,通过`new_current_thread()`使用单线程运行时,`new_multi_thread()`使用多线程运行时,并会返回`Builder`实例 + + + +### new_multi_thread + +```rust + /// Returns a new builder with the multi thread scheduler selected. + /// + /// Configuration methods can be chained on the return value. + #[cfg(feature = "rt-multi-thread")] + #[cfg_attr(docsrs, doc(cfg(feature = "rt-multi-thread")))] + pub fn new_multi_thread() -> Builder { + // The number `61` is fairly arbitrary. I believe this value was copied from golang. + Builder::new(Kind::MultiThread, 61) + } +``` + +调用`new`方法返回一个`Kind`为`MultiThread`的`Builder`,对于定时器和I/O事件释放CPU之前需要61ticks + + + +### enable_all + +```rust + /// Enables both I/O and time drivers. + /// + /// Doing this is a shorthand for calling `enable_io` and `enable_time` + /// individually. If additional components are added to Tokio in the future, + /// `enable_all` will include these future components. + pub fn enable_all(&mut self) -> &mut Self { + #[cfg(any( + feature = "net", + all(unix, feature = "process"), + all(unix, feature = "signal") + ))] + self.enable_io(); + #[cfg(feature = "time")] + self.enable_time(); + + self + } +``` + +操作成员变量,使能运行时I/O和定时器 + + + +### build + +```rust + /// Creates the configured `Runtime`. + /// + /// The returned `Runtime` instance is ready to spawn tasks. + pub fn build(&mut self) -> io::Result { + match &self.kind { + Kind::CurrentThread => self.build_current_thread_runtime(), + #[cfg(feature = "rt-multi-thread")] + Kind::MultiThread => self.build_threaded_runtime(), + #[cfg(all(tokio_unstable, feature = "rt-multi-thread"))] + Kind::MultiThreadAlt => self.build_alt_threaded_runtime(), + } + } +``` + +运行时创建的核心步骤,创建已经做配置的`runtime`,并返回`Runtime`实例准备创建异步任务 + +```rust +pub struct Runtime { + /// Task scheduler + scheduler: Scheduler, + + /// Handle to runtime, also contains driver handles + handle: Handle, + + /// Blocking pool handle, used to signal shutdown + blocking_pool: BlockingPool, +} +``` + +`scheduler`:异步任务调度器 + +`handle`:运行时句柄 + +`blocking_pool`:阻塞池句柄,用于发出关闭信号 + + + +### build -> build_threaded_runtime + +```rust +fn build_threaded_runtime(&mut self) -> io::Result { + use crate::loom::sys::num_cpus; + use crate::runtime::{Config, runtime::Scheduler}; + use crate::runtime::scheduler::{self, MultiThread}; + + let core_threads = self.worker_threads.unwrap_or_else(num_cpus); + + let (driver, driver_handle) = driver::Driver::new(self.get_cfg(core_threads))?; + + // Create the blocking pool + let blocking_pool = + blocking::create_blocking_pool(self, self.max_blocking_threads + core_threads); + let blocking_spawner = blocking_pool.spawner().clone(); + + // Generate a rng seed for this runtime. + let seed_generator_1 = self.seed_generator.next_generator(); + let seed_generator_2 = self.seed_generator.next_generator(); + + let (scheduler, handle, launch) = MultiThread::new( + core_threads, + driver, + driver_handle, + blocking_spawner, + seed_generator_2, + Config { + before_park: self.before_park.clone(), + after_unpark: self.after_unpark.clone(), + before_spawn: self.before_spawn.clone(), + after_termination: self.after_termination.clone(), + global_queue_interval: self.global_queue_interval, + event_interval: self.event_interval, + local_queue_capacity: self.local_queue_capacity, + #[cfg(tokio_unstable)] + unhandled_panic: self.unhandled_panic.clone(), + disable_lifo_slot: self.disable_lifo_slot, + seed_generator: seed_generator_1, + metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(), + }, + ); + + let handle = Handle { inner: scheduler::Handle::MultiThread(handle) }; + + // Spawn the thread pool workers + let _enter = handle.enter(); + launch.launch(); + + Ok(Runtime::from_parts(Scheduler::MultiThread(scheduler), handle, blocking_pool)) + } +``` + +1. 引入 + +`num_cpus`:用于获取系统的 CPU 核心数量。 + +`Config`:运行时的配置项。 + +`MultiThread`:多线程调度器,负责在多个线程间调度异步任务。 + + + +2. 确定核心线程数 + +``` +let core_threads = self.worker_threads.unwrap_or_else(num_cpus); +``` + +- `self.worker_threads`:用户在`Builder`配置的核心线程数。如果未指定,使用系统的 CPU 核心数作为默认值(`num_cpus()`)。 +- `core_threads` 是最终确定的核心线程数,表示运行时的调度线程数量。 + + + +3. 初始化驱动 + +```rust +let (driver, driver_handle) = driver::Driver::new(self.get_cfg(core_threads))?; +``` + +- **`driver`**:负责管理底层 IO 和定时器的核心组件。 +- **`driver_handle`**:运行时与驱动交互的句柄,用于调度异步任务和管理定时器。 +- `get_cfg(core_threads)`:返回结构体`driver::Cfg`,包含驱动的配置项,包含workers等配置。 + + + +4. 创建阻塞任务池 + +```rust +let blocking_pool = blocking::create_blocking_pool(self, self.max_blocking_threads + core_threads); +let blocking_spawner = blocking_pool.spawner().clone(); +``` + +- 阻塞任务池:用于执行需要阻塞运行的任务(例如文件 IO 或计算密集型任务),避免阻塞异步任务调度线程。 + + - 线程数 = 用户指定的 `max_blocking_threads` + 核心线程数。 + +- `blocking_spawner`:用于将任务提交到阻塞任务池的对象。 + + + +5. 生成随机数种子 + +```rust +let seed_generator_1 = self.seed_generator.next_generator(); +let seed_generator_2 = self.seed_generator.next_generator(); +``` + +- Tokio 的调度器可能需要随机化任务的分配,例如在多线程调度器中均衡负载。 +- `seed_generator_1` 和 `seed_generator_2` 是生成的两个独立随机数种子,分别用于不同的组件。 + + + +6. 初始化调度器 + +```rust +let (scheduler, handle, launch) = MultiThread::new( + core_threads, + driver, + driver_handle, + blocking_spawner, + seed_generator_2, + Config { ... }, +); +``` + +- 调用 `MultiThread::new` 创建一个多线程调度器,返回: + + - `scheduler`:核心调度器,管理线程间任务的分配和调度。 + - `handle`:调度器的句柄,用于外部与调度器交互。 + - `launch`:启动调度器工作线程的接口。 + +- 传递的参数: + + - `core_threads`:核心线程数。 + + - `driver` 和 `driver_handle`:用于与 IO 和定时器交互的驱动组件。 + + - `blocking_spawner`:用于执行阻塞任务的接口。 + + - `seed_generator_2`:随机数种子,用于内部调度逻辑。 + + - `Config`:调度器的配置,包括以下内容: + - `before_park` 和 `after_unpark`:线程挂起与唤醒时的回调函数。 + - `before_spawn` 和 `after_termination`:任务生成和结束时的回调函数。 + - `global_queue_interval` 和 `event_interval`:全局队列检查和事件处理的时间间隔。 + - `local_queue_capacity`:本地任务队列容量。 + - 其他运行时的定制项。 + + + +7. 创建运行时句柄 + +```rust +let handle = Handle { inner: scheduler::Handle::MultiThread(handle) }; +``` + +- `Handle` 是对调度器的抽象封装,用于外部访问运行时和调度器的功能。 + + + +8. 启动工作线程 + +```rust +let _enter = handle.enter(); +launch.launch(); +``` + +- `handle.enter()`:将当前线程设置为调度器的上下文,用于初始化调度环境。 +- `launch.launch()`:启动调度器的所有工作线程,使其开始运行。 + + + +9. 返回运行时 + +```rust +Ok(Runtime::from_parts(Scheduler::MultiThread(scheduler), handle, blocking_pool)) +``` + +- 创建并返回完整的`Runtime`实例,包括: + - `Scheduler::MultiThread(scheduler)`:多线程调度器。 + - `handle`:运行时句柄。 + - `blocking_pool`:阻塞任务池。 + + + +## driver::Driver::new方法 + +```rust +pub(crate) fn new(cfg: Cfg) -> io::Result<(Self, Handle)> { + let (io_stack, io_handle, signal_handle) = create_io_stack(cfg.enable_io, cfg.nevents)?; + + let clock = create_clock(cfg.enable_pause_time, cfg.start_paused); + + let (time_driver, time_handle) = + create_time_driver(cfg.enable_time, io_stack, &clock, cfg.workers); + + Ok(( + Self { inner: time_driver }, + Handle { + io: io_handle, + signal: signal_handle, + time: time_handle, + clock, + }, + )) + } +``` + +1. 创建 IO 栈 + +```rust +let (io_stack, io_handle, signal_handle) = create_io_stack(cfg.enable_io, cfg.nevents)?; +``` + +- 调用 `create_io_stack` 创建 IO 栈(底层 IO 相关的资源),返回: + + - `io_stack`:IO 栈的核心组件,用于管理底层 IO 操作。 + - `io_handle`:用于和 IO 栈交互的句柄。 + - `signal_handle`:用于处理信号的句柄(如处理 `Ctrl+C`)。 + +- 参数解释: + + - `cfg.enable_io`:如果为 `true`,启用 IO 支持;否则,不创建 IO 栈。 + - `cfg.nevents`:指定事件队列的大小,用于多路复用器。 + +2. 创建时钟 + +``` +let clock = create_clock(cfg.enable_pause_time, cfg.start_paused); +``` + +- 调用 `create_clock` 创建时钟组件,用于时间相关功能的管理,返回一个 `Clock` 对象。 +- 参数解释: + - `cfg.enable_pause_time`:是否支持暂停时间。 + - `cfg.start_paused`:是否让时钟在启动时进入暂停状态。 + +时钟的主要用途: + +- 为定时器、延迟任务提供精确的时间点。 +- 支持测试或调试时暂停时间的功能。 + +3. 创建时间驱动器 + +``` +let (time_driver, time_handle) = + create_time_driver(cfg.enable_time, io_stack, &clock, cfg.workers); +``` + +- 调用 `create_time_driver` + + 创建时间驱动器,返回: + + - `time_driver`:管理时间相关任务的核心组件(例如定时器、延迟任务)。 + - `time_handle`:与时间驱动器交互的句柄。 + +- 参数解释: + + - `cfg.enable_time`:是否启用时间功能。 + - `io_stack`:IO 栈,与时间驱动器共享底层的事件循环。 + - `&clock`:前面创建的时钟,提供时间相关支持。 + - `cfg.workers`:用于分配时间任务的工作线程数。 + +时间驱动器的主要功能: + +- 实现异步延迟(如 `tokio::time::sleep`)。 +- 管理基于时间的任务调度。 + +4. 返回结果 + +```rust +Ok(( + Self { inner: time_driver }, + Handle { + io: io_handle, + signal: signal_handle, + time: time_handle, + clock, + }, +)) +``` + +- 返回一个包含驱动器和其句柄的元组: + - `Self { inner: time_driver }` + - 驱动器的核心部分是时间驱动器,封装到 `Self` 结构体中。 + - `Handle`: + - 包含多个句柄,用于驱动器与外部的交互: + - `io_handle`:处理 IO 事件的句柄。 + - `signal_handle`:处理信号的句柄。 + - `time_handle`:管理时间任务的句柄。 + - `clock`:提供时间支持的时钟实例。 + +### tokio启动 + +通过`launch.launch();`启动所有的 `worker` 线程: + +```rust +pub(crate) fn launch(mut self) { + for worker in self.0.drain(..) { + runtime::spawn_blocking(move || run(worker)); + } +} +``` + + + +`runtime::spawn_blocking` 调用时, `|| run(worker)` 匿名函数会被传进去,这其实就是 worker 线程要执行的逻辑。 + +如下,匿名函数会被包装为 `BlockingTask`,并被放在 blocking thread 的 run queue 中,这样当它运行时就会执行这个匿名函数。因为这时没有足够的线程,就会初始化一个新的 OS 线程(如果有 idle 的线程,就会通过 condvar 通知),并开始执行 blocking 线程的逻辑。每个 worker 都占用一个 blocking 线程,并在 blocking 线程中运行直到最后。 + +```rust +// runtime::spawn_blocking: +let (task, _handle) = task::joinable(BlockingTask::new(func)); + +let mut shared = self.inner.shared.lock(); +shared.queue.push_back(task); + +let mut builder = thread::Builder::new(); // Create OS thread +// run worker thread +builder.spawn(move || { + rt.blocking_spawner.inner.run(id); +}) +``` +