An application calling read() looks like a normal function call. Illegal instructions, page faults, interrupts, and system calls can also look like “the CPU jumped to another handler.”
But these paths are not normal function calls.
A normal function call stays in the same privilege level and address space. The caller knows the callee address, passes arguments according to the ABI, and returns when the callee finishes. Exceptions and system calls are different: current code cannot freely choose a high-privilege entry. The CPU must switch state according to architectural rules, transfer execution to a controlled entry, and allow the kernel, firmware, or exception handler to decide what happens next.
The safest first model is this: privilege levels limit what code can do; exception entry is the CPU-approved controlled path for crossing protection boundaries or handling special events; a system call is a user-triggered exception that requests kernel service.
low-privilege code
-> syscall / trap / exception / interrupt
-> CPU saves state required for return
-> CPU switches to a controlled entry and higher-privilege context
-> kernel / firmware / handler checks and handles the event
-> controlled return to the previous execution state
The exact instructions and registers differ across ARM, RISC-V, and x86, but this path is stable.
Privilege Levels Decide Who Can Do What
A CPU does not give every piece of code the same power.
Applications usually cannot directly modify page tables, disable interrupts, access arbitrary physical addresses, write device registers, or change scheduler state. Kernels, exception handlers, hypervisors, secure firmware, or machine firmware may have higher privileges.
Privilege boundaries protect resources such as:
- memory mappings and page tables
- peripheral registers
- interrupt control
- task scheduling and CPU state
- security keys, boot policy, and isolated environments
- virtualization and multiple-system isolation
ARM and RISC-V use different names. ARM Cortex-M commonly has Thread mode and Handler mode. AArch64 has EL0, EL1, EL2, and EL3. RISC-V has U mode, S mode, and M mode. The names differ, but the common rule is this: low-privilege code cannot perform high-privilege actions arbitrarily. It can only request higher-level handling through architecture-approved entries.
A Normal Jump Cannot Cross the Protection Boundary
If a user program could jump directly to any kernel address, isolation would collapse.
So a CPU does not treat “jump to an address” and “gain kernel privilege” as the same operation. Normal branch, call, and jump instructions execute under the current privilege. Entering a higher-privilege path requires an architectural exception, trap, system call, interrupt, or return mechanism.
This has two important consequences.
First, the entry address is not chosen freely by the user program. System calls can only enter a preconfigured entry. Exceptions and interrupts enter through a vector table, exception-vector base, or trap vector.
Second, high-privilege code cannot trust parameters from low-privilege code. User registers, user pointers, system-call numbers, lengths, and flags are requests, not trusted facts. The kernel must re-check permissions, addresses, and object state.
That is why a system call is heavier and more controlled than a normal function call.
Exception Entry Must Save Enough State to Return
When an exception occurs, the CPU is executing some instruction in some context. After handling, the system may return to the same place, terminate the task, schedule another one, or enter error handling.
Exception entry therefore has to solve several problems:
- record the program counter or return address before the exception
- record the previous status register or privilege state
- record the exception cause
- switch to the contracted entry address
- switch to a usable stack or exception context
- limit or adjust interrupt enable, priority, and return rules
Some state is saved automatically by hardware. Some must be saved by entry assembly or kernel code. Architectures differ greatly, but the goal is the same: the handler must know why it was entered and must be able to return safely or terminate safely.
If the return address, status register, stack, or exception cause is corrupted, the system may fail exception return, repeat the same fault, run at the wrong privilege level, or crash immediately after the handler returns.
A System Call Is a User-Triggered Exception
A system call can be understood as a user-triggered controlled exception.
User space enters the kernel through a dedicated instruction. ARM/AArch64 commonly uses svc, RISC-V commonly uses ecall, and x86 has syscall, sysenter, or older interrupt-gate mechanisms. The instructions differ, but the role is similar: current code asks the CPU to enter a preconfigured higher-privilege entry.
After entry, the kernel usually sees:
- system-call number
- argument registers
- current task or thread
- user return address
- user status
- current address space
The kernel then dispatches and checks the request: whether the system-call number exists, whether argument addresses are user-accessible, whether a file descriptor is valid, whether the process has permission, whether the call may block, and whether a signal interrupted it.
So a system call is not a direct user-space call into a kernel function. User space places a request in agreed registers, enters through exception entry, and lets the kernel decide which path is allowed to run.
Exceptions Are Not Always Errors
Many people hear “exception” and think only of faults. In practice, exception entry carries multiple event types.
Common examples include:
- system call: user space actively requests kernel service
- page fault: a virtual page is missing or permission is insufficient
- illegal instruction: the current CPU or privilege level cannot execute it
- unaligned access: disallowed on some platforms or configurations
- debug exception: breakpoint, single-step, or watchpoint
- interrupt: a peripheral, timer, or IPI needs CPU attention
- secure or virtualization trap: a lower-level operation is intercepted by a higher layer
These events use similar entry mechanisms, but their semantics differ. A page fault may be repaired and return to user space. An illegal instruction may signal or terminate the task. A timer interrupt may trigger scheduling. A system call may block. A secure monitor or hypervisor may intercept sensitive operations.
So after seeing that the CPU entered an exception, check the cause, source privilege, entry type, and OS handling policy.
Return Is Controlled Too
After exception handling finishes, a normal function return is not enough.
The return path must restore:
- previous program counter
- previous status register and privilege level
- required general-purpose registers
- stack state
- address space or task context
- interrupt enable and priority state
Architectures usually provide a dedicated exception-return, eret, sret, mret, or similar mechanism. These do more than jump to an address. They restore privilege, state, and exception context.
That is why exception-return bugs are hard to debug. Missing saved state, stack corruption, wrong return status, or returning to the wrong privilege level can crash the system after the handler “finished.”
The Common Model Across ARM and RISC-V
ARM and RISC-V differ greatly in register names, mode names, and entry details, but the common model comes first.
For ARM/AArch64, watch:
- current Exception Level or mode
- exception-vector base
- saved return address and status register
- entry type such as
svc, interrupt, abort, or fault eretor exception-return mechanism
For RISC-V, watch:
- current U/S/M mode
- trap vector
- cause of
ecall, interrupt, or exception - epc, status, tval, and related state registers
sret,mret, and return mechanisms- whether SBI or machine firmware participates
Do not begin by memorizing complete register tables. First ask: which privilege did it come from, why did it enter, where is the entry, where is the return address, who checks the parameters, and which privilege will it return to?
Evidence to Debug This Layer
Privilege and exception problems usually require state evidence, not just source code.
Useful evidence includes:
- current privilege level or mode
- exception cause
- PC or return address at exception time
- accessed address or instruction at exception time
- status-register interrupt enable, privilege, and return-state bits
- whether the current stack is correct
- whether system-call number and argument registers match the ABI
- whether the vector table or trap vector points to the expected entry
- whether the return path restores the correct address and privilege
For example, a user process faulting when accessing a peripheral register may not mean the address is wrong; the access may simply be disallowed. A system call returning EFAULT may not mean the driver is broken; a user pointer may be inaccessible. Repeated exceptions at the same address may mean the handler did not fix the cause, or the return state did not advance.
What to Remember
Privilege levels, exceptions, and system calls solve one underlying problem: low-privilege code cannot freely perform high-privilege actions, but the system must still allow service requests, hardware responses, and error handling.
A normal function call jumps within the current privilege. Exception entry is the CPU-approved controlled path: save required state, switch to a contracted entry, let higher-privilege code or a dedicated handler run, then use a controlled return to restore execution.
With that model, system calls, interrupts, page faults, illegal instructions, debug breakpoints, and secure traps stop being unrelated concepts. They are different reasons for the CPU to enter controlled paths. The differences are who triggered them, which privilege they came from, who handles them, and whether execution can return.