进程是操作系统为任务分配资源的基本单位,而线程是操作系统调度的基本单位。FTL OS设计上进程可以被多线程所有,所有线程都是对等的,只有所有线程全部退出或执行了exit
系统调用才会释放进程。这两种途径不会完全释放进程的内存,因为进程退出后需要留下为父进程留下必要的信息,进程还需要被父进程持有。因此进程的所有权树如下:
每个线程被所在的异步Future
持有,Future
将不断地被调度器执行poll
,直到返回了Ready
后被释放。线程将不断则进行用户循环:
// kernel/src/process/userloop.rs
async fn userloop(thread: Arc<Thread>) {
// Future从参数捕获了thread
loop {
// 进入用户态
...
// 离开用户态处理陷阱
...
if exit {
break; // 线程在这里离开的主循环
}
}
// 执行到这后poll会返回Ready(()), thread将被释放
}
每个进程都具有唯一标识符pid
,目前FTL OS不支持进程生成额外的线程,因此pid
在全局分配并通过pid
获取线程的tid
,tid
和pid
相同。但在支持多线程后需要改变获取pid
的方式,先有线程获取tid
,再从tid
获取pid
,tid
分配的唯一性保证了pid
的唯一性。但初始线程tid
的回收应该在进程释放时进行,因为初始线程释放后进程可能还被其他线程持有,此时如果立刻释放tid
则下次产生的线程对应的进程会和旧进程拥有一样的pid
并导致一系列错误。
FTL OS目前阶段的线程只携带了用户地址空间的上下文信息,定义如下:
pub struct Thread {
// never change
pub tid: Tid,
pub process: Arc<Process>,
// thread local
inner: UnsafeCell<ThreadInner>,
}
pub struct ThreadInner {
pub stack_id: StackID,
pub set_child_tid: UserInOutPtr<u32>,
pub clear_child_tid: UserInOutPtr<u32>,
pub signal_mask: SignalSet,
uk_context: Box<UKContext>,
}
ThreadInner
包含了线程的局部信息。这部分信息被定义为只有线程自身才能访问,因此不需要加锁。目前线程局部信息只有uk_context
被使用,其他字段处于保留状态。uk_context
处于内核态时保存了用户态的上下文,处于用户态时保存了内核态的上下文,是用户态和内核态的上下文交换区。
FTL OS的进程控制块定义如下:
pub struct Process {
pid: PidHandle,
pub pgid: AtomicUsize,
pub event_bus: Arc<EventBus>,
pub alive: Mutex<Option<AliveProcess>>,
pub exit_code: AtomicI32,
}
pub struct AliveProcess {
pub user_space: UserSpace,
pub cwd: Arc<VfsInode>,
pub exec_path: String,
pub envp: Vec<String>,
pub parent: Option<Weak<Process>>,
pub children: ChildrenSet,
pub threads: ThreadGroup,
pub fd_table: FdTable,
pub signal_queue: LinkedList<SignalPack>,
}
可以看到进程控制块被分为了Process
与AliveProcess
两个部分,Process
包含了进程的不可变信息,获取这部分信息不需要加锁。AliveProcess
只有在进程退出之前才有效,进程退出时会变为None
并在析构函数中释放所有持有的句柄。
Process
的各个字段定义如下:
字段 | 描述 |
---|---|
PidHandle | 进程ID句柄,析构时自动回收 |
pgid | 进程组ID |
event_bus | 事件总线,其他进程访问此进程的唯一途径 |
alive | 进程运行信息,只能被所在进程访问 |
exit_code | 进程退出码,只有进程退出后才有效 |
AliveProcess
的各个字段定义如下:
字段 | 描述 |
---|---|
user_space | 进程地址空间管理器,持有进程页表 |
cwd | 当前命令行所在的目录 |
exec_path | 程序执行时的目录 |
envp | 程序执行时的环境变量 |
parent | 此进程的父进程,如果为空说明是初始化进程 |
children | 子进程集合 |
threads | 运行在此进程的全部线程 |
fd_table | 所有打开文件的映射表 |
signal_queue | 信号队列,尚未启用 |
FTL OS的事件总线能够从根本上防止死锁并提高效率,但目前还没有实现全部功能,仍然允许其他进程在退出时访问AliveProcess
字段。但即使只允许当前进程访问自身AliveProcess
然需要被锁保护,因为进程可以拥有多个线程。