task.md 12 KB

Eonix内核的任务管理

Eonix内核的任务管理系统通过线程、进程、进程组、会话和信号机制,构建了一个高效、灵活的多任务管理框架。以下是对其任务管理部分的全面介绍:


1. 核心设计概述

任务管理是内核调度和进程间通信的基础,其核心组件包括:

  • 线程(Thread): 是调度的基本单位。
  • 进程(Process): 是资源分配的基本单位,包含线程的集合。
  • 进程组(ProcessGroup): 组织和管理一组相关进程,通常用于信号广播。
  • 会话(Session): 包含多个进程组,为作业控制和终端管理提供支持。
  • 信号机制: 负责进程间通信、异常处理和任务状态控制。

Eonix的任务管理通过统一的接口和模块化设计,支持复杂的任务调度、资源管理和进程间交互。


2. 线程管理

线程是Eonix内核调度的基本单位,由Thread结构体表示,每个线程包含独立的内核栈(KernelStack)、文件系统上下文和文件描述符表等资源。线程的状态管理(如RunningISleepUSleep等)通过调度器接口完成,支持线程在用户态和内核态之间高效切换。线程中有进程的指针,这样可以做到多个线程共享一部分资源。通过InterruptContextTaskContext实现上下文保存与切换,确保任务运行的连续性和调度的高效性。

每个线程由Thread结构体表示,包含:

  • TID(tid): 唯一标识。
  • 线程状态(state): 包括PreparingRunningReadyZombie等多种状态,用于调度器管理。
  • 内核栈(kstack): 为每个线程分配一个独立的内核栈,用于上下文切换和异常处理。
  • 文件系统上下文(fs_context)和文件描述符表(files): 提供线程的独立资源管理。

线程的生命周期

线程的创建方面,初始线程通过Thread::new_for_init创建。像在cloneexec等中复制的线程通过Thread::new_cloned创建,和父线程一起共享一个进程的资源。状态管理通过调度器(Scheduler)接口完成,支持从Running切换到ISleepUSleep等状态。 上下文切换使用InterruptContextTaskContext保存线程的寄存器状态,支持线程在内核和用户态间切换。


3. 进程管理

进程是资源分配的基本单位,由Process结构体表示。每个进程具有唯一的PID,并包含线程集合、父子关系、内存管理信息(MMList)以及所属的进程组和会话。进程的创建通过克隆现有进程实现,支持资源继承,同时初始进程(如init进程或闲置进程)通过专门方法生成。进程支持多线程资源共享,通过WaitList等待子进程状态变化(如退出或收到信号),并结合信号机制实现进程间通信和同步操作。进程的多线程安全性由锁机制保障。

进程的基本结构

每个进程由Process结构体表示,包含:

  • PID(pid): 每个进程都有一个唯一的标识符,用于区分其他进程。
  • 线程集合(threads): 用BTreeMap储存一个包含属于该进程的所有线程的列表。
  • 子进程(children): 使用BTreeMap存储该进程的所有子进程,支持高效查找和管理。
  • 内存管理信息(mm_list): 使用MMList表示进程的内存映射,管理用户态和内核态的地址空间。
  • 父进程(parent): 引用父进程,支持动态更新和安全访问。
  • 所属进程组(pgroup): 每个进程都属于一个进程组,用于协作管理和信号广播。
  • 会话(session): 进程所属的会话,支持终端绑定和作业控制。

进程的生命周期

进程创建通过Process::new_cloned方法实现新进程的创建,子进程会继承父进程的大部分资源(如内存和会话)。对于系统初始化的特殊进程(如init进程),通过Process::new_for_init创建。

进程终止时,调用ProcessList::do_kill_process方法将进程标记为Zombie状态,并清理相关资源,包括用户态内存、子进程管理以及从进程组和会话中移除。如果终止的是会话领导进程,关联的控制终端也会被释放。同时,其没有退出的所有子进程都会被收容到init进程中,确保每个进程都可以正常地被回收。

进程管理与多线程支持

ProcessList是管理所有进程的核心结构,通过全局实例GLOBAL_PROC_LIST维护。ProcessList使用BTreeMap存储所有的进程、线程、进程组和会话,并通过RwSemaphore实现线程安全访问。关键操作包括:

  1. 添加与移除: 通过add_processremove_process方法实现进程的动态添加与移除。在移除进程时,还会同步清理其线程、会话和进程组信息。
  2. 查询: 提供try_find_processtry_find_pgroup等方法,支持高效查找进程和相关结构。 调度整合: 结合调度器Scheduler,支持线程和进程的动态调度,确保高效的多任务管理。

4. 信号管理

信号是Eonix内核进程间通信和异常处理的核心机制,用于实现进程控制、任务协作和异常处理。信号通过Signal结构体表示,并结合SignalListSignalAction实现挂起、屏蔽和处理机制。信号的设计符合POSIX标准,支持多种信号类型(如SIGKILLSIGSTOP等),并通过进程组和会话实现信号广播和作业控制。

信号的基本结构

信号由Signal结构体表示,包含信号类型,即每个信号通过唯一的数字标识,支持常见的POSIX信号类型,包括:

  • 终止信号: 如SIGTERMSIGKILL,用于立即终止进程。
  • 停止信号: 如SIGSTOPSIGTSTP,用于暂停进程。
  • 用户自定义信号: 如SIGUSR1SIGUSR2,用于用户进程间通信。
  • 忽略信号: 如SIGCHLD,默认行为是被内核忽略。
  • 核心转储信号: 如SIGSEGVSIGFPE,会生成核心转储文件并终止进程。

信号管理与挂起

信号管理由SignalList完成,负责信号的挂起、屏蔽和优先级管理。挂起信号存储在BinaryHeap中,按照优先级排序,以确保高优先级信号优先处理。通过SignalList::maskSignalList::unmask动态管理信号掩码,决定哪些信号会被屏蔽。对于线程或进程接收到的信号,会首先判断其屏蔽状态,屏蔽信号会进入挂起队列,等待后续处理。

信号处理与分发

信号的处理方式由SignalAction定义,支持以下三种模式:

  • 默认行为: 系统为每种信号定义的默认处理方式,如终止进程、暂停进程或忽略信号。
  • 自定义Handler: 用户可以为特定信号设置自定义处理器,在用户态执行信号处理逻辑。
  • 忽略信号: 指定某些信号被忽略,如SIGCHLD默认不会影响进程运行。

信号分发可以针对单个线程、进程组或会话的前台进程组进行广播。通过SignalList::raise方法,信号被加入目标对象的挂起信号队列,并根据优先级等待处理。

信号与上下文切换

信号处理与上下文切换紧密结合。当线程被调度器切换到运行状态时,内核会检测其挂起信号列表,并调用相应的信号处理器。信号处理器通过SignalAction::handle加载到用户堆栈中,修改InterruptContext返回地址,将执行跳转到用户态的信号处理函数中。

对于不可屏蔽信号(如SIGKILLSIGSTOP),内核会立即终止或暂停目标进程,不会经过挂起队列或信号处理器。

信号的进程间通信

信号是进程间通信的重要机制,支持通过进程组和会话范围实现信号广播。例如:

  • 通过ProcessGroup::raise将信号发送给整个进程组。
  • 使用Session::raise_foreground向会话的前台进程组发送信号,实现作业控制。

信号分发结合进程组和会话机制,为任务管理中的状态同步和协作提供了基础设施。

Eonix内核的信号管理模块通过模块化设计和POSIX标准兼容,实现了灵活的进程间通信和异常处理。挂起信号、屏蔽机制和优先级队列确保信号的高效处理,自定义处理器扩展了信号的适用场景。信号与上下文切换和调度器紧密结合,为系统的异常响应和作业控制提供了强大支持,同时通过进程组和会话的广播功能增强了信号的可扩展性。


5. 会话、进程组与作业控制

Eonix内核中的会话和进程组机制是任务管理系统的重要组成部分,为进程的组织管理、信号广播和作业控制提供了基础。会话通过将多个进程组归为一个集合,结合控制终端(Terminal)实现用户与作业的交互。作业控制支持前台/后台作业切换、暂停与恢复等操作,使内核能够高效管理任务协作。

会话的基本结构

会话由Session结构体表示,通过SessionJobControl管理前台进程组和控制终端,使用读写信号量(RwSemaphore)确保多线程安全访问。包含以下核心组件:

  • SID(sid): 由会话领导进程的PID决定,是会话的唯一标识。
  • 领导进程(leader): 会话的创建者,负责初始化会话并管理其生命周期。
  • 前台进程组(foreground): 当前与用户交互的进程组,用户输入和信号优先发送到此进程组。
  • 控制终端(control_terminal): 绑定到会话的终端设备,用于用户与前台进程组的交互。
  • 进程组集合(groups): 存储会话中的所有进程组,支持动态添加和移除。

进程组的基本结构

进程组由ProcessGroup结构体表示,是进程的集合,主要用于信号广播和作业管理。支持成员的动态添加和移除,并通过所属会话与控制终端关联。。每个进程组包含以下部分:

  • 进程组ID(pgid): 用于唯一标识进程组,通常等于其领导进程的PID。
  • 领导进程(leader): 进程组的创建者,负责管理该组的资源和信号。
  • 所属会话(session): 进程组所属的会话,确保跨进程组的协作一致性。
  • 成员进程(processes): 使用BTreeMap存储进程组内的所有进程,支持快速查找和广播信号。

作业控制

作业控制是通过会话的前台进程组和控制终端实现的,支持以下功能:

  1. 前后台作业切换: 通过Session::set_foreground_pgid设置前台进程组,使用户输入和信号优先发送到新的作业。
  2. 信号广播: 通过Session::raise_foreground将信号发送给前台进程组,常用于暂停(SIGSTOP)或恢复(SIGCONT)前台作业。
  3. 终端绑定: 使用Session::set_control_terminal将终端绑定到会话,确保用户输入输出只影响绑定的会话。会话领导进程拥有设置和释放控制终端的权限。

会话与进程组的管理由ProcessList模块实现,新会话通过Session::new创建,进程组通过Session::new_group添加到会话。移除进程组时,如果会话中无其他进程组,会自动解除与控制终端的绑定。ProcessList提供方法查找会话和进程组,如try_find_sessiontry_find_pgroup。信号通过ProcessGroup::raise广播到整个进程组,或通过Session::raise_foreground广播到前台进程组。

会话和进程组与信号机制紧密协作,提供多层次的任务控制能力。会话通过前台进程组分发信号,实现广播,可以控制用户当前交互作业。用户可以通过绑定的终端发送信号(如SIGSTOPSIGCONT)控制作业状态切换。当会话领导进程终止时,内核会自动释放绑定的控制终端,并将会话中所有进程移至后台。

实现中遇到的问题

当前实现中,进程中对于其子线程、子进程,以及会话和进程组中对于所有成员的引用的设计可能会造成不一致。比如我们在移除一个进程的时候,需要将这个进程从其父进程的子进程表中删除,还需要将其从进程组和会话的成员列表中删除。同时在添加进程的时候要注意把这个进程加到这些表里。我们在这些过程当中有多个位置都要注意,一不小心就会导致内核崩溃。想到的一个解决办法就是用侵入式链表改良,将这几个过程合起来,降低复杂性。

还需要实现clone系统调用,用于实现 POSIX 标准中的线程。