“Ring 0” is a vendor spelling. A course that teaches the mechanism and then says “x86 calls it ring 0” has taught one vendor's vocabulary and left you unable to read the other two manuals, which is a strange thing to do to a reader who will eventually need them.
The reason this is worth a whole concept rather than a footnote is that the three designs genuinely differ, not just in naming. x86 puts the privilege level in a segment register and compares it against a field in a table. AArch64 puts it in the current exception level and compares it against a per-level vector base. RISC-V puts it in two bits of a status register and makes the question of who handles a given trap a writable field that firmware sets. Those are three different answers to the question “where does the privilege decision live,” and the third one is the interesting one.
Strip the vocabulary away and every one of these machines has the same five things:
Now the three.
x86-64
levels rings 0..3. Only 0 and 3 are used by
an operating system today; 1 and 2 exist
for the historical OS/2 split.
the table IDT, 16 bytes per entry, indexed by
vector*16. One for the whole machine.
the rule IF gate.DPL < CPL THEN #GP(vector,1,0)
The level is a NUMBER in CS, compared
against a FIELD in a table.
the drop SYSCALL. No check at all; CPL := 0.
the return SYSRET, which is #GP(0) at CPL != 0.
SYSEXIT is #GP(0) at CPL != 0.
The return has a check and the entry
does not -- backwards from intuition.
user proof CS.RPL on the saved frame; error-code
bit 2 (U/S) on #PF.
AArch64
levels EL0..EL3. MORE levels than x86 has
rings, and they are asymmetric in a way
rings are not: EL2 is a hypervisor, not
"the second OS".
the table VBAR_ELx. ONE PER LEVEL. A vector
table, like the IDT, but the register
you read it from depends on where you
are.
the rule PSTATE.PAN. With PAN=1, EL1+ may not
access EL0 data. Structurally the same
job as SMAP, and reached with a
different instruction: STAC/CLAC set
and clear the permission, exactly as
x86's STAC/CLAC toggle EFLAGS.AC.
the drop SVC #imm16 -- an ORDINARY instruction
at any EL. A 16-bit immediate, not a
register, so no argument marshalling in
the instruction at all.
the return ERET.
the record THREE CSRs, and this is the big one:
ESR_ELx the syndrome: which exception
AND which of its many causes
FAR_ELx the faulting address
ELR_ELx where to return to
x86 squeezes the same three things into
a stack frame plus CR2. AArch64 keeps
them in registers.
categories Four, not three: from a lower EL, from a
higher EL, from the current EL with SP0
selected, and from the current EL with
the process stack. That last distinction
is about WHICH STACK the exception landed
on, and it is a fourth thing to get right.
RISC-V
modes M, S, U. And this is the one that
differs most.
the mandatory M mode is MANDATORY. Every RISC-V hart
implements it. So there is a mode BELOW
user mode by construction, and it is
not optional and not virtualised away.
Nothing equivalent is true on x86 or
AArch64, where the lowest mode is
unprivileged.
the table mtvec, stvec, utvec. ONE PER MODE.
the rule medeleg and mideleg. Two registers,
one bit per exception CAUSE, saying
whether S or U handles it.
x86 hardwires the split at CPL 0.
AArch64 hardwires it at the EL boundary.
RISC-V makes it a FIELD FIRMWARE WRITES.
the drop ECALL (and EBREAK for a breakpoint).
the return MRET, which restores privilege from
mstatus.MPP.
the record Three CSRs again, and the naming is
better than either of the others:
mcause the reason, an ENUM
mtval the offending value, and
OPTIONAL per cause -- the
spec says it may be written
as zero where there is nothing
meaningful to put there
mepc where to come back to
the one to mstatus itself. MPP records the
not skip privilege to return to, and the handler
must save it before it does anything
else at all. Get that wrong and the
return drops to the wrong mode.
Two of those are worth a second look, because they are better ideas and they are portable.
RISC-V's mtval being optional per cause is a genuinely better design than x86's, and it is worth copying. x86's #PF error code is a fixed-format bitfield whether or not every bit is meaningful for the event that occurred, and bit 3 means “a reserved bit in a paging entry” — which is a completely different kind of thing from every other bit, sharing a field with them. AArch64's syndrome is explicitly a combination of an EC (exception class) and an ISS (instruction-specific syndrome) whose meaning depends on the class, which is the same idea done properly: the low bits are generic, the high bits are interpreted according to what kind of event this was. Three architectures, one field, and the two newer ones both decided that the meaning of the bits should depend on the class of the event.
RISC-V's delegation as a writable field is the other. It means an implementation can choose where a given trap is handled, and firmware can be changed to match the platform, without an ISA change. x86 and AArch64 fix the boundary in silicon. That is a real trade — a field firmware writes is a field firmware can get wrong, and a misconfigured delegation is a security bug — but it is the more flexible of the three designs and it is the direction the industry has been moving.
Strip the three tables down to a single line each, and the portable content is the shape rather than the number:
And here is what a compiler author actually needs from this, in the order they need it:
SYSCALL, and yes — a convention exists and it is the operating system's, not the architecture's. AArch64: SVC, and the immediate is a 16-bit constant, so the number is in the instruction and the arguments go in the ordinary registers. RISC-V: ECALL, with the number in a7 and arguments in a0–a5 — and a7 is the seventh register, chosen so that the first six arguments have the same numbers as x86's. Every ISA arrived at a register-based convention; only x86 had to bend one because the hardware took two registers first.RCX and R11, chosen by the hardware and impossible to change. AArch64 and RISC-V: none, because the convention does not have to dodge a return address — you simply keep the return address somewhere else.SYSRET is #GP(0) at CPL = 0), and that asymmetry is a consequence of the design rather than a decision: because SYSCALL checks nothing, its return cannot rely on the entry having established anything.The one-line portability rule that survives all three: the entry convention is not the architecture's, it is the operating system's, and it will outlive every kernel you have ever heard of. Emit against the ABI your target kernel documents, and treat the architecture manual as a source of constraints on that ABI rather than as a definition of it. The convention concept showed that on x86-64 this means five of the six facts come from Linux; on the other two machines the split is different, and the reason is the same.
You have an AArch64 or RISC-V machine, or a cross-compiler and an emulator. Do this, and it takes about twenty minutes.
And the question that ties all three together, which is the one worth carrying into the next course in this chain: in every case, the lowest privilege level is unprivileged, and in one case there is a mode below it that is not optional. That asymmetry is the difference between a design where the kernel is a guest and a design where the kernel is the ground floor. Both are legitimate; they answer different questions about what you are building on top.
mtval design. Write two sentences for why “this field may be zero depending on the cause” is better than a fixed-format bitfield, and two for why it is worse. (Better: a fault that has no meaningful value does not have to invent one, so the zero is honest. Worse: a handler that reads it without checking the cause has no way to tell “zero” from “not applicable,” which is the same problem as a NULL return from a function that cannot fail.)Forward, the harness concept closes the course by asserting which of these claims were measured and which were quoted — and the fact that this concept is the one where that distinction matters most is the argument for having the harness at all.
Backwards, this concept reads the other seven as one instance of a design and asks how many other instances there are. The vector concept's Intel-versus-AMD divergences are one data point: even within one architecture, “the manual's number” is not always portable. The doors concept'sSYSENTER result is a second, and a stronger one, because there the divergence is not a name but an instruction's existence. The error-code concept's three formats are a third: the same concept, expressed three ways, with the newest design making the best choice.
Outward, the collection has a gap this concept makes visible. Everything in the last four concepts is about a single processor crossing a boundary. The memory course's limits block named its own exclusion explicitly — cache coherence, NUMA, and multiprocessor memory are the subject of the Multiprocessor Architecture course, which does not exist yet. And the thing this course has just taught transfers to that one intact: a table the kernel owns, a reason code per event, and a rule about who handles it are the shape of a cache-coherence protocol too, and the same discipline applies — read the second vendor's manual, quote the shape rather than the number, and check the field order.
And the practical one. If you write a compiler backend, this concept is the one that saves you: the register convention for entering the kernel is not the architecture's, the number is not the architecture's, and the clobbered registers are the architecture's and you cannot change them. Five of the six facts in the x86-64 system-call ABI are an operating system's decision, and every one of the three architectures here has made a different set of them.