任务结构及生命周期管理
本文档详细说明了 Task 的核心数据结构、生命周期状态转换以及暴露给其他内核模块的接口。
1. 核心数据结构:Task
操作系统的核心任务表示是 Task 结构体,它统一了进程和线程的概念。所有与执行流相关的信息都封装在其中。
源码链接: os/src/kernel/task/task_struct.rs
Task 结构体的主要字段可以分为以下几类:
1.1 身份与亲属关系
这些字段用于唯一标识一个任务并建立任务间的层级关系。
tid: u32: 任务ID (Task ID)。由TidAllocator分配的全系统唯一标识符。pid: u32: 进程ID (Process ID)。对于线程,它与创建它的主任务pid相同。对于一个进程的第一个任务,pid等于其tid。ppid: u32: 父任务ID (Parent Process ID)。
1.2 调度与执行上下文
这些字段由调度器和中断处理机制在任务切换和执行时使用。
context: Context: 任务上下文。保存了任务切换时需要恢复的最小寄存器集合(主要是ra和sp),用于非中断驱动的上下文切换。trap_frame_ptr: AtomicPtr<TrapFrame>: 中断帧指针。当任务从用户态或内核态陷入(trap)时,CPU的完整上下文(所有通用寄存器、sepc、sstatus等)被保存在其内核栈上,此指针指向该TrapFrame的位置。当中断返回时,__restore会用它来恢复现场。state: TaskState: 任务状态。定义在os/src/kernel/task/task_state.rs,是任务生命周期管理的核心。preempt_count: usize: 抢占计数器。当大于0时,禁止内核抢占,用于保护临界区。
1.3 资源管理
这些字段管理任务执行所必需的系统资源。
kstack_base: usize: 内核栈顶地址。每个任务都有自己独立的内核栈。kstack_tracker&trap_frame_tracker: 用于跟踪内核栈和中断帧所占用的物理页帧,以便在任务销毁时正确回收。memory_space: Option<Arc<MemorySpace>>: 内存地址空间。对于用户任务,它包含了页表、内存映射区域等信息。对于内核线程,此字段为None。
1.4 生命周期与退出状态
这些字段用于处理任务的终止和父任务的等待。
exit_code: Option<i32>: 退出码。用于进程,在调用exit系统调用时设置。return_value: Option<usize>: 返回值。用于线程,在线程函数返回时设置。
2. 任务的生命周期与状态转换
任务的生命周期由 TaskState 枚举驱动,并通过一系列接口函数进行管理。
2.1 任务的创建
内核线程
- 接口:
kthread_spawn(name: &'static str, entry: fn(usize) -> !, arg: usize) - 源码:
os/src/kernel/task/ktask.rs - 机制:
- 调用
TASK_MANAGER分配tid。 - 分配内核栈和中断帧所需的物理内存。
- 调用
Task::ktask_create创建Task实例。此函数会:- 初始化
Context,将ra指向forkret,sp指向内核栈顶。 - 初始化位于内核栈上的
TrapFrame,将sepc设置为线程入口点entry,sstatus设置为S模式,并设置好内核栈指针x2_sp。
- 初始化
- 将新创建的任务包装在
Arc<SpinLock<Task>>(即SharedTask) 中,并交给调度器SCHEDULER的运行队列。
- 调用
用户任务 (待实现)
- 接口: (例如
utask_create或sys_clone) - 机制: 与内核线程类似,但需要额外创建和关联一个
MemorySpace(用户地址空间),并初始化TrapFrame以便从S模式返回到U模式执行。
2.2 任务的执行与切换
forkret:- 源码:
os/src/kernel/task/mod.rs - 机制: 所有新创建的任务在第一次被调度器选中时,都会从
__switch跳转到forkret函数。forkret的唯一职责是从当前任务的trap_frame_ptr中加载中断帧地址,并调用restore汇编例程。restore会将中断帧中的寄存器值恢复到CPU中,最后通过sret指令跳转到任务的真正入口点(sepc),任务从而开始执行。
- 源码:
2.3 任务的睡眠与唤醒
- 接口:
sleep_task(task: SharedTask, receive_signal: bool)wake_up(task: SharedTask)
- 源码:
os/src/kernel/scheduler/rr_scheduler.rs(作为Schedulertrait 的一部分) - 机制:
- 睡眠: 当任务需要等待资源时(例如等待一个
SleepLock),它会调用sleep_task。调度器会将该任务的state设置为Interruptible或Uninterruptible,并将其从运行队列中移除。随后调度器会选择下一个任务运行。 - 唤醒: 当资源可用时,持有该资源的模块会调用
wake_up。调度器会将任务的state恢复为Running,并将其重新加入运行队列。
- 睡眠: 当任务需要等待资源时(例如等待一个
2.4 任务的终止
- 接口:
terminate_task(return_value: usize) -> ! - 源码:
os/src/kernel/task/mod.rs - 机制:
- 当一个内核线程的入口函数返回时,
TrapFrame中预设的返回地址ra会指向terminate_task。 terminate_task获取当前任务,将其state设置为Stopped,并保存返回值return_value。- 它主动调用
schedule()让出CPU,由于任务状态已是Stopped,它将不会再被调度器放回运行队列。 - 任务占用的资源(如内核栈)的最终回收依赖于
Arc的引用计数。当所有对该任务的SharedTask引用都消失后,Task的Drop实现会被调用,从而释放内存。
- 当一个内核线程的入口函数返回时,
3. 暴露接口
os/src/kernel/task/mod.rs:SharedTask:Arc<SpinLock<TaskStruct>>的类型别名,是任务在内核中传递的标准形式。into_shared(task: TaskStruct) -> SharedTask: 将一个Task结构包装为SharedTask。
os/src/kernel/task/ktask.rs:kthread_spawn(...): 创建内核线程的顶层API。
os/src/kernel/mod.rs(通过scheduler模块暴露):yield_task(): 主动让出CPU,触发一次调度。sleep_task(...): 使指定任务进入睡眠状态。wake_up(...): 唤醒指定任务。exit_task(...): 终止指定任务并设置退出码。
这些接口共同构成了任务管理的核心功能,为上层模块(如锁、IPC、系统调用)提供了构建并发服务的基础。