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":
  1. 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:
  2. 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.
  3. 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. 
  4. 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.