🐳
uCore OS(on RISC-V64)实验指导书
  • Introduction
  • LAB0:ready~go!
    • 实验目的
    • 实验内容
    • 前导知识
      • 了解uCore
      • 了解RISC-V
      • 了解OS实验
      • 了解实验环境
      • 了解开发调试基本工具
      • 了解硬件模拟器
    • 配置环境
      • 安装虚拟环境
      • 安装开发工具
      • 安装硬件模拟器
      • 安装调试工具
  • LAB0.5:最小可执行内核
    • 实验目的
    • 实验内容
    • 练习
    • 内存布局
    • 链接脚本
    • 真正的入口点
    • 从SBI到stdio
    • 编译运行
    • 项目组成与执行流
  • LAB1:中断机制
    • 实验目的
    • 实验内容
    • 练习
    • RISC-V中断相关
    • 上下文处理
    • 中断处理程序
    • 时钟中断
    • 项目组成与执行流
  • LAB2:物理内存管理
    • 实验目的
    • 实验内容
    • 练习
    • 地址与页表
    • 物理内存探测
    • 以页为单位管理物理内存
    • 页面分配算法
    • 项目组成与执行流
  • LAB3:虚拟内存管理
    • 实验目的
    • 实验内容
    • 练习
    • 页面置换
    • PageFault
    • 使用多级页表
    • 页面置换机制
    • FIFO置换算法
    • 项目组成与执行流
  • LAB4:进程管理
    • 实验目的
    • 实验内容
    • 练习
    • 进程与线程
    • 相关结构体
    • 进程模块初始化
    • 进程切换
    • 项目组成与执行流
  • LAB5:用户程序
    • 实验目的
    • 实验内容
    • 练习
    • 用户进程
    • 用户程序
    • 创建并执行用户进程
    • 系统调用
    • 用户进程的退出和等待
    • 项目组成与执行流
  • LAB6:进程调度
    • 实验目的
    • 实验内容
    • 练习
    • 进程状态
    • 再次认识进程切换
    • 调度算法框架
    • 项目组成与执行流
  • LAB7:同步互斥
    • 实验目的
    • 实验内容
    • 练习
    • 同步互斥的基本概念
    • 信号量
    • 条件变量与管程
    • 项目组成与执行流
  • LAB8:文件系统
    • 实验目的
    • 实验内容
    • 练习
    • 文件系统介绍
    • 文件系统抽象层VFS
    • 硬盘文件系统SFS
    • 设备即文件
    • 从中断到终端
    • 项目组成与执行流
由 GitBook 提供支持
在本页

这有帮助吗?

  1. LAB4:进程管理

进程切换

在ucore完成初始化后,ucore内核就没事做了,于是进入了“idle”的状态(这也是我们给第0个进程起名叫idle的原因)。这是,它会陷入死循环,不断检查自己是否需要调度:

void
cpu_idle(void) {
    while (1) {
        if (current->need_resched) {
            schedule();
            ......

因为我们之前在初始化中把idle进程的need_resched设为了1,所以其总会调用schedule函数来检查是否有进程可以调度。我们已经初始化了另外一个内核进程,所以调度器发现了这个进程,并且准备调度到这个进程。

我们实现的schedule函数非常的简单:当需要调度的时候,把当前的进程放在队尾,从队列中取出第一个可以运行的进程,切换到它运行。这就是FIFO调度算法。schedule函数会调用proc_run来唤醒选定的进程。proc_run函数内部如下:

void
proc_run(struct proc_struct *proc) {
    if (proc != current) {
        bool intr_flag;
        struct proc_struct *prev = current, *next = proc;
        local_intr_save(intr_flag);
        {
            current = proc;
            lcr3(next->cr3);
            switch_to(&(prev->context), &(next->context));
        }
        local_intr_restore(intr_flag);
    }
}

函数中主要进行了三个操作:

  1. 将当前运行的进程设置为要切换过去的进程

  2. 将页表换成新进程的页表

  3. 使用switch_to切换到新进程

switch_to函数如下:

.text
# void switch_to(struct proc_struct* from, struct proc_struct* to)
.globl switch_to
switch_to:
    # save from's registers
    STORE ra, 0*REGBYTES(a0)
    STORE sp, 1*REGBYTES(a0)
    STORE s0, 2*REGBYTES(a0)
    STORE s1, 3*REGBYTES(a0)
    STORE s2, 4*REGBYTES(a0)
    STORE s3, 5*REGBYTES(a0)
    STORE s4, 6*REGBYTES(a0)
    STORE s5, 7*REGBYTES(a0)
    STORE s6, 8*REGBYTES(a0)
    STORE s7, 9*REGBYTES(a0)
    STORE s8, 10*REGBYTES(a0)
    STORE s9, 11*REGBYTES(a0)
    STORE s10, 12*REGBYTES(a0)
    STORE s11, 13*REGBYTES(a0)

    # restore to's registers
    LOAD ra, 0*REGBYTES(a1)
    LOAD sp, 1*REGBYTES(a1)
    LOAD s0, 2*REGBYTES(a1)
    LOAD s1, 3*REGBYTES(a1)
    LOAD s2, 4*REGBYTES(a1)
    LOAD s3, 5*REGBYTES(a1)
    LOAD s4, 6*REGBYTES(a1)
    LOAD s5, 7*REGBYTES(a1)
    LOAD s6, 8*REGBYTES(a1)
    LOAD s7, 9*REGBYTES(a1)
    LOAD s8, 10*REGBYTES(a1)
    LOAD s9, 11*REGBYTES(a1)
    LOAD s10, 12*REGBYTES(a1)
    LOAD s11, 13*REGBYTES(a1)

    ret

可以看出来这段代码就是将需要保存的寄存器进行保存和调换。在之前我们也已经谈到过了,这里只需要调换被调用者保存寄存器即可。由于我们在初始化时把上下文的ra寄存器设定成了forkret函数的入口,所以这里会返回到forkret函数,进一步进入到forkrets。forkrets函数很短:

    .globl forkrets
forkrets:
    # set stack to this new process's trapframe
    move sp, a0
    j __trapret

这里把传进来的参数,也就是进程的中断帧放在了sp,这样在__trapret中就可以直接从中断帧里面恢复所有的寄存器啦!我们在初始化的时候对于中断帧做了一点手脚,epc寄存器指向的是kernel_thread_entry,s0寄存器里放的是新进程要执行的函数,s1寄存器里放的是传给函数的参数。在kernel_thread_entry函数中:

.text
.globl kernel_thread_entry
kernel_thread_entry:        # void kernel_thread(void)
    move a0, s1
    jalr s0

    jal do_exit

我们把参数放在了a0寄存器,并跳转到s0执行我们指定的函数!这样,一个进程的初始化就完成了。至此,我们实现了基本的进程管理,并且成功创建并切换到了我们的第一个内核进程。

上一页进程模块初始化下一页项目组成与执行流

最后更新于4年前

这有帮助吗?