ContentsThe library

What Happens While You Wait

Your Program Cannot Read a File, and Never Could

The separation between your code and the kernel is enforced by the processor, not agreed by convention. Here is what your code genuinely cannot do, and the single door it uses to ask.

A bit in the processor

Open a file in any language and the line looks like every other line. A name

goes in, something comes back, and the vocabulary is the vocabulary of ordinary

code.

It is not ordinary code. Your program did not open anything. It asked, and

something else did it, and the reason it had to ask is not politeness or

library design. It is that the processor your program is running on is in a

state where the instructions required to open a file are refused.

Processors have at least two of these states. In the stronger one, every

instruction works. In the weaker one, a specific set of instructions fault

instead of executing: anything that talks to a device, anything that changes

how addresses are translated, anything that affects interrupts, anything that

reconfigures the machine itself.

Your program runs in the weaker state, always. The kernel runs in the stronger

one. That is the whole separation, and the important thing about it is where it

lives: in a register in the processor, which no instruction available to your

program can change.

FIG 1What each side can do, and what happens when your side tries
your code maythe kernel mayfaults if your code trie
arithmetic on your own d110
calling your own functio110
reading your own memory110
allocating within memory110
an infinite loop110
writing to a disk contro011
changing an address mapp011
The top five rows are the whole of what a program can do unaided, and they are all operations on things it already has. The first marked row is there because it surprises people: looping forever is permitted, since the separation is about authority rather than about resource use, and a runaway loop is dealt with by the scheduler rather than by the boundary. The second marked row is the shape of every denial, which is not an error code from a library but a fault raised by the processor at the instruction itself.

The list of things you cannot do

It is worth having the list, because every system call you will ever use exists

to cover one of its entries.

Your code cannot reach a device. Disks, network cards, keyboards and timers are

reached through addresses and ports the processor will not let you touch. So

every file read, every packet sent and every character printed is a request.

Your code cannot change what its own addresses mean. The mapping from the

addresses in your program to actual memory is maintained by the kernel, in

structures your code cannot write. So growing the heap is a request, and so is

mapping a file, and so is sharing a page with another process.

Your code cannot see another process. Not its memory, not its registers. Any

communication at all, a pipe, a socket, a shared page, a signal, goes through

the kernel because there is no other path between two address spaces.

Your code cannot stop being interrupted. The instruction that masks interrupts

is privileged, which is exactly why no program can monopolise a processor by

refusing to be taken off it.

And your code cannot set the clock, mount a filesystem, change its own

privileges, or any of the other machine-wide operations, for the same reason in

each case.

Notice what is left. Computation on data you already hold. That is it. Every

program worth running therefore spends its life asking, and the rest of this

course is about what happens during the asking.

One door, and the kernel picks the room

The request mechanism is smaller than people imagine.

FIG 2A request, at the level the processor sees
plaintext
put the request number in one agreed register
put up to six arguments in other agreed registers
execute the one instruction that traps

the processor then:
  switches to the stronger state
  saves where your code was
  jumps to an address the kernel set at boot

the kernel then:
  looks up the number in its table
  checks the arguments are permissible
  does the work, or refuses
  puts a result in the agreed register
  returns, switching the state back
Nineteen words of your code and a great deal of somebody else's. The shape worth noticing is in the third block: your program supplies a number and some arguments, and nothing else. It does not supply an address to jump to, because if it could, the whole arrangement would be pointless. The kernel registered the entry point once, before any program ran, and every request in the system arrives there.

Two properties of this design do all the work.

Your code names a number, not a destination. The number is an index into a

table the kernel controls, so the set of things that can be asked for is fixed

by the kernel and cannot be extended by a program.

And the arguments are checked, every time, on the kernel's side of the

boundary. A pointer you pass is validated against what your process is actually

allowed to touch before anything is read through it. This is slow, it is the

reason a request costs what the next lesson will measure, and skipping it has

historically been the source of the most serious bugs in operating systems.

FIG 3Where control goes when you ask
Follow the arrows and notice that your program appears in exactly two places, at the start and after the return. Everything between is a path through code you did not write, at a privilege you do not have, and the reason a profiler that only knows about your functions shows a blank where most of the time went.

Why convention would not do

The obvious objection is that all of this could be done in software. A library

that checks before acting, a compiler that refuses to emit the dangerous

instructions, a rule that everybody follows.

None of those hold, for the same reason. A software check is code, and code is

in an address space, and a program determined to do otherwise can jump past it,

or overwrite it, or simply never call it. A compiler's refusal binds only

programs that went through that compiler. A convention binds only the

well-behaved, and the well-behaved were never the problem.

FIG 4A program trying to write to a device directly
stepstepwhat the program doeswhat the processor doeswhere control iswhat happened
11computes the device addressnothing specialin the programEntirely legal. Arithmetic is arithmetic, and there is no rule against knowing an address.
22issues the writechecks the current statein the programThe check is made by the hardware on this instruction, not by anything the program called. There is nothing to jump past.
33nothing, it never executedraises a faultin the kernelControl has left the program at the faulting instruction. The instruction had no effect at all.
44receives a signal, or diesresumed in the weaker statewherever the kernel decidedThe program does not get to choose what happens next, which is the part that makes this a boundary rather than a warning.
4 steps
The whole argument is in the second row. The check is performed by the processor, as part of executing the instruction, on behalf of no software at all. There is no call to avoid, no branch to invert and no byte to patch, because the enforcement is not made of instructions. This is also why the boundary survives a program that has been completely taken over by an attacker, which is the property that the rest of the system is built on.

The cost of this is what the next lesson is about. A boundary that is enforced

by the hardware is a boundary that has to be crossed by a specific mechanism,

and that mechanism is thousands of times more expensive than a function call.

Knowing how much more expensive, and why, explains a surprising amount of

otherwise mysterious performance.

Recap

  • The separation is a hardware state, so a program cannot bypass it by being clever, and the instructions that matter simply fault when your code runs them.
  • Your code cannot touch a device, change what its own addresses mean, or see another process, which is why almost everything interesting is a request rather than an action.
  • There is one door, your code chooses only the number and the arguments, and the kernel chooses where control lands, which is the property that makes the whole arrangement safe.

This is the reading half

Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.

The contents

NextThe Cost of Asking →

The rest of this course

  1. 01Your Program Cannot Read a File, and Never Couldyou are here
  2. 02Two Hundred Times the Price, for a Line That Looks the Sameopening only
  3. 03Your Program Is the Small Part of Your Processopening only
  4. 04The Program That Waits Gets Served First, and It Is Not Being Rewardedopening only
  5. 05Not Running Is Two Different Problems With One Symptomopening only
  6. 06Ten Thousand Connections, One Thread, and One Thing It Still Cannot Doopening only
  7. 07Your Handler Runs Between Two Instructions You Did Not Chooseopening only
  8. 08Four Seconds of Wall Clock, Half a Second of Work, and Where the Rest Wentopening only

Read alongside