Skip to content

process模块(独立模块)

我们实现了一个独立模块process,它是操作系统无关的,可以作为一个基础组件提供给其他操作系统使用,没有依赖ArceOS或者UndefinedOS中的组件。

在Linux中,进程的组织结构包括session, process group, process这几个层次,POSIX thread属于特殊的process。此外,进程之间还存在父子关系。

在我们的设计中,我们认为调度的基本单元是线程,我们据此设计了操作系统无关的线程和进程模型,旨在提供一个通用的包,来提供进程和线程管理操作,基本覆盖了POSIX的所有进程/线程操作,如:

  • 会话/进程组/进程/线程创建
  • 线程/进程退出(包括exit_group)和收养
  • 更改进程所在的进程组
  • pid/tid查询和管理
  • 进程fork和创建新线程

细节说明

会话/进程组/进程/线程创建

static THREAD_TABLE: Mutex<BTreeMap<Pid, Arc<Thread>>> =
    Mutex::new(BTreeMap::<Pid, Arc<Thread>>::new());

同一层次间,我们使用一张如上所示的表来管理。对于创建会话/进程组/进程/线程,每一层的实现相仿。出于通用性考量,创建新的会话/进程组/进程/线程的接口在模块内部公开,只需提供指定的 id 、parent 、所属上级结构即可调用。

这里以创建一个新的进程为例进行解释。

static PROCESS_TABLE: Mutex<BTreeMap<Pid, Arc<Process>>> =  Mutex::new(BTreeMap::<Pid, Arc<Process>>::new());

/// Create a new process if the process does not exist
fn create_process(pid: Pid, parent: Weak<Process>, group: Weak<ProcessGroup>) -> Arc<Process> {
    let mut process_table = PROCESS_TABLE.lock();
    if process_table.contains_key(&pid) {
        panic!("[process] process with id {} already exists", pid);
    }
    // create process
    let process = Process::new(pid, parent.clone(), group.clone());
    // add to process group
    let group = group.upgrade().unwrap();
    group.add_process(process.clone());
    // add to parent
    if let Some(parent) = parent.upgrade() {
        parent.children.lock().insert(pid, process.clone());
    }
    // create main thread
    create_thread(pid, Arc::downgrade(&process));
    // add to process table
    process_table.insert(pid, process.clone());
    process
}

调用 create_process 所需参数为 分配给新进程的 pid ,指向父进程的指针、指向新进程的 process group 的指针。

调用 create_process 后首先实例化一个新的 Process 结构体,将其与指定pid关联,加入全局进程表与指定的 process group 中,随后调用 create_thread ,为这个新的进程创建一个线程 。

进程Fork和线程创建

进程 fork 通过调用 create_process 实现,发起 fork 的进程调用 create_process 时需指定 parent 为自己,子进程与父进程属于同一个进程组,新创建出的子进程只有一个主线程。

线程/进程退出(包括exit_group)和收养

我们维护进程之间的父子关系,进程 fork 时成为新进程的父进程,并且实现了收养机制。

具体来说:

  • 线程退出调用 Process::remove_thread更新线程表,最后一个线程退出触发进程退出
  • 进程退出时通过 is_zombie原子标志位标记为僵尸状态,将自己从所属进程组移除
  • 空进程组自动从会话中移除
  • 空会话自动从全局会话表移除
  • 进程退出时其子进程由最近的收养者或是init进程收养
  • 线程调用 exit_group 先自己 exit ,然后向其他线程发送信号,使所有线程退出

pid/tid查询和管理

static NEXT_PID: AtomicU32 = AtomicU32::new(1);

fn generate_next_pid() -> Pid {
    NEXT_PID.fetch_add(1, Ordering::Acquire)
}
  • 对于session, process group, process这几个层次结构,我们实现了和linux一致的 main thread 、process group leader 、session leader 等
  • process id 实际上就是主线程的 thread id , process group id 等于其 leader 的 process id , session id 等于其 leader 的 process group id
  • 由于 process id 实际上就是主线程的 thread id,所以我们通过同一个原子计数器保证全局唯一PID/TID分配
  • 进程/线程生命周期与ID绑定,对象销毁时释放ID

更改进程所在的进程组

实现上非常简单,先在旧的进程组中移除某进程,然后将其添加到新的进程组中,但需要遵循约束条件

  • session leader 无法迁移进程组
  • 目标进程组必须存在于当前会话中,不能跨会话迁移进程组
  • 创建新的进程组时一定有 pgid == pid ,即进程组id等于创建它的进程的id

process data 与 thread data

// 解耦后的访问方式(无直接指针)
let process = get_process(pid); // 从 PROCESS_TABLE 获取
let data = get_process_data(pid); // 从 PROCESS_DATA_TABLE 获取

我们将 process data 单独作为一个结构体 ,其中不包含对应的process的指针,反过来,Process 结构体中也不包含对应的 process data 的指针。

对于 thread data 和 thread 作相同处理。(此外,我们通过thread data 获取对应的ProcessData。)

pub struct TaskExt {
    /// The time statistics.
    pub time: RefCell<TimeStat>,
    /// The POSIX thread corresponding to this task.
    pub thread: Arc<Thread>,
    /// The thread data bind to this task.
    pub thread_data: Arc<ThreadData>,
}

pub fn current_thread() -> Arc<Thread> {
    current().task_ext().thread.clone()
}

pub fn current_thread_data() -> Arc<ThreadData> {
    current().task_ext().thread_data.clone()
}

pub fn current_process() -> Arc<Process> {
    current_thread().get_process()
}

pub fn current_process_data() -> Arc<ProcessData> {
    current_thread_data().process_data.clone()
}

这样设计,我们通过pid分别可以在 PROCESS_TABLE 和 PROCESS_DATA_TABLE 中分别获取 Process 和 ProcessData ,这样解耦合后可以将 Process 相关部分独立出来作为一个单独的 crate 。