CS 452/652 Winter 2024 - Lecture 5
ABI, Coding, Kernel
Jan 23, 2024 prev next
Application Binary Interface (ABI)
- subroutine call is a simple context switch (new stack frame)
- coroutine call is similar to task switch (but deterministic)
- ABI standardized to support separate compilation units
- some hardware specifications, mostly software/compiler convention
- important (for us): registers preserved during function call (caller-owned, callee-saved)
- argument passing: registers and stack, stack alignment (16)
- x0-x7 arguments, return x0/x1 (caller-saved)
- x8 return ptr (instead of x0) for structs > 16 bytes by value
- set up by caller, register itself caller-saved
- x9-x15 local registers (caller-saved)
- x16,x17 temporary for linker (caller-saved)
- x18 platform (thread-local storage?) (caller-saved)
- x19-x28 global registers (callee-saved)
- x29 frame pointer (callee-saved)
- x30 link register (return address, callee-saved)
- pstate (flags) is undefined on entry/return of subroutine
- floating point registers → do not use!
→ can use ABI rules when implementing (synchronous) system call!
Coding: Mixing C/C++ and Assembler
See various examples file in demo05. Take a look at the compiled .s source
- main.c, adder.c - basic argument passing in x0, x1
- main.c, asm_adder.S - manual assembler routine working with C caller
- adder10.c - passing more than 8 arguments (on stack)
- main3.c, data_adder.c - memory-based data exchange between C and assembler
- asm.c - embed assembler in C code; exchange data with local variables
special register access: msr
/mrs
Recommendations
- use embedded assembler via asm only for short code sequences that do not access stack
- use assembler routine linked as C routine for context-switch code
Kernel Design
- no stack (use SP_EL0, unsafe)
- temp stack - kernel as event handler
- single stack - default here
- per-task kernel stack - preemptive time-slicing kernel
- often with return to task after system call
- extra benefit: lazy context-switch (even on interrupt)
- system call: caller-saved done by compiler
- interrupt: save only caller-saved
- continue on per-task kernel stack: callee-saving done by compiler
- stack switch (in kernel): save only callee-saved
Context Switch (details)
- After SVC
- handler in exception vector executes
- processor in EL1
- ELR_EL1 holds return address
- ESR_EL1 contains N from SVC
- SPSR holds the processor state from before SVC
- SP is banked SP_EL1 (run with SPSel set to 1)
- special access to pstate bits via MSR/MRS (APG Section 6.5.2)
- C/C++ function call: compiler has saved x0-x18 (as needed)
- SVC Exception Handler
- must save ELR (and later SPSR for IRQ)
- temporary access to SP_EL0 using SPSel
- has access to x0-x30
- choices for storing user context:
- on user stack, or
- in task descriptor
- then restore kernel context
- symmetry with kernel entry and exit (syscall vs irq)?
- Kernel Exit
- store kernel context
- restore given user context (next task to be run)
- return to user mode via
eret
- System Call Parameters
- example: int Create(int priority, void (*function)())
- how does the kernel receive the two parameters?
- how does it return the int?
- simple answer: can use the same arg/return conventions established by the ABI
- user task puts params into x0 and x1 before svc
- exception handler saves application context
- kernel can inspect saved x0 and x1 to find parameters
- kernel overwrites saved x0 with return value before
eret
- after
eret
, user task looks in x0 to find result
- system call stubs: create a user-level function corresponding to each system call
Additional Information
An earlier document by Bill Cowan is available here. This is certainly not the only way to write a context switch and I do not necessarily recommend (or not recommend) this particular approach, but I figure every bit of information can help.
Kernel Initialization
- initially in boot.S:
- sets up EL1 stack pointer, masks interrupts, jumps to kmain
- what else needs to happen?
- need to initialize exception vector
- need to initialize any kernel data structures
- allocate slab of task descriptors and stacks and manage via free list
- need to create an initial task, and context switch to it
- initial task will need to run some user-level function, at some priority
- hard code function pointer into kernel
- set up new task context to be the "saved" context for user task
- return from exception (restore user context, then
eret
)
Task Creation