|
|
|
@ -957,7 +957,7 @@ lab1_1实验需要读者了解和掌握操作系统中系统调用机制的实
|
|
|
|
|
27 }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
我们发现,do_user_call函数是通过ecall指令完成系统调用的,且在执行ecall指令前,所有的参数(即do_user_call函数的8个参数)实际上都已经载入到RISC-V机器的a0到a7这8个寄存器中(这一步是我们的编译器生成的代码帮我们完成的)。ecall指令的执行将根据a0中的值获得系统调用号,并使RISC-V转到S模式(因为我们的操作系统内核启动时将所有的中断、异常、系统调用都代理给了S模式)的trap处理入口执行(在kernel/strap_vector.S文件中定义):
|
|
|
|
|
我们发现,do_user_call函数是通过ecall指令完成系统调用的,且在执行ecall指令前,所有的参数(即do_user_call函数的8个参数)实际上都已经载入到RISC-V机器的a0到a7这8个寄存器中(这一步是我们的编译器生成的代码帮我们完成的)。ecall指令的执行将根据a0寄存器中的值获得系统调用号,并使RISC-V转到S模式(因为我们的操作系统内核启动时将所有的中断、异常、系统调用都代理给了S模式)的trap处理入口执行(在kernel/strap_vector.S文件中定义):
|
|
|
|
|
|
|
|
|
|
```assembly
|
|
|
|
|
16 .globl smode_trap_vector
|
|
|
|
@ -984,7 +984,13 @@ lab1_1实验需要读者了解和掌握操作系统中系统调用机制的实
|
|
|
|
|
37 jr t0
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
从以上代码我们可以看到,trap的入口处理函数首先将“进程”(即我们的obj/app_helloworld的运行现场)进行保存(第24行);接下来将a0寄存器中的系统调用号保存到内核堆栈(第27--28行),再将p->trapframe->kernel_sp指向的为应用进程分配的内核栈设置到sp寄存器(第31行,即切换堆栈,而不使用PKE内核自己的栈,**这里请读者思考为何要这样安排?**),后续的执行将使用应用进程所附带的内核栈来保存执行的上下文,如函数调用、临时变量这些;最后,将应用进程中的p->trapframe->kernel_trap写入t0寄存器(第34行),并最后(第37行)调用p->trapframe->kernel_trap所指向的smode_trap_handler()函数。
|
|
|
|
|
从以上代码我们可以看到,trap的入口处理函数首先将“进程”(即我们的obj/app_helloworld的运行现场)进行保存(第24行);接下来将a0寄存器中的系统调用号保存到内核堆栈(第27--28行),再将p->trapframe->kernel_sp指向的为应用进程分配的内核栈设置到sp寄存器(第31行),**该过程实际上完成了栈的切换**。完整的切换过程为:
|
|
|
|
|
|
|
|
|
|
- 1)应用程序在U模式即应用态执行,这个时候是使用的操作系统为其分配的栈(称为用户栈),这一部分参见`kernel/kernel.c`文件中`load_user_program`的实现;
|
|
|
|
|
- 2)应用程序调用ecall,后陷入内核,开始执行`smode_trap_vector`函数,此时使用的是操作系统内核的栈。参见`kernel/machine/mentry.S`文件中的PKE入口`_mentry`;
|
|
|
|
|
- 3)中断处理例程`smode_trap_vector`函数执行到第31行时,将栈切换到用户进程“自带”的“用户内核栈“,也就是`kernel/process.c`文件中`switch_to`函数的第35行所引用的`proc->kstack`,而不使用PKE内核自己的栈,**这里请读者思考为何要这样安排**?
|
|
|
|
|
|
|
|
|
|
后续的执行将使用应用进程所附带的内核栈来保存执行的上下文,如函数调用、临时变量这些;最后,将应用进程中的p->trapframe->kernel_trap写入t0寄存器(第34行),并最后(第37行)调用p->trapframe->kernel_trap所指向的smode_trap_handler()函数。
|
|
|
|
|
|
|
|
|
|
smode_trap_handler()函数的定义在kernel/strap.c文件中,采用C语言编写:
|
|
|
|
|
|
|
|
|
@ -1051,7 +1057,7 @@ handle_syscall()函数的定义也在kernel/strap.c文件中:
|
|
|
|
|
|
|
|
|
|
但是,做实验的时候,需要读者思考在handle_syscall()函数中调用do_syscall()函数,后者的参数怎么办?毕竟有8个long类型(因为我们的机器是RV64G,long类型占据8个字节)的参数,另外,do_syscall()函数的返回值怎么处理?毕竟do_syscall()函数有一个long类型的返回值,而这个返回值是要通知应用程序它发出的系统调用是否成功的。
|
|
|
|
|
|
|
|
|
|
除了实验内容之外,在handle_syscall()函数的第19行,有一个`tf->epc += 4;`语句,**这里请读者思考为什么要将tf->epc的值进行加4处理?**这个问题请结合你对RISC-V指令集架构的理解,以及系统调用的原理回答。
|
|
|
|
|
除了实验内容之外,在handle_syscall()函数的第19行,有一个`tf->epc += 4;`语句,**这里请读者思考为什么要将tf->epc的值进行加4处理?**这个问题请结合你对RISC-V指令集架构的理解,以及系统调用的原理回答。另外,**我们的PKE操作系统内核是如何得到应用程序中“hello world!”字符串的地址的呢**?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|