* the size unit of pgalloc_f() is byte
* set as->area to the uvm address space to let OS determine where the
user stack is located
* change c->as to store as->ptr
* do not switch to NULL address space, just return
* _kcontext() should set c->ptr to NULL
* let _map() check whether va and pa are aligned by PGSIZE
* To support stack switching from user to kernel with a software-based
method, we need the following
(1) something to identify whether the trap comes from user or kernel
space
(2) the kernel stack to switch to
(3) something to identify whether the trap returns to user or kernel
space
(4) the user stack to resume to
* In mips32,
+ We use $k0 to achieve (1) + (2). When a trap is taken, first examine
$k0. If it is 0, the trap comes from kernel space and there is no
need to perform stack switching. If it is not 0, it must store the
kernel stack to switch to. Then we set $k0 to 0 to support nested
traps.
+ We use $sp's saving slot in the context structure to achive (3) +
(4). If it is 0, the trap is going to return to kernel and there is
no need to perform stack switching. Also there is no need to update
$k0 since it is still 0. If it is not 0, it must store the user
stack to switch to. Before switching back to user stack, we should
also set $k0 to the current $sp as the kernel stack. At the next
time a trap is taken, we will switch to such $sp.
+ The remaining is to correct initialize $k0 and $sp's saving slot.
* riscv32 is similar to mips32, except that we use sscratch to achieve
(1) + (2)
* A real x86 uses DPL + TSS to achieve (1) + (2), and iret to achieve
(3) + (4). But this is complicated for students. Therefore we use a
nearly software-based solution.
+ We use a global variable __am_ksp to achieve (1) + (2), and a new
member `usp` which next to `eip` in the context structure to achieve
(3) + (4).
+ We will still push part of context on the user stack before we
switch to the kernel stack when a trap is taken. There is no way to
avoid this, since x86 leverages hardware stack. We should move the
values pushed on the user stack to the kernel stack after we switch
to it, and store the stack pointer right before the trap is taken to
`usp`, pretending the whole context is pushed totally on the kernel
stack.
+ Trouble comes when we are going to return to user space. Switching
back to user stack and popping eflags/cs/eip should be performed
atomically. We can never achieve this with any software-based
solution. Therefore we modify the behavior of the iret instruction
to a customized version:
temp <- pop();
standard_iret();
if (temp != 0) esp <- temp;
This is why we require the new `usp` member should be next to `eip`
in the context structure.
+ Note that the customized version of iret still does not write
memory. Thus it will not hurt the process of DiffTest in NEMU by
using `difftest_skip_ref()` to let QEMU skip the iret instruciton
and perform register synchronization. But we need to synchronize
eflags, too.
* To run apps in PA3, we have the following convention
* TRM should guaruntee the address of 0x3000000/0x83000000 is
avaliable.
* Navy-apps will link apps to this address.
* Nanos-lite use naive_uload() to directly call `_start()` in libc
with the AM boot stack.
* `_start` in libc should initialize the frame pointer, then directly
calls `call_main()`.
* `call_main()` does not parse arguments on the stack, and set up
empty argc/argv/envp, then call `main()`. Therefore we do not
support passing arguments to `main()` at this time.
* Exceptions are handled on the AM boot stack.
* Now native and *-nemu all support naive_uload().
* setcontext() will only restore those which will be saved in getcontext()
* Signal handler will atomically restore everything in ucontext. This
greatly simplifies the trap code, and also makes it possible to return
to user space while switching to the user stack.