G27
Self-checkedTier 2 18 min reados.io-systems

I/O subsystem, buffering and spooling

How the operating system handles input and output: controllers, drivers and interrupt handlers, memory-mapped and isolated I/O and how it is protected, polling against interrupts against DMA, the services of the kernel I/O subsystem (scheduling, buffering, caching, spooling), blocking, non-blocking and asynchronous I/O, the time saved by single and double buffering, and CPU utilisation when processes wait for I/O.

Why it exists

Programs need the keyboard, the disk, the network and the screen, but devices differ wildly (a keyboard delivers a byte now and then, a disk a block at a time, a network card thousands of packets a second), they are slow next to the CPU, and a program must not be allowed to use them as it likes. The I/O subsystem is the part of the operating system that hides the differences behind a small set of calls, shares the devices safely, overlaps the slow device with useful computation, and protects them. GATE asks what happens in which order when a device interrupts, how I/O is protected, what buffering, caching and spooling are and what they gain, how I/O requests interact with scheduling (a process that does I/O leaves the CPU and later returns to the ready queue), and how much a given buffering scheme saves.

Prerequisites

The hardware side (device registers, polling, interrupts and DMA) is on the I/O interface, interrupts and DMA pages; this page is about what the operating system does with it.

Precise definitions

Hardware parts. A device controller (or adapter) is the electronics that operates a device and has registers (status, command, data) that the CPU reads and writes. A device driver is the kernel module that knows one kind of controller and gives the kernel a uniform interface to it. An interrupt handler (interrupt service routine) is the kernel code that runs when the device signals completion.

How the CPU reaches the registers.

  • Isolated (port-mapped) I/O: the registers have their own address space and special instructions (IN, OUT). These instructions are privileged, so a user program that tries them traps: that is the protection.
  • Memory-mapped I/O: the registers appear at ordinary memory addresses, read and written with ordinary load and store instructions; no special I/O instruction exists to make privileged. Protection is then done by the operating system with the memory protection hardware: the pages that hold device registers are mapped only into the kernel's address space (a user program that touches them takes a protection fault), unless the OS deliberately maps them for one trusted process.

Three ways to move data.

MethodWho moves the dataCPU costBest for
Polling (programmed I/O)the CPU, in a loop that tests the status registerbusy for the whole transfervery fast devices and short waits
Interrupt-driventhe CPU, one unit per interruptone interrupt (save, handle, restore) per unitslow, sporadic devices
DMAthe DMA controller, straight to memorya set-up and one interrupt at the endblock devices, fast transfers

What happens at an interrupt, in order: (1) the device raises the interrupt line; (2) the CPU finishes the current instruction; (3) it saves the state of the running process (program counter and status word) on the control stack; (4) it loads the new program counter from the interrupt vector for that device; (5) the interrupt service routine runs; (6) the saved state is restored and the interrupted process resumes (or another is chosen). The state is saved before the new program counter is loaded and the old one is restored last.

An I/O request through the layers. A process calls read (a system call); the kernel checks the arguments and whether the data is already in a buffer or cache; if not, it passes the request to the driver, which starts the device and the process is put to wait (moved from running to the device's wait queue); when the device finishes it interrupts, the handler completes the request and moves the process back to the ready queue: a process that does I/O enters the ready queue once more for every I/O that finishes.

Kinds of device and I/O call. Block devices (disks) transfer fixed-size blocks and allow random access; character devices (keyboard, serial line) deliver a stream of bytes; network devices have their own interface (sockets). A call can be:

  • blocking: the process is suspended until the I/O completes;
  • non-blocking: the call returns at once with whatever can be done now (possibly nothing, as an error such as "would block");
  • asynchronous: the call returns at once and the whole transfer happens later; the process is told when it is complete (a flag, signal or callback).

Services of the kernel I/O subsystem. I/O scheduling (ordering the requests of each device, as in disk scheduling); buffering; caching; spooling and device reservation; error handling (retry, report); protection (users reach devices only through system calls).

Buffering. A buffer is a memory area that holds data while it moves between two parties. It is used (1) to match speeds (a fast producer and a slow consumer), (2) to match transfer sizes (a network message in packets, a file read in blocks), (3) to keep copy semantics (what is written is the data at the time of the call, even if the program changes its own copy before the device has taken it). Suppose each block takes TT to read from the device into a system buffer, MM to move from the system buffer to the process's area, and CC for the process to work on. The time per block in the steady state is

  • no buffering (read, then compute): T+CT + C;
  • single buffer (the next read overlaps the processing, but the buffer must be emptied first): max⁡(T,C)+M\max(T, C) + M;
  • double buffer (the device fills one buffer while the process uses the other): max⁡(T,  C+M)\max(T,\; C + M).

A circular buffer is a ring of more than two buffers, which smooths out bursts. Adding buffers cannot make the whole faster than the slower of the device and the process.

Caching, buffering and spooling. A cache holds a copy of data that also exists elsewhere, kept for speed (the original is still on disk). A buffer may hold the only copy of data in transit. Spooling (simultaneous peripheral operations on-line) stores the output for a device that cannot be shared, typically a printer, in a file on disk: each process writes its output to its own spool file, and a daemon sends the files to the printer one at a time. Several processes then appear to use the printer at once, nobody holds the printer while doing other work, and one source of deadlock disappears.

CPU utilisation and the degree of multiprogramming. If each of nn processes in memory waits for I/O a fraction pp of its time, independently of the others, the CPU is idle only when all nn are waiting: utilisation=1−pn\text{utilisation} = 1 - p^n.

How it works

Interrupt sequence. The processor-level view: the device interrupts, the current instruction completes, the state is saved, the handler runs, the state is restored.

An interrupt: finish, save, vector, service, return: step 1

Machine run, not typed
start(main program running)
PC
100
SP
1000
I
1
R0
5
IRQ
0
DEV
66
addrdecimalmeaning
064main: ADD R0,#1PC
065main: ADD R0,#1
066main: ADD R0,#1
004200vector table entry 4 (keyboard)
0C8ISR: PUSH R0
0C9ISR: IN R0,[DEV]
0CAISR: ST R0,[300]
0CBISR: POP R0
0CCISR: IRET
12C.input buffer
3E7.stack
3E6.stack
3E5.stack

Step 1

The main program is adding 1 to R0 (R0 = 5). The interrupt-enable flag I is 1, so interrupts are accepted. The stack grows downward from 1000. Entry 4 of the interrupt vector table holds the address of the keyboard routine (200). A key is about to be pressed: the keyboard will put its code (66) in its data register DEV and raise an interrupt request.

1 / 16
computed by script: a simulation of the machine or protocol, one event per step. python3 scripts/gen-coa-io-traces.py

DMA. The CPU sets up the controller and goes on; the controller moves the block and interrupts once at the end.

A DMA transfer of 4 words by cycle stealing: step 1

Machine run, not typed
start(CPU running a program)
AR
-
WC
-
CTRL
-
DREQ
0
HOLD
0
HLDA
0
bus
CPU
IRQ
0
addrdecimalmeaning
200.memory buffer
201.memory buffer
202.memory buffer
203.memory buffer

Step 1

A disk controller must deliver 4 words to memory at 512..515. With programmed I/O or interrupts the CPU would move every word. With DMA the CPU only starts the transfer. The DMA controller (DMAC) has an address register AR, a word count WC and a control register; it can take over the buses (the CPU is the bus master now).

1 / 29
computed by script: a simulation of the machine or protocol, one event per step. python3 scripts/gen-coa-io-traces.py

What each method costs the CPU. A 4096-byte block from a slow device and from a fast one. A fast device cannot be served by an interrupt per byte at all: the CPU is overloaded.

CPU time to transfer a 4096-byte block: polling, an interrupt per byte, or DMA: start

Computed by a script, not typed
devicetransfer timeCPU time, pollingCPU time, interrupt per byte (8 µs each)CPU time, DMA (30 µs set-up + 8 µs interrupt)
slow device: one byte every 50 µs (20 KB/s)
fast device: one byte every 0.5 µs (2 MB/s)

Step 0

Interrupts are the right tool for slow, sporadic devices; fast devices that move blocks need DMA. The 8 µs, 30 µs figures are illustrative round numbers.

0 / 2
computed by script: exact arithmetic, an event simulation of buffering and a seeded Monte Carlo run. python3 scripts/gen-os-io-traces.py

What buffering buys. Ten blocks, three combinations of times. One buffer saves the overlap of reading and computing; a second buffer also hides the move; when the process is the bottleneck, nothing helps.

Time to input and process 10 blocks with no buffer, one buffer and two buffers: start

Computed by a script, not typed
parameters (time units)no buffersingle bufferdouble buffer
T = 10, C = 5, M = 1
T = 4, C = 10, M = 1
T = 8, C = 8, M = 2

Step 0

Event simulation of the device, the system buffers and the process (scripts/os_io.py). Per block in the steady state: no buffer T + C, one buffer max(T, C) + M, two buffers max(T, C + M). The second line shows that when the process is the bottleneck, a second buffer gains nothing.

0 / 3
computed by script: exact arithmetic, an event simulation of buffering and a seeded Monte Carlo run. python3 scripts/gen-os-io-traces.py

Why the operating system keeps several processes. While one process waits for its I/O, another can use the CPU.

CPU utilisation as the degree of multiprogramming grows: 1 - p^n: start

Computed by a script, not typed
degree of multiprogramming nutilisation, p = 0.5utilisation, p = 0.8simulated, p = 0.8
1 process in memory
2 processes in memory
3 processes in memory
4 processes in memory
5 processes in memory
6 processes in memory
7 processes in memory
8 processes in memory

Step 0

p is the fraction of its time a process spends waiting for I/O; the processes are assumed independent, which is a model, not a law (real processes queue behind the same disk).

0 / 8
computed by script: exact arithmetic, an event simulation of buffering and a seeded Monte Carlo run. python3 scripts/gen-os-io-traces.py

Worked examples

EX 1Order of events at an interrupt, and who handles the protection

Easy

A device controller interrupts while process L runs. The correct order is: the processor finishes the current instruction, pushes the status of L on the control stack, loads the new program counter from the vector, executes the interrupt service routine, and finally pops the status of L. The state of L must be saved before the program counter is overwritten, and restored only after the routine has finished. For protection: a CPU with explicit I/O instructions makes them privileged; a CPU with memory-mapped I/O has no such instruction, so the operating system protects the device registers by mapping their addresses only into the kernel's space with the memory protection hardware. A user process cannot reach the device except through system calls.

EX 2Buffering: how much does each scheme save?

Medium

A program reads 10 blocks and processes each. A block takes T=10T = 10 to arrive, M=1M = 1 to copy into the program's area and C=5C = 5 to process. The C program simulates the device, the buffers and the process for no buffer, one buffer and two buffers, for two parameter sets.

#include <stdio.h>
 
#define MAXN 64
 
static int max(int a, int b) { return a > b ? a : b; }
 
/* time at which block n has been processed; buffers = 0 (read straight into the program's area), 1 or 2 */
static int total(int n, int T, int C, int M, int buffers) {
    if (buffers == 0) return n * (T + C);
    int read_end[MAXN] = {0}, move_end[MAXN] = {0}, comp_end[MAXN] = {0};
    for (int i = 1; i <= n; i++) {
        int start;
        if (buffers == 1) start = move_end[i - 1];                                  /* the buffer is free once the previous block has been copied out */
        else start = max(read_end[i - 1], i >= 2 ? move_end[i - 2] : 0);            /* the device fills the buffer that is free, one block at a time */
        read_end[i] = start + T;
        move_end[i] = max(read_end[i], comp_end[i - 1]) + M;                        /* the program is ready and the block has arrived */
        comp_end[i] = move_end[i] + C;
    }
    return comp_end[n];
}
 
int main(void) {
    int cases[2][3] = {{10, 5, 1}, {4, 10, 1}};                                      /* T, C, M */
    for (int k = 0; k < 2; k++) {
        int T = cases[k][0], C = cases[k][1], M = cases[k][2];
        printf("T=%d C=%d M=%d, 10 blocks: no buffer %d, single %d, double %d; per block %d, %d, %d\n", T, C, M,
               total(10, T, C, M, 0), total(10, T, C, M, 1), total(10, T, C, M, 2),
               total(11, T, C, M, 0) - total(10, T, C, M, 0), total(11, T, C, M, 1) - total(10, T, C, M, 1), total(11, T, C, M, 2) - total(10, T, C, M, 2));
    }
    return 0;
}
T=10 C=5 M=1, 10 blocks: no buffer 150, single 115, double 106; per block 15, 11, 10
T=4 C=10 M=1, 10 blocks: no buffer 140, single 114, double 114; per block 14, 11, 11

Reading it. First set: without a buffer every block costs T+C=15T + C = 15; with one buffer reading the next block overlaps the processing, and the cost per block is max⁡(10,5)+1=11\max(10, 5) + 1 = 11; with two buffers it is max⁡(10,5+1)=10\max(10, 5 + 1) = 10: the device is now the bottleneck, so it cannot get better. Without a buffer the total is exactly 10 times 15. With buffers the totals (115 and 106) are a little more than 10 times the per-block time (110 and 100), because the first block has to arrive before the overlap starts. Second set: the process is the bottleneck (C=10C = 10 against T=4T = 4); one buffer gives max⁡(4,10)+1=11\max(4, 10) + 1 = 11 and two buffers give max⁡(4,10+1)=11\max(4, 10 + 1) = 11: a second buffer gains nothing.

EX 3Blocking, non-blocking, and how many calls a buffer saves

Medium

A pipe is a small character device inside the kernel. The program makes the read end non-blocking and reads from the empty pipe, then writes five bytes and reads again; then it counts how many write system calls are needed to send 64 KiB to a file with different chunk sizes.

#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
 
static long calls_for(int fd, size_t total, size_t chunk) {
    static char buf[65536];
    long calls = 0;
    while (total > 0) {
        size_t k = total < chunk ? total : chunk;
        ssize_t w = write(fd, buf, k);
        if (w < 0) return -1;
        total -= (size_t)w;
        calls++;
    }
    return calls;
}
 
int main(void) {
    int p[2];
    if (pipe(p) != 0) return 1;
    if (fcntl(p[0], F_SETFL, fcntl(p[0], F_GETFL) | O_NONBLOCK) != 0) return 1;
    char buf[16];
    ssize_t n = read(p[0], buf, sizeof buf);
    printf("empty pipe, non-blocking read: returned %zd, error is 'would block': %s\n", n, (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) ? "yes" : "no");
    if (write(p[1], "hello", 5) != 5) return 1;
    n = read(p[0], buf, sizeof buf);
    printf("after writing 5 bytes, the same read returned %zd\n", n);
 
    int fd = open("/dev/null", O_WRONLY);
    if (fd < 0) return 1;
    size_t chunk[4] = {1, 512, 4096, 65536};
    for (int i = 0; i < 4; i++) printf("sending 65536 bytes in chunks of %5zu bytes: %ld write calls\n", chunk[i], calls_for(fd, 65536, chunk[i]));
    close(fd);
    return 0;
}
empty pipe, non-blocking read: returned -1, error is 'would block': yes
after writing 5 bytes, the same read returned 5
sending 65536 bytes in chunks of     1 bytes: 65536 write calls
sending 65536 bytes in chunks of   512 bytes: 128 write calls
sending 65536 bytes in chunks of  4096 bytes: 16 write calls
sending 65536 bytes in chunks of 65536 bytes: 1 write calls

Reading it. A blocking read on the empty pipe would have put the process to sleep; the non-blocking one returns at once with an error that means "nothing now, ask again". After the write it returns the five bytes. Every write is a system call, with the trap into the kernel and the checks that this costs, so collecting the output in a buffer and writing it in blocks of 4096 bytes needs 16 calls where one byte at a time needs 65536: this is the reason that the C library buffers printf output, and why a buffer is useful even when the device is not slow.

EX 4CPU utilisation with several processes

Medium

Each process in memory spends 80 per cent of its time waiting for I/O (p=0.8p = 0.8). One process uses the CPU 1−0.8=201 - 0.8 = 20 per cent of the time. With nn processes the CPU is idle only if all wait: 1−0.8n1 - 0.8^n, which is 48.848.8 per cent for 3 processes and 83.283.2 per cent for 8 (the table above, with a simulation that agrees). To reach 90 per cent we need 0.8n≤0.10.8^n \le 0.1, so n≥10.3n \ge 10.3: eleven processes (1−0.810=89.31 - 0.8^{10} = 89.3 per cent is not enough, 1−0.811=91.41 - 0.8^{11} = 91.4 per cent is). The formula assumes the processes wait independently; if they all queue for one disk, the real figure is lower.

EX 5Which statement is true?

GATE level

Spooling lets several processes share a printer. True, by giving each its own file and printing the files in turn. A cache and a buffer are the same thing. False: a cache holds a copy of data that exists elsewhere, a buffer can hold the only copy. With one buffer, the next block can be read while the previous one is being processed, but the buffer must first be copied out. True (the MM in max⁡(T,C)+M\max(T, C) + M). Double buffering always halves the time. False: it gives max⁡(T,C+M)\max(T, C + M) per block, which is at best max⁡(T,⋅)\max(T, \cdot): no better than the slower of the device and the process. DMA is useful for a keyboard. No: a keyboard delivers a byte at a time slowly, and interrupts handle that comfortably; DMA is for block transfers. A process that has finished an I/O returns to the ready queue, not directly to the CPU. True: the scheduler then chooses. In the question about the number of times a process enters the ready queue because of I/O, count one entry for each I/O that completes.

Cheat box

Cheat box

  • Interrupt order: finish instruction →\to save state →\to load new PC →\to run ISR →\to restore.
  • Isolated I/O: privileged IN/OUT. Memory-mapped I/O: no special instruction; the OS protects the registers with memory protection (kernel-only mapping).
  • Polling: CPU busy. Interrupts: per unit. DMA: set-up plus one interrupt; for blocks.
  • Per block: no buffer T+CT + C; single max⁡(T,C)+M\max(T, C) + M; double max⁡(T,C+M)\max(T, C + M). Never better than the slower side.
  • Cache: copy of data elsewhere. Buffer: data in transit. Spool: output file for a device that cannot be shared.
  • Blocking: sleeps until done. Non-blocking: returns at once with what is possible. Asynchronous: returns at once, completes later.
  • CPU utilisation =1−pn= 1 - p^n.
  • After an I/O completes the process goes to the ready queue.

Traps and GATE patterns

Connections

The interrupt and the DMA transfer are hardware mechanisms (interrupts, DMA); here the operating system builds buffering, caching and spooling on top of them. The request enters the kernel through a system call; the process waits and later returns to the ready queue as in the process state diagram. The disk is the device with the longest delays and has its own request ordering in disk scheduling. Spooling removes the "no sharing" condition for printers and so is one of the answers to deadlock.

PYQs

5 previous year questions from your book. I re-solved each one myself (3 by running or simulating, 2 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2014 · NATQ35 · page 301 of the book
Three processes A, B and C each execute a loop of
100 iterations. In each iteration of the loop, a
process performs a single computation that requires
tc CPU milliseconds and then initiates a single I/O
operation that lasts for tio milliseconds. It is
assumed that the computer where the processes
execute has sufficient number of I/O devices and
the OS of the computer assigns different I/O
devices to each process. Also, the scheduling
overhead of the OS is negligible. The processes
have the following characteristics:

Process id
TC
Tio
A
100 ms
500 ms
B
350 ms
500 ms
C
200 ms
500 ms

The processes A, B, and C are started at times 0, 5
and 10 milliseconds respectively, in a pure time
sharing system (round robin scheduling) that uses a
time slice of 50 milliseconds. The time in
milliseconds at which process C would complete its
first
I/O
operation
is
___________.
GATE 2006 · MCQ · 2 marksQ52 · page 304 of the book
Consider three processes, all arriving at time zero,
with total execution time of 10, 20, and 30 units,
respectively. Each process spends the first 20% of
execution time doing I/O, the next 70% of time doing
computation, and the last 10% of time doing I/O
again.

The operating system uses a shortest remaining
compute time first scheduling algorithm and
schedules a new process either when the running
process gets blocked on I/O or when the running
process finishes its compute burst. Assume that all
I/O operations can be overlapped as much as possible.

For what percentage of time does the CPU remain
idle?
  • (a)0%
  • (b)10.6%
  • (c)30.0%
  • (d)89.4%
GATE 2025 · NATQ4 · page 357 of the book
Suppose in a multiprogramming environment, the
following C program segment is executed. A process
goes into I/O queue whenever an I/O related
operation is performed. Assume that there will always
be a context switch whenever a process requests for
an I/O, and also whenever the process returns from an
I/O. The number of times the process will enter the
ready queue during its lifetime (not counting the time
the process enters the ready queue when it is run
initially) is ______

Int main()
{

int x = 0, i = 0;

 scanf("%d", &x);

 for(i = 0; i < 20; i++)

 {

x = x + 20;

printf("%d\n", x);

 }

return 0;
}

OPERATING SYSTEMS
GATE 2018 · MCQ · 1 markQ11 · page 358 of the book
The following are some events that occur after a
device controller issues an interrupt while process L
is under execution:

P. The processor pushes the process status of L onto
the control stack
Q. The processor finishes the execution of the
current instruction
R. The processor executes the interrupt service routine
S. The processor pops the process status of L from
the control stack
T. The processor loads the new PC value based on
the interrupt

Which of the following is the correct order in which
the events above occur?
  • (a)QPTRS
  • (b)PTRSQ
  • (c)TRPQS
  • (d)QTPRS OPERATING SYSTEMS
GATE 2005 · MCQ · 1 markQ30 · page 361 of the book
Normally user programs are prevented from
handling I/O directly by I/O instructions in them.
For CPUs having explicit I/O instructions, such I/O
protection is ensured by having the I/O instruction
privileged. In a CPU with memory mapped I/O,
there is no explicit I/O instruction. Which one of the
following is true for a CPU with memory mapped
I/O?
  • (a)I/O protection is ensured by operating system routine(s)
  • (b)I/O protection is ensured by a hardware trap
  • (c)I/O protection is ensured during system configuration
  • (d)I/O protection is not possible OPERATING SYSTEMS

Self-check

Question 1

Put in order: (a) the interrupt service routine runs, (b) the CPU finishes the current instruction, (c) the CPU saves the process state, (d) the CPU loads the program counter of the routine, (e) the saved state is restored.

Show answer

(b), (c), (d), (a), (e).

Question 2

With memory-mapped I/O, which part of the system stops a user program from writing to a device register?

Show answer

The operating system, through the memory protection hardware: the device pages are mapped only into kernel space, so the user access faults.

Question 3

T=6T = 6, C=4C = 4, M=1M = 1. Time per block with no buffer, one buffer and two buffers?

Show answer

No buffer: 6+4=106 + 4 = 10. One buffer: max⁡(6,4)+1=7\max(6, 4) + 1 = 7. Two buffers: max⁡(6,4+1)=6\max(6, 4 + 1) = 6.

Question 4

Three processes, each waiting for I/O half of the time. CPU utilisation?

Show answer

1−0.53=0.8751 - 0.5^3 = 0.875, 87.5 per cent.

Question 5

A process reads from a pipe that is empty, with a non-blocking call. What happens?

Show answer

The call returns at once with an error meaning "would block" (nothing available now); it does not sleep. A blocking call would put the process in the wait queue until data arrived.

Revision card

One-screen revision

  • Interrupt: finish instruction, save state, load PC, ISR, restore. After I/O the process goes to the ready queue.
  • Isolated I/O: privileged instructions. Memory-mapped: OS protects through memory mapping.
  • Polling / interrupt per unit / DMA for blocks.
  • Buffer time per block: T+CT + C, max⁡(T,C)+M\max(T, C) + M, max⁡(T,C+M)\max(T, C + M).
  • Cache = copy; buffer = data in transit; spooling = shared output through a disk file.
  • Blocking / non-blocking / asynchronous. Utilisation 1−pn1 - p^n.

References

  • ·Silberschatz, Galvin, Gagne, Operating System Concepts, Chapter 12 I/O Systems (I/O hardware, application I/O interface, kernel I/O subsystem, transforming I/O requests to hardware operations)
  • ·Tanenbaum and Bos, Modern Operating Systems, Chapter 5 Input/Output (principles of I/O hardware and software, I/O software layers)
  • ·Stallings, Operating Systems: Internals and Design Principles, Chapter 11 I/O Management and Disk Scheduling (I/O buffering)

Verification

The buffering totals (no buffer, one buffer, two buffers) come from an event simulation (scripts/os_io.py) that scripts/enum-checks/os_io_check.py recomputes with a different, tick-by-tick simulation on 1728 parameter combinations; the steady-state periods T + C, max(T, C) + M and max(T, C + M) are checked against both. The CPU-utilisation table uses the formula 1 - p^n and a seeded Monte Carlo run, which the check repeats with other seeds. The C programs (a buffering simulation whose output is compared by the check with the table, a non-blocking read on a real pipe, and the number of write calls for different chunk sizes) are run by npm run verify:c. The polling, interrupt and DMA times use made-up round numbers for the interrupt and set-up costs. The sequence of events at an interrupt, the services of the kernel I/O subsystem and the device classes are textbook descriptions, not computed. Memory-mapped I/O protection is described as the operating system's job using memory protection (the usual answer); a particular architecture may differ. Textbook chapter titles are from memory. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand. No defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never