OPERATING SYSTEMS / 7. SYSTEM CALLS
System Calls — The Bridge Between User & Kernel
Every interaction with hardware goes through exactly this interface
EXPLANATION
A system call (syscall) is the fundamental mechanism by which a user-space program requests a service from the kernel. It is the only legitimate way for user code (Ring 3) to perform privileged operations. Why syscalls exist: user code cannot directly touch hardware (NIC, disk, keyboard). If it could, any program could corrupt another program's data, read other processes' passwords, or crash the whole system. The CPU's privilege levels enforce this. Only kernel code (Ring 0) can access hardware. How a syscall works mechanically (x86-64): ① User code puts the syscall number in register RAX (RAX=1 = write, RAX=0 = read, RAX=2 = open, RAX=57 = fork…) ② Arguments go in RDI, RSI, RDX, R10, R8, R9 ③ User code executes the SYSCALL instruction ④ CPU switches to Ring 0, jumps to kernel's syscall handler ⑤ Kernel validates arguments (checks pointers are in user space, etc.) ⑥ Kernel performs the operation (writes to file, allocates memory, etc.) ⑦ Kernel puts return value in RAX ⑧ CPU switches back to Ring 3, execution continues after SYSCALL The most important Linux syscalls (know these): • open(path, flags, mode) → returns file descriptor • read(fd, buf, count) → reads bytes from fd • write(fd, buf, count) → writes bytes to fd • close(fd) → releases file descriptor • fork() → creates a child process (copy of parent) • exec(path, argv, envp) → replaces process image with a new program • waitpid(pid) → parent waits for child to finish • exit(status) → terminates the current process • mmap(addr, len, prot, flags, fd, offset) → maps memory into address space • munmap(addr, len) → unmaps memory • socket(domain, type, protocol) → creates a network socket • connect(fd, addr, addrlen) → TCP connect • send(fd, buf, len, flags) → send data on socket • kill(pid, signal) → send a signal to a process • clone(flags) → fork with fine-grained sharing control (basis of threads) • getpid() → returns current PID • stat(path, buf) → get file metadata strace: the ultimate debugging tool — it intercepts and prints every syscall a program makes. "strace python myscript.py" shows you every file it opens, every network call, every memory allocation — the complete conversation between your program and the kernel. The cost of syscalls: each syscall requires a privilege level switch, TLB considerations, and kernel validation. A syscall takes ~100-300 ns. This is why read()/write() with small buffers in a loop is slow — each call has overhead. Buffered I/O (stdio in C, Python's file objects) batches many small reads/writes into fewer, larger syscalls. VDSO (Virtual Dynamic Shared Object): some syscalls that just read kernel data (gettimeofday, clock_gettime) are mapped into user space — no ring switch needed. Makes clock reads extremely fast.
DIAGRAM
SYSCALL MECHANISM:
User Space (Ring 3) Kernel Space (Ring 0)
───────────────────── ─────────────────────
int fd = open("file", O_RDONLY)
↓
RAX = 2 (open syscall #)
RDI = ptr to "file"
RSI = O_RDONLY flag
↓
SYSCALL instruction
─────────────────────────→ CPU switches to Ring 0
Validate args
Find/create file in VFS
Allocate file descriptor
Return fd in RAX
←───────────────────────── CPU switches back to Ring 3
fd = (value from RAX)
(could be 3, 4, 5, ...)
STRACE OUTPUT (running ls):
execve("/bin/ls", ["ls"], envp) = 0
openat(AT_FDCWD, "/etc/ld.so.cache") = 3
read(3, "<7f>ELF...", 832) = 832
mmap(NULL, 2220368, PROT_READ, ..) = 0x7f2a...
write(1, "file1 file2 dir1
", 20) = 20
exit_group(0)CODE