The wall, and the one door through it
From the previous guide you already know the great divide: your program runs in user mode, fenced in by the hardware, while the kernel runs in privileged kernel mode and is the only code allowed to touch devices, hand out memory, or reach into the machine. That fence is the whole point — it is what keeps a buggy or hostile program from wrecking everyone else. But a fence with no gate would be useless: a program that can do nothing but arithmetic in its own little box is not much of a program. The system call is the gate.
Picture a bank. You cannot simply walk into the vault and scoop up cash — that is forbidden, and for good reason. But you can step up to the teller window, fill out a slip saying exactly what you want, and the teller (trusted, behind the counter) checks your request and does the privileged work on your behalf. A system call is that teller window: the one official, guarded passage from your program into the kernel. There is no other sanctioned way through the wall.
So almost everything interesting a program does — opening a file, drawing on the screen, sending bytes over the network, starting another program, asking for more memory — is really the program asking the kernel through a system call. Pure computation (adding numbers, sorting an in-memory list) stays inside the box and needs no system call at all. The moment a program wants to reach the outside world, it has to knock on this one door.
What you call is not what the kernel runs
Here is a point that trips up almost everyone at first: when you write code, you almost never make a raw system call yourself. You call a friendly function from a library — something with a tidy name, parameters, and a return value — and that function reaches the kernel for you. This is the API-versus-system-call distinction. The API (application programming interface) is the set of functions you are given to call; the system call is the actual kernel-crossing that may happen underneath.
The mapping between the two is not one-to-one, and that is worth pinning down. Sometimes one API call triggers exactly one system call. Sometimes one API call triggers none at all — sorting an array needs no kernel help, so it makes zero system calls. And sometimes the library is clever: a call like fprintf may just stash your text in a buffer in your own memory and issue a single real write(...) system call only later, once the buffer fills, turning a thousand little API calls into one expensive crossing.
A round trip through the door, step by step
Let us follow a single call all the way in and back out. Say your program runs read(fd, buf, 100): "read up to 100 bytes from the open file fd into the buffer buf." The first argument, fd, is a file descriptor — a small integer the kernel handed you earlier that names one of your open files, the way a coat-check ticket names your coat. The actual privileged work (talking to the disk) only the kernel may do, so this innocent-looking line is going to leave user mode entirely.
- Pack the request. The program places the system-call number (which service it wants — read) and the arguments (the file descriptor, where to put the bytes, how many) into agreed-upon CPU registers, following a fixed convention.
- Knock on the door. The program executes a special trap instruction. In one indivisible step the hardware flips the mode bit to kernel mode, saves where the program was so it can be resumed, and jumps to a single fixed entry point inside the kernel.
- The kernel looks up what you asked for. Using the system-call number, it indexes into its system-call table to find the right handler — like flipping to an entry in an index to find the page you need.
- The kernel validates every argument. Is fd really one of your open files? Does buf actually lie inside your own memory? It must assume nothing — a hostile program will pass bad pointers and out-of-range numbers on purpose.
- The kernel does the privileged work — fetching the bytes from the disk, copying them into your buffer — then writes the result (how many bytes it actually read) into a register.
- Return through the door. The kernel executes a return-from-trap instruction that flips the mode bit back to user, restores your saved state, and resumes your program on the very next line — now holding the data and the return value.
From your program's point of view, none of that machinery is visible. read(...) looked just like an ordinary function call that happened to be unusually powerful: you called it, you waited, you got an answer. The whole trip in and out of the kernel happened between two lines of your code.
Two doorbells, one doorway
The trap instruction is the polite, deliberate way into the kernel: your program chose to knock. But the kernel can also be summoned without anyone in your program asking. When the network card finishes receiving a packet, or you press a key, the device raises an interrupt — like a doorbell ringing at the front desk. The CPU drops what it was doing, switches into kernel mode, runs the kernel's handler for that device, and then returns.
The honest distinction is about cause and timing. A trap (your system call, or an error like dividing by zero) is synchronous: it is caused by the instruction the CPU is running right now, at a predictable moment. A hardware interrupt is asynchronous: it is caused by an outside device and can strike between any two of your instructions, with no warning. They are different events with different triggers — yet they share the very same plumbing to cross into the kernel, which is why both are sometimes loosely lumped together as "interrupts."
Why crossing costs, and why it must be careful
Two honest points carry over into real performance and real security. First, a system call is genuinely more expensive than a plain function call. Switching the mode bit, saving and restoring state, validating arguments, and (often) a context switch while you wait for slow hardware all cost real time. So a well-written program does not make millions of tiny system calls; it batches and buffers, reading a big chunk once instead of one byte at a time. This is exactly why that fprintf buffer exists.
Second, the kernel must never trust the arguments a system call hands it. This boundary is the front line of OS security. A malicious program will deliberately pass a pointer aimed at someone else's memory, or a length far larger than the buffer it gave, hoping the kernel will blindly read or write where it should not. Careful checking at this one doorway — is this descriptor yours, does this buffer lie within your own memory — is a core part of what keeps one program from using the kernel as a crowbar against another.
Step back and the picture is simple and powerful. The system call is how every operating-system service is actually reached — process control, file and device I/O, communication between programs, protection checks, all of it. The exact register conventions and call numbers form what is called the application binary interface, the low-level contract between a program and the kernel. You will rarely write a raw system call by hand, but now you know what is happening every time your friendly library function quietly crosses the wall on your behalf.