This document is a tutorial explaining how the eXpOS kernel must manage the kernel stack. The document is specific to the implementation of eXpOS on the XSM machine. There are three different situations where the eXpOS kernel needs to do careful stack management:
a. A user program invokes a system call
b. An Exception / Interrupt occurs when a user process is executing
c. A kernel module invokes another kernel module
The calling conventions followed here correspond to the one given in the SPL documentation.
Important Note: Whenever the kernel stack pointer of a process is saved in the KPTR field of the process table, the offset of SP register within the user area page will be stored (and not the physical address of the kernel stack pointer). The page number of the user area page is stored in the USER AREA PAGE NUMBER field in the process table. Thus, the value of the offset can be calculated by the fomula offset = SP – (512 * USER AREA PAGE NUMBER).
The purpose of storing the offset (instead of the physical address) in the KPTR field is to allow the OS to relocate the user area page to another physical memory page. Thus, if the user area page is swapped out, it can be swapped back later to a different physical memory page.
An application program must push the input parameters to a system call through the user stack. The return value of the system call is also passed back to the application through the user stack. For the calling conventions followed in eXpOS, see ABI.
If the application program is written in ExpL, then the ExpL compiler will generate code for pushing arguments into the user stack (if you are writing directly in assembly language, then your code must explicitly contain the code to do these). The system call module must be designed so as to access these arguments from the user stack of the process, do the processing required and store the return value into the appropriate place in the user stack before returning to the user mode. The system call module must also change the stack to its own (kernel) stack upon entry and switch back to the user stack at the time of return.
Please note that the description below does not apply to the stack management during context switch which is discussed elsewhere.
a. Push the registers in use to the stack
b. Push the system call number into the stack
c. Push the arguments
d. Push one space for the return value
e. Invoke the INT machine instruction corresponding to the system call
a. Compute the physical address of the top of the user stack.
b. Extract the system call number and the arguments. (Note : The convention is to extract the system call number to register R0, and arguments to R1,R2, and R3 in that order from the caller's stack)
c. Switch the stack from user to kernel
c.1. Transfer the current SP register to User stack pointer field of the process table.
c.2. Compute the physical address of the kernel stack pointer.
c.3. Transfer the physical address of the kernel stack pointer to the SP register.
d. Identify the system call using the system call number and transfer control to the system call code
a. Store the return value in the user stack
b. Pop off all the contents from the kernel stack and store the logical address of the top of kernel stack to the KPTR field of the process table
c. Point the stack pointer (SP) to top of the user stack
d. Return to the user program using the IRET machine instruction
a. Save the return value
b. Pop off the arguments and the system call number from the stack
c. Restore the register context and resume execution.
A hardware interrupt/exception can occur while an application program is executing. It can be the exception handler, timer interrupt routine, disk interrupt routine or the console interrupt routine. Since the application does not have control over the transfer to the interrupt module, it would not have saved its context. Thus in this case the kernel module must save the register context of the application in its own stack before using the registers and must restore the context before returning to the application. The kernel stack is used to store the execution context of the user process. This context is restored before the return from the kernel module. (The backup and restore instructions of the XSM machine facilitate this).
NOTE: If an exception is caused by error conditions ( such as stack overflow, invalid stack pointer value, arithmetic exceptions etc.), the user program will be terminated. This is not dealt with in this tutorial. The tutorial describes the stack management only in the case of page fault exception (since this does not result in termination of the user program).
The XSM machine pushes the return address (IP+2 value) to the user stack before transferring the control to the Interrut Service Routine
a. Switch from the user stack to the kernel stack
b. Save the values of the machine registers in the kernel stack using BACKUP machine instruction.
c. Transfer control to the interrupt routine code
Execution context before an interrupt/exception:
a. Restore the values of the machine registers using RESTORE machine instruction
b. Store the logical address of the current kernel stack top to the KPTR field
c. Set the value of SP to the top of the user stack
d. Transfer control back to the user process
A kernel module or an interrupt service routine can invoke another kernel module while it is executing.
Since the invocation is voluntarily, execution context of the callee should be saved in the kernel stack
before transferring control to the invoked kernel module.
The arguments to the kernel module are passed through the registers R0, R1, R2.
The invoked kernel module also uses the same kernel stack.
The return value will be stored in register R0.
Before returning control to the callee, the invoked module pops off the space used
during its execution, from the stack. The callee restores
the register context upon return before resuming its execution.
Note : RET instruction is used instead of IRET to return back to the kernel module.
a. Save the registers in use to the kernel stack
b. Store the arguments argument_1, argument_2, argument_3,... to R0, R1, R2, ... respectively
c. Transfer control to the kernel module to be executed
a. Pop off the space used during its execution from the kernel stack
a. Use RET instruction to transfer control back to the kernel module which invoked it