OS Types and the OS Interface

Operating Systems — Lecture 02

Why is there an operating system?

./hello           (prints one line)     ~1,015,131 instructions
./hello_nostd     (write + exit)          ~108,573 instructions

The program wrote a dozen instructions. The machine ran a hundred thousand to a million.

The rest is the operating system.

The operating system is a provider of services — so it has an interface.

It is an enforcer of isolation — so that interface is a boundary, not a function call.

Today

  1. What is the OS made of?
  2. How do you talk to it? (the system call interface)
  3. What makes the boundary a boundary? (hardware modes)
  4. The boundary is expensive — how do you pay less?
  5. Where should the code live? (OS types, virtualization)

OS types

Monolithic (all in the kernel) · microkernel (drivers are processes) · unikernel (no boundary)

The system call is the interface

$ strace ./read_write_syscall
write(1, "Gimme message: ", 15)   = 15
read(0, "hello there\n", 64)      = 12
write(1, "hello there\n", 12)     = 12
exit_group(0)                     = ?

Call number in rax, args in registers, syscall, result in rax.

The trace is the program. Nothing sits between it and the kernel.

libc calls are not system calls

Programs call printf(), malloc(). libc turns those into system calls — when it has to.

  • getpid() — one system call
  • strlen() — none, ever
  • printf() — a write(), or nothing, depending on the buffer

libc decides when to cross. That is what optimisation exploits.

The boundary is in silicon

$ ./cli
first
Segmentation fault (core dumped)

cli disables interrupts — ring 0 only.

There is no check for it in the kernel. The CPU faults on it at ring 3.

A software check can have a bug. This cannot.

Mode is not identity

kernel/user = a hardware mode · root/non-root = a software identity

Root still runs in user mode. Root still faults on cli.

What the boundary costs

./make_syscalls    (write to /dev/null)   703224 us
./make_libcalls    (a function call)        9360 us

~70x. /dev/null does nothing — almost all of it is the crossing.

So: cross it less.

Two ways to cross less

fwrite_buffered      65846 us          sendfile server    ~1800 us
fwrite_unbuffered  1508197 us          write server       ~5900 us

Down — buffer in user space, batch the crossings.

Up — push the whole job into the kernel, remove them.

One idea, two ends.

Where should the code live?

  • Microkernel — drivers are processes. Safe, but every filesystem call is now a crossing.
  • Monolithic — everything privileged, one address space. Fast, but a driver bug is a kernel bug.
  • Unikernel — no boundary; isolation from outside.

No correct answer. Linux is monolithic; seL4 flies aircraft.

Virtualization

A hypervisor is to operating systems what an OS is to applications.

More privileged than the kernel. Each guest OS believes it owns the machine.

Dual role: provider of services, enforcer of isolation.

Isolation needs a boundary. The boundary is entered through system calls, it costs, and OS types and virtualization are where you put it.