NJU ICS2024 PA 作业心得(四)
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怎么修改?
kcontext通过修改一个
kstack结构中的信息来初始化一个新的Context。所以我们需要先创造一个这样大小的空间,并将其中的sp指针指向栈底,同时根据ABI手册要求,将参数覆给a0寄存器。同时设置好mepc寄存器指向当前的调用入口位置,并为了使得通过difftest检验,设置mstatus为0x1800。至于
__am_asm_trap,按照要求在call指令后,将sp寄存器设置为a0寄存器的内容进行恢复即可。
怎么在Nanos-lite实现上下文切换?
在context_kload直接利用kcontext函数创建上下文即可。
schedule函数中保存好当前执行上下文状态,再调度到另一个上下文即可。
为了实现载入用户程序,我们需要实现context_uload,它需要实现下面这些功能:
- 为即将执行的用户程序分配内存页面。
- 加载指定的程序(
filename)并获取其入口地址。 - 设置用户进程的上下文,包括栈、地址空间、入口地址等。
- 处理命令行参数(
argv)和环境变量(envp),将它们复制到内存,并更新pcb(进程控制块)中的相关信息。 - 最终,将构建好的上下文(包括参数等)保存到
pcb中,为进程的启动做好准备。
为了验证PAL试运行在用户栈而不是内核栈,一个非常简单的方法,是验证执行该程序时,sp指针是不是位于AM中划分好的用户空间 RANGE(0x40000000, 0x80000000)。
一山不能藏二虎?
在 navy-apps/scripts/$ISA.mk 中,程序被编译时会指定一个固定的 链接地址,即所有程序的代码和数据都会被链接到 0x83000000 这个内存地址。这个地址被用作每个程序的 加载地址。因此,当你加载第二个用户进程时,第二个进程会把第一个进程的内存空间完全覆盖,导致第一个进程的代码和数据丢失,或者发生不可预期的行为。
为什么少了一个const?
这个差异主要反映了 main() 和 execve() 在处理命令行参数和环境变量时的不同语义:
main()函数中的argv和envp是可修改的:在main()中,程序启动时传入的命令行参数和环境变量可能会被修改。例如,argv[0]可能会被修改为程序的名称,或者你可能会修改环境变量envp。所以,main()中的argv和envp没有被声明为const,允许对它们进行修改。execve()函数中的argv和envp是常量数组:execve()是用来执行一个新的程序,它的参数(argv和envp)指向的内容不应该被修改。执行一个新程序时,操作系统会用这些参数来设置新的进程环境。如果允许修改argv或envp,可能会导致新的程序的启动环境不一致或错误。因此,execve()将argv和envp声明为char *const [],以禁止修改指向这些字符串数组的指针。
