跳至主要内容

muxOS 开发日志(三)—— 进程

1.1 什么是进程?

进程是操作系统中的一个核心概念,它代表了计算机中程序的一次执行过程。在操作系统中,进程是资源分配和调度的基本单位,是程序的实体。进程不仅包含程序代码,还包括正在执行的活动,如程序计数器的值和处理器寄存器的内容。

1.2 进程的数据结构

context_t — 寄存器上下文
字段 类型 偏移 说明
esp uint32_t +0 内核栈指针(pusha 帧顶 或 C 栈帧)
ebp uint32_t +4 用户栈顶(用户进程)/ 基址指针
ebx uint32_t +8 callee-saved
esi uint32_t +12 callee-saved
edi uint32_t +16 callee-saved
process_t — 进程控制块(sizeof = 40)
字段 类型 偏移 说明
pid uint32_t +0 进程 ID,0 = kernel_main
ctx context_t +4 寄存器上下文(20字节)
state uint32_t +24 进程状态(1 = 运行)
started uint32_t +28 是否已首次运行(0=未启动,1=已启动)
kernel_stack uint32_t +32 内核栈顶地址
sleep_ticks uint32_t +36 剩余睡眠 tick 数,0 = 可调度

1.3 介绍

进程初始化时,首先初始化一个运行的、已启动的、pid为0的进程。随即创建一个内核态进程执行 task_kernel_init 函数。

MuxOS 目前采用时间片轮转调度,计时器每 tick 执行一次 irq0_stub。当执行 irq0_stub 时:

  1. 首先,会保存当前所有的寄存器数据(pusha
  2. 随即向 PIC 发送 EOI
  3. 保存当前执行的进程索引后执行 process_tick

process_tick 首先会递减所有进程的 sleep_ticks,并获取第一个未休眠的进程索引。

当符合以下情况时,不会执行下一个进程(返回 -1):

  • 所有进程都不可运行(全部在睡眠或只有 pid=0)
  • 下一个可运行进程就是当前进程

进程切换逻辑

  1. 保存旧进程的 esp 到 processes[old].ctx.esp
  2. 如果新进程 started=0,标记为 started=1
  3. 切换 esp 到新进程的内核栈
  4. 如果返回用户态(CS RPL=3),恢复用户段寄存器
  5. popa 恢复寄存器,iret 返回到新进程

在 muxOS 中,提供了一些基础函数用来操作进程:

1
2
3
4
5
6
7
8
9
void process_schedule();              // 主动调度函数(内核进程让出CPU时使用,非IRQ0路径)
int process_tick(); // IRQ0中断调用的调度核心:递减sleep_ticks,返回下一个可运行进程索引(-1表示不切换)
void process_create_kernel(void (*entry)()); // 创建内核态进程(ring 0),构造初始pusha+iret帧
void process_create_user(void (*entry)()); // 创建用户态进程(ring 3),分配代码页/用户栈/内核栈
void process_register_current(); // 将kernel_main注册为pid=0的进程(系统启动时调用一次)
void start_user_process(int pid); // 通过enter_usermode直接启动指定用户进程(设置TSS并跳转到ring 3)
void process_sleep(uint32_t ticks); // 设置当前进程的sleep_ticks,下次调度时会被跳过
void process_exit(); // 终止当前进程:从进程表删除,通过process_jump跳到下一个进程
int process_fork(uint32_t child_eax_ret); // fork当前进程:复制代码/栈/内核栈,返回子进程pid(子进程中返回0)

关于进程的介绍,请参考 加载进程 - OSDev 维基

关于本文

由 Latos 撰写,采用 CC BY-NC 4.0 许可协议。

#OS #muxOS #C