深入理解nuta/operating-system-in-1000-lines中的进程管理实现

深入理解nuta/operating-system-in-1000-lines中的进程管理实现

operating-system-in-1000-lines Writing an OS in 1,000 lines. operating-system-in-1000-lines 项目地址: https://gitcode.com/gh_mirrors/op/operating-system-in-1000-lines

进程基础概念

在操作系统中,进程是最基本的执行单元。每个进程都拥有独立的执行上下文和资源,如虚拟地址空间。nuta/operating-system-in-1000-lines项目采用了一种简化的设计理念,将每个进程视为单线程执行单元,这与现代操作系统将线程作为独立执行上下文的设计有所不同,但非常适合教学目的。

进程控制块(PCB)设计

进程控制块是操作系统管理进程的核心数据结构。在项目中,它被定义为:

struct process {
    int pid;             // 进程ID
    int state;           // 进程状态
    vaddr_t sp;          // 栈指针
    uint8_t stack[8192]; // 内核栈
};

这个结构体包含了几个关键元素:

  • pid:唯一标识进程
  • state:记录进程状态(未使用或可运行)
  • sp:保存进程的栈指针
  • stack:为每个进程分配的8KB内核栈空间

这种为每个进程分配独立内核栈的设计,使得上下文切换可以通过简单地保存和恢复CPU寄存器以及切换栈指针来实现。

上下文切换机制

上下文切换是进程管理的核心操作,项目中的实现非常精妙:

__attribute__((naked)) void switch_context(uint32_t *prev_sp, uint32_t *next_sp) {
    __asm__ __volatile__(
        // 保存当前进程的寄存器到栈
        "addi sp, sp, -13 * 4\n"
        "sw ra,  0  * 4(sp)\n"
        // ... 保存其他寄存器
        // 切换栈指针
        "sw sp, (a0)\n"
        "lw sp, (a1)\n"
        // 从新进程栈恢复寄存器
        "lw ra,  0  * 4(sp)\n"
        // ... 恢复其他寄存器
        "addi sp, sp, 13 * 4\n"
        "ret\n"
    );
}

这个实现有几个值得注意的技术点:

  1. 只保存被调用者保存的寄存器(callee-saved registers),因为调用者保存的寄存器已经由调用者负责保存
  2. 使用naked属性确保编译器不会生成额外的代码
  3. 通过直接操作栈指针实现上下文保存和恢复

进程创建与初始化

进程创建函数create_process负责初始化进程控制块:

struct process *create_process(uint32_t pc) {
    // 查找空闲进程控制块
    struct process *proc = NULL;
    for (int i = 0; i < PROCS_MAX; i++) {
        if (procs[i].state == PROC_UNUSED) {
            proc = &procs[i];
            break;
        }
    }
    
    // 初始化栈和寄存器状态
    uint32_t *sp = (uint32_t *) &proc->stack[sizeof(proc->stack)];
    *--sp = 0; // 初始化各寄存器
    *--sp = (uint32_t) pc; // 设置返回地址
    
    // 初始化进程控制块字段
    proc->pid = i + 1;
    proc->state = PROC_RUNNABLE;
    proc->sp = (uint32_t) sp;
    return proc;
}

这个函数的关键在于正确设置进程的初始栈布局,使得第一次上下文切换时能够正确跳转到指定的入口点。

调度器实现

项目实现了一个简单的轮转调度器:

void yield(void) {
    // 查找下一个可运行进程
    struct process *next = idle_proc;
    for (int i = 0; i < PROCS_MAX; i++) {
        struct process *proc = &procs[(current_proc->pid + i) % PROCS_MAX];
        if (proc->state == PROC_RUNNABLE && proc->pid > 0) {
            next = proc;
            break;
        }
    }
    
    // 执行上下文切换
    struct process *prev = current_proc;
    current_proc = next;
    switch_context(&prev->sp, &next->sp);
}

调度器的工作流程:

  1. 从当前进程开始搜索下一个可运行进程
  2. 如果找不到其他可运行进程,则继续运行当前进程
  3. 执行上下文切换到选中的进程

异常处理与内核栈管理

项目对异常处理进行了特殊处理,确保在用户态异常时能正确切换到内核栈:

void kernel_entry(void) {
    __asm__ __volatile__(
        "csrrw sp, sscratch, sp\n" // 切换到内核栈
        // 保存寄存器状态
        // ...
        // 恢复原始栈指针并保存
        "csrr a0, sscratch\n"
        "sw a0,  4 * 30(sp)\n"
        // 重置内核栈指针
        "addi a0, sp, 4 * 31\n"
        "csrw sscratch, a0\n"
        // 调用异常处理函数
        "mv a0, sp\n"
        "call handle_trap\n"

这种设计解决了用户态异常时栈指针不可信的问题,通过sscratch寄存器保存可信的内核栈地址。

安全考量

项目特别考虑了安全性问题,尤其是防止用户态程序通过操纵栈指针导致内核崩溃。通过在异常处理开始时立即切换到内核栈,避免了用户态恶意设置栈指针带来的风险。

总结

nuta/operating-system-in-1000-lines项目的进程管理实现虽然精简,但涵盖了操作系统进程管理的核心概念:

  1. 进程控制块的组织
  2. 上下文切换机制
  3. 进程调度策略
  4. 异常处理与内核栈管理

这种实现方式非常适合教学目的,通过最简化的代码展示了操作系统进程管理的基本原理。对于希望深入理解操作系统工作原理的学习者来说,这个项目提供了一个极好的起点。

operating-system-in-1000-lines Writing an OS in 1,000 lines. operating-system-in-1000-lines 项目地址: https://gitcode.com/gh_mirrors/op/operating-system-in-1000-lines

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方蕾嫒Falcon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值