NJU ICS2024 PA 作业心得(四)

less than 1 minute read

Published:

深入探讨多道程序处理中的上下文切换实现,分析系统调用、进程调度与内存管理的核心机制,并对比 main() 和 execve() 参数设计的差异。

多道程序问题处理

在系统调用的实现中,当进程A触发了ecall指令并陷入到内核模式时,A的上下文结构会通过__am_asm_trap()保存到A的栈上。系统调用处理完毕后,__am_asm_trap()会根据保存的上下文来恢复A的执行状态。然而,若要实现进程之间的切换,在系统调用完成后,__am_asm_trap()不立即恢复A的上下文,而是先切换到另一个进程B的上下文。由于B的栈上已保存了其上下文信息,因此接下来的操作将恢复B的上下文。当__am_asm_trap()返回时,进程B开始执行。

kcontext怎么实现和__am_asm_trap怎么修改?

  1. kcontext通过修改一个 kstack结构中的信息来初始化一个新的Context。所以我们需要先创造一个这样大小的空间,并将其中的sp指针指向栈底,同时根据ABI手册要求,将参数覆给a0寄存器。同时设置好mepc寄存器指向当前的调用入口位置,并为了使得通过difftest检验,设置mstatus0x1800

  2. 至于__am_asm_trap,按照要求在call指令后,将sp寄存器设置为a0寄存器的内容进行恢复即可。

怎么在Nanos-lite实现上下文切换?

context_kload直接利用kcontext函数创建上下文即可。

schedule函数中保存好当前执行上下文状态,再调度到另一个上下文即可。

为了实现载入用户程序,我们需要实现context_uload,它需要实现下面这些功能:

  1. 为即将执行的用户程序分配内存页面。
  2. 加载指定的程序(filename)并获取其入口地址。
  3. 设置用户进程的上下文,包括栈、地址空间、入口地址等。
  4. 处理命令行参数(argv)和环境变量(envp),将它们复制到内存,并更新 pcb(进程控制块)中的相关信息。
  5. 最终,将构建好的上下文(包括参数等)保存到 pcb 中,为进程的启动做好准备。

为了验证PAL试运行在用户栈而不是内核栈,一个非常简单的方法,是验证执行该程序时,sp指针是不是位于AM中划分好的用户空间 RANGE(0x40000000, 0x80000000)

一山不能藏二虎?

navy-apps/scripts/$ISA.mk 中,程序被编译时会指定一个固定的 链接地址,即所有程序的代码和数据都会被链接到 0x83000000 这个内存地址。这个地址被用作每个程序的 加载地址。因此,当你加载第二个用户进程时,第二个进程会把第一个进程的内存空间完全覆盖,导致第一个进程的代码和数据丢失,或者发生不可预期的行为。

为什么少了一个const?

这个差异主要反映了 main()execve() 在处理命令行参数和环境变量时的不同语义:

  • main() 函数中的 argvenvp 是可修改的:在 main() 中,程序启动时传入的命令行参数和环境变量可能会被修改。例如,argv[0] 可能会被修改为程序的名称,或者你可能会修改环境变量 envp。所以,main() 中的 argvenvp 没有被声明为 const,允许对它们进行修改。
  • execve() 函数中的 argvenvp 是常量数组execve() 是用来执行一个新的程序,它的参数(argvenvp)指向的内容不应该被修改。执行一个新程序时,操作系统会用这些参数来设置新的进程环境。如果允许修改 argvenvp,可能会导致新的程序的启动环境不一致或错误。因此,execve()argvenvp 声明为 char *const [],以禁止修改指向这些字符串数组的指针。