-
Notifications
You must be signed in to change notification settings - Fork 108
System calls and interrupts
All system calls and interrupts get funneled through the exact same entry point, and its here where the magic of exactly controlling the system's response to them is handled. Lets take a quick tour through this very interesting entry point _irqit
.
System calls in ELKS are implemented by passing each argument in a separate register, then executing an INT 80h. This instructions, "Software Interrupt", saves the CPU flags register (F), the current code segment register (CS), and the current instruction pointer (IP) on the stack, which is pointed to by the stack segment and stack register pair (SS:SP).
Incoming hardware interrupts to the CPU do the same thing, the F, CS and IP registers are saved on the current stack.
What happens next is the CPU looks up the interrupt number from a table in low memory, and loads a new code segment and instruction pointer (CS:IP) from that table. Since this is "two words", we call it a far pointer.
In ELKS, each interrupt location (called a vector) in the low memory table goes to yet another table in the kernel, which consists of four bytes each, a 3-byte far call to _irqit
, followed by a byte indicating the interrupt number. Here's the entries for interrupt 0 and interrupt 80h, which are the clock and system call interrupts, respectively:
_irq0: // Timer
call _irqit
.byte 0
_syscall_int: // Syscall
call _irqit
.byte 128
Using the above table, all hardware and software interrupts are directed to a far call to _irqit
followed by a byte indicating the interrupt number. Later, you'll see how the saved CS:IP from the far call is used as a far data pointer to get the interrupt number. This works because the far call pushes the saved CS:IP on the stack, and the "saved CS:IP" points to the next "instruction" which is the interrupt number byte. The call to _irqit never returns, as you'll see. The magic begins!
We're going to take a step-by-step analysis of each instruction executed in this amazing piece of code. It is here where the extremely precise handling of processes (making system calls) and interrupts (especially including the hardware timer interrupt) occurs. To study on your own, this file is in elks/arch/i86/kernel/irqtab.S
.
*
!
! On entry CS:IP is all we can trust
!
! There are three possible cases to cope with
!
! Interrupted user mode or syscall (_gint_count == 0)
! Switch to process's kernel stack
! Optionally, check (SS == current->t_regs.ss)
! and panic on failure
! On return, task switch allowed
!
! Interrupted kernel mode, interrupted kernel task
! or second interrupt (_gint_count == 1)
! Switch to interrupt stack
! On return, no task switch allowed
!
! Interrupted interrupt service routine (_gint_count > 1)
! Already using interrupt stack, keep using it
! On return, no task switch allowed
!
! We do all of this to avoid per process interrupt stacks and
! related nonsense. This way we need only one dedicated int stack
!
*/
_irqit:
//
// Make room
//
push %ds
push %si
push %di
The explanation at the top might take a while to understand, but lets start by commenting on each instruction. The first three instructions push the registers DS, SI and DI, in order to use these registers to start working. Remember, this same code is executed by hardware and software interrupts, and so far, exactly the same way.
What exactly does the stack look like at this point? The system could be in absolutely any state, that is, it could be that an application is making a system call, in which case SS=DS and both are set to the application data segment. It could be a hardware interrupt during application execution, in which case SS=DS and set to the application data segment. Or a hardware interrupt could have occurred during kernel execution, (that is, after having already entered this code, and running kernel code), in which case the SS=DS and set to the kernel data segment. Or it could be an interrupt that is interrupting an interrupt... we'll get to these cases later. The point is at the time that the system executes this code, there is a stack segment and stack register which was just used to push DS, SI, and DI.
So, there are now eight words pushed onto the interrupted stack (growing downwards):
------
| F | Flags word at time of interrupt
------
| CS | Code segment at time of interrupt
------
| IP | Instruction pointer at time of interrupt
------
| CS | Kernel code segment from "call _irqit"
------
| IP | Address of next (return) instruction after "call _irqit"
------
| DS | Saved DS at time of interrupt
------
| SI | Saved SI at time of interrupt
------
| DI | Saved DI at time of interrupt
------