System Calls
2010-12-11
Introduction
Up to now we have developed a kernel with a functional console driver. However, the functions are only accessible from within the kernel. In this chapter, we will introduce system calls that allow the functions to be called from outside the kernel.
How System Calls Work
There are various ways to implement system calls, the only requirement being that applications know how to make the system call. This can be achieved by loading functions in the application's address space, so that the application can make regular function calls. This makes calling really simple, but it requires adding code to every application. Another approach is to have the functions at known addresses, and have the application call them there. The problem with this approach is that it makes the system very inflexible and fragile - if we change the kernel in such a way that the addresses shift, all applications must be changed to use the new addresses. Instead, we will use a software interrupt to access system calls.
We will write an interrupt handler and bind it to int 80h. The calling convention we will follow is similar to what we used before; the parameters are pushed on the stack in reverse order, and the stack is cleared by the caller after the return. Additionally, we require that a value be put in ax to select the system call to be invoked. Those are the same conventions used by the BSD family of operating systems.
Implementation
Our handler for int 80h will have to call the right function depending on the value passed in ax. A straightforward way to implement this is to use a table that contains the addresses of the functions. The code looks as follows:
syscallHandler:
cmp ax, SYSCALL_INVALID
jb .ok
; Syscall number too high, return 0 with carry flag set
popf
stc
pushf
xor ax, ax
iret
.ok:
mov bx, ax
shl bx
jmp near [cs:syscallTable + bx]
Note how we use jmp
to get to the function body.
This means that the stack will be passed unmodified, containing the arguments
pushed by the caller, and the flags, code segment, and offset pushed by the
int
instruction. Our functions only expect the arguments and an
offset. This means that the functions have to be changed slightly, to reflect
the new position of the arguments. Also, the ret
at the end
will have to be changed to iret
.
Finally, since the functions implementing the system calls now expect to be called through an interrupt, we need to change any calls the kernel makes to them to reflect the new conditions. And, of course, we need to install the interrupt handler.
Files
The file syscall_kernel.asm contains the source for the new kernel. It requires the file syscall.inc, which contains the definitions to be used with system calls. Note that the binary generated by the assembler now exceeds 512 bytes, and thus needs more than one sector on the boot floppy. The bootsector, however, loads only the first sector. So, before you can run this new kernel, you have to adapt the bootsector to load both sectors. This should be straightforward; it involves changing a constant at the top of the bootsector code, after which you can rebuild and reinstall the bootsector.
Next part: The Floppy Drive