System Calls
CS 321 2007 Lecture, Dr. Lawlor
The Silberschatz book documents system calls in section 2.3.
Interrupts as a cry for help
The OS normally doesn't care what you're working on. It doesn't
hover
over your shoulder, watching your reads and writes to registers or
memory. It's got better things to do. Instead, if you want
the OS's attention, you've got to break something--do something the CPU
will generate an error for! The standard unignorable operation is
to throw an
"software interrupt", which on x86 CPUs you can do with the special
"INT" (generate
software interrupt) instruction.
Hardware interrupts are used by the hardware to get the CPU's
attention--for example, the IDE controller will raise an I/O interrupt
when a read is complete. The OS (the BIOS, or Windows, or Linux)
handles all interrupts, both hardware and software. We'll talk a
lot more about hardware interrupts when we talk about I/O.
How interrupts work as system calls ("syscalls")
So the standard way to ask the OS to do something is to issue a special interrupt, normally called a system call or "syscall":
- Stash the information about what you want done.
Usually there's some sort of "selector code" that tells the OS what you
want done (on Linux, the "syscall number", which goes into eax), and
then a set of arguments. The selector and arguments can be stored:
- Inside processor registers. It's pretty common to store a selector code in register eax.
- On the runtime stack, just like arguments to a normal
subroutine. For some reason, this is fairly rare, probably
because the OS uses its own separate stack, so it's a pain to access
your stack.
- Inside
the interrupt instruction itself. The x86 "INT"
instruction can initiate 256 different interrupts. The Motorola
68000 had a 16-bit "OS Trap" instruction 0xA??? that left 12 bits for
the "trap number", which could be any of 4096 different values (and the
old MacOS used a substantial fraction of these for different routines!).
- Call the OS, by issuing an interrupt. You could also imagine a
machine where you ask the OS to do stuff by just segfaulting--accessing
a special off-limits memory location.
- The
OS's "interrupt service routine" (just normal code, called by the CPU
when an interrupt happens) then reads the selector code from your
registers, does what you want done, and then returns control back to
you.
- The OS might have stashed return information (like an error code)
in registers, the stack, or elsewhere. As with all this stuff,
you've got to read each OS's docs to figure out how it works.
Linux can use either interrupt 0x80 (INT 0x80) for system calls, or the
slightly-faster SYSENTER instruction. The selector code goes in
eax, arguments go in ebx, ecx, edx, esi, and edi. Nothing goes on
the stack.
The PC BIOS uses interrupts 0x10 and 0x16. MS-DOS mostly uses interrupt 0x21. Ralph Brown's Interrupt List is the definitive reference for all BIOS and MS-DOS interrupt functions. The selector code goes in ax.
Windows XP now uses the special SYSENTER x86 instruction for system
calls. Windows 2000 and earlier versions of NT used INT
0x2e to access OS. Unfortunately, Windows randomly reassigns
system call numbers, so it's quite uncommon to make Windows system
calls directly.
Nowadays, you almost never make system calls directly, since the system
call interfaces require assembly code to load parameters into registers. Instead,
you normally call nice system library routines, like UNIX "write" or Windows "WriteFile", that hide the ugly assembly.
Syscall example--Linux
Konstantin Boldyshev has a good writeup and examples of Linux, BSD, and BeOS x86 syscalls, and a list of common Linux syscalls. He uses NASM for the examples. Here's a slightly cleaned up version of his Linux example:
; To make a Linux syscall, we load up the registers and call INT 0x80
mov eax,4 ;system call number (sys_write)
mov ebx,1 ;file descriptor (stdout)
mov ecx,msg ;message to write
mov edx,8 ;message length, in bytes
int 0x80 ;call kernel
; Kernel call return value is in eax-- it'll do as a function return code.
ret
section ".data" ;<- this puts the string into writeable memory...
msg:
db 'Wazzup?',0xa ; our little string, followed by a newline
(Executable NetRun Link)
Syscall examples--IBM PC BIOS
In 16-bit IBM PC DOS, to access the
BIOS "screen output" functionality, you invoke Interrupt 0x10 with
ah==0x0e. al is the character you want to output. bx is the
display style:
mov bx, 0x0007
mov ax, 0x0e4E ; low bits == ASCII 'N'
int 0x10 ; "output character" interrupt
You can also accomplish the exact same thing like this:
mov bl, 0x07 ; Color (only in color modes)
mov bh, 0x00 ; Page number
mov al, 'N' ; character to output
mov ah, 0x0e ; Function 0x0e: output character to screen
int 0x10 ; generic BIOS interrupt
Remember that "al" is the low 8 bits of "ax", so if you've set ax to
0xff00, al is zero; if you've set ax to 0x00ff, al is 0xff. You
can willy-nilly read and write from al, ah, ax, and eax, and they will all still correspond.
This system call doesn't return anything. But the wait-for-keystroke interrupt returns the key in ASCII in register al. There's also a hardware "scan code" in ah.
xor ax,ax ; set ax to zero
int 0x16 ; Wait for a keypress
; now al contains the keyboard key that was pressed...
You can stick these two togther to just echo characters from the keyboard to the screen.