The boundary · L4 and L3

Devices as files

A device worth reaching is a file under /dev, served by its driver (sysk 046). The first is entropy: cat /dev/rng shows fresh random bytes, every time. The driver holds the device and answers the same files protocol the kernel answers for /sys and the file server for /, so the shell needed no new command, and the kernel no new line.

Three trees, one namespace

The shell's namespace is three trees. The first name in a path says which server answers: /sys the kernel, through a call; /dev the entropy driver, over a pair of channels; anything else the file server, which keeps its tree on the disk. Pick a path to see where its requests go and what the shell says.

Every top-level directory is one server (docs/directions.md, "The file tree"), and the shell lists the other trees' names at the root: ls / shows dev/ net/ prg/ sys/ after the disk's own, its namespace's mounts (sysk 053, Files). Writing, making and removing are the disk's alone: the shell refuses them on any other tree before it asks, and the servers refuse them too.

A stream has no end

A file in the kernel's tree is a snapshot, rendered whole when it is read. A file on the disk has a length. /dev/rng has neither: every read is new bytes, as many as asked, and offsets mean nothing. The files protocol already named this kind, stream, in the formats research (sysk 026, F5), beside dir, file and ctl; sysk 046 is its first use. Its format is bytes, which promises nothing about the contents, unlike sexp.

So cat cannot read to the end, as it does a file. On a stream it reads once, as much as one line shows, and shows bytes in hex: 31 bytes, 62 digits. A program that wants entropy reads as much as it needs, up to a payload's 512 bytes a request.

a line of the same shape, made here by your browser's own generator

One cat, five messages

Every request and reply is 576 bytes: a 64-byte header and a 512-byte payload (Files). The shell names nodes with fids, small numbers the driver keeps for it in an 8-byte table: a node and whether it is open. Select a row to see what the driver does.

The shell asksThe driver answers

The device lends nothing back but bytes

The entropy device is the simplest virtio device there is. It has one queue, and a request is one buffer for the device to fill (virtio 1.2 §5.4). The specification forbids a part the device would read, so a read of n bytes allocates exactly n and lends all of it, written from the first part on. What the device wrote comes back through virtio-written, at most what was lent:

(let b (alloc h count))
(let l (virtio-lend dev q b 0 0 0))      ; one part, the device writes it
(while (not (virtio-poll q))
  (invariant (and (running k) (queue-busy q) (lent-for q l) (queue-lent q count) (== (len wants) 3)))
  (let s (alt k wants 2))                ; the device's interrupt
  (virtio-ack dev)
  (device-ack k dev))
(let got (virtio-take q l))
(let w (virtio-written q))               ; ≤ count: the queue says so

This is the measure sysk 042 set for its generic queue: a second kind of device landed with no line changed in kernel/devices.sysp. Everything that keeps the device from reaching the wrong memory, lending as a move, addresses only from the queue's own file, contents unknown until read, holds for it unchanged (Storage). The entropy is QEMU's, from the host's own generator; on real hardware it would be the device's. sysk trusts it as it trusts the disk to hold what was written.

The manifest

The device is a line in arch/rv64/devices.sysk, on QEMU's second virtio transport, and the system hands it to one task, which serves the shell over a pair of streams:

(virtio-rng (mmio 268443648 4096) (irq 2))
…
(dev-ask (bytes 576))
(dev-reply (bytes 576))
(task shell … (tx dev-ask) (rx dev-reply))
(task rng-driver (device virtio-rng) (heap 4096) (rx dev-ask) (tx dev-reply))

When input ends, the shell ends dev-ask, reads dev-reply to its end, and the driver closes the device and finishes: the deadlock check proves every run can still finish. On a machine without the device, the driver still serves its tree, and every read answers io: the shell says device failed: /dev/rng.

What comes next

With one device, /dev is its driver's tree. A second, such as the console at /dev/cons, asks a question best answered with both in view: the shell's namespace could make /dev a union of the drivers' trees, as Plan 9 binds each kernel device into /dev, or one small server could front them. The shell's side was made ready in sysk 048: its namespace is one value, Ns, so a tree more is a field more; and since sysk 053 the namespace is a table the manifest gives, so a union is a mount one name deeper, ("/dev/cons" cons-ask) beside ("/dev" dev-ask), when the second device comes.