应用程序的一次执行过程称为一个任务。
在 BTD-OS 中我们将进程和线程统一用任务控制块 (TaskContralBlock) 结构表示,维护着操作系统对于任务的管理信息,结构如下:
// branch-main: kernel/src/task/task.rs
pub struct TaskControlBlock {
// immutable
pub pid: PidHandle,
pub tgid: usize,
pub kernel_stack: KernelStack,
// mutable according to clone flags
pub sigactions: Arc<RwLock<[SigAction; MAX_SIGNUM as usize]>>,
pub memory_set: Arc<RwLock<MemorySet>>,
pub fd_table: Arc<RwLock<FDTable>>,
// mutable
inner: RwLock<TaskControlBlockInner>,
}
- pid / tgid 由全局分配器 PID_ALLOCATOR 分配,并且分配的 ID 仅增长不回收,因为 BTD-OS 实现线程的过程中,默认认为 tgid 不小于 pid;
- 若该任务控制块为进程,则 TaskControlBlock 的 pid 与 tgid 字段值相同;
- pid 与 tgid 的差值可以用于计算线程 TrapContext 虚拟地址;
- kernel_stack 字段仅用于创建时分配内核栈,故无需修改;
- sigactions, memory_set, fd_table 字段均需要根据 fork 时的 CloneFlags 参数结合 man-page 要求创建;
pub struct TaskControlBlockInner {
pub trap_cx_ppn: PhysPageNum,
pub task_cx: TaskContext,
pub task_status: TaskStatus,
pub trap_cause: Option<Scause>,
pub parent: Option<Weak<TaskControlBlock>>,
// child process and thread collection
pub children: Vec<Arc<TaskControlBlock>>,
pub pending_signals: SigSet,
pub sigmask: SigMask,
pub cwd: AbsolutePath,
pub exit_code: i32,
pub interval_timer: Option<IntervalTimer>,
pub utime: TimeVal,
pub stime: TimeVal,
pub last_enter_umode_time: TimeVal,
pub last_enter_smode_time: TimeVal,
pub robust_list: RobustList,
pub rlimit_nofile: RLimit,
pub clear_child_tid: usize, /* CLONE_CHILD_CLEARTID */
}
- trap_cause 用于记录 TCB 进入 trap_handler 的原因,BTD-OS 具体用于时钟中断更新时间片的问题(后文会介绍 BTD-OS 为何改进时钟中断处理);
- parent 表示可能存在的父进程;
- children 用于收集 fork 时创建的子进程/子线程的 Arc (原子引用计数);
- pending_signals 表示待处理的信号集;
- sigmask 表示被屏蔽的信号集;
- interval_timer 用于处理与定时器相关的系统调用;
- utime, stime, last_enter_umode_time, last_enter_smode_time 用于记录 TCB 分别在 U Mode 和 S Mode 下所耗费的时间,用于处理 getrusage 系统调用;
- robust_list 用于实现 get_robust_list,set_robust_list 系统调用
- rlimit_nofile 用于实现 rlimit 相关系统调用,但目前仅用于限制进程可打开的文件数
- clear_child_tid 用于实现 fork,set_tid_address 系统调用
另外,我们在参考往届优秀作品实现时,有注意到有些队伍在 TCB 中保留了 user_stask 字段。该字段用于保存为线程分配用户栈时保留的 user_stack 虚拟地址。
BTD-OS 的 TCB 中并没有保存,原因是按照 man-page 关于 fork(clone) 系统调用的规定对于共享内存的子进程或子线程,创建传入 stask 参数,该参数规定了子进程/线程的用户栈的位置,故实际上不需要我们额外创建 user_stack。
对于非 forK(clone) 创建的进程(new/exec load_elf 方法中),BTD-OS 会分配并映射该进程的 user_stack。
// kernel/src/mm/memory_set.rs: fn load_elf
memory_set.user_stack_areas = VmArea::new(
user_stack_bottom.into(),
user_stack_top.into(),
MapType::Framed,
VmAreaType::UserStack,
MapPermission::R | MapPermission::W | MapPermission::U,
None,
0,
);