Cheat SheetsOperating SystemsI/O & Kernel

I/O & Kernel — Cheat Sheet

Operating Systems · 4 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
I/O & Kernel
Operating Systems4 topicsQuick revision reference
1

I/O Techniques: Polling, Interrupts & DMA

Three I/O techniques — polling (CPU busy-waits), interrupt-driven (CPU is notified when device is ready), and DMA (device transfers data directly to memory) — trade CPU utilisation against I/O throughput.

  • Polling wastes CPU cycles and is only appropriate for very fast devices where interrupt overhead exceeds benefit.
  • Interrupt-driven I/O allows the CPU to do useful work while waiting for slow devices like disks and networks.
  • DMA offloads bulk data transfer from the CPU to a dedicated controller, interrupting the CPU only on completion.
  • Java NIO Selector uses OS-level mechanisms (epoll on Linux, kqueue on macOS) — the same interrupt model, applied to sockets.
  • AsynchronousFileChannel provides non-blocking file I/O with completion callbacks, hiding DMA-level operations behind the JVM.
  • Context-switch overhead makes polling viable only for sub-microsecond I/O (e.g., network cards with busy-polling mode).
Java — NIO Selector (interrupt-driven I/O model)
import java.nio.channels.*;
import java.nio.ByteBuffer;
import java.net.InetSocketAddress;
import java.util.Set;

// Polling analogy (BAD — busy-wait):
// while (!socket.hasData()) { /* spin */ }
// data = socket.read();

// NIO Selector: OS interrupt-driven I/O surfaced to Java
// One thread handles thousands of connections via event notification
Selector selector = Selector.open();

// Open a non-blocking server socket
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false); // non-blocking mode
server.bind(new InetSocketAddress(8080));

// Register interest in ACCEPT events (OS will notify us)
server.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    // Blocks until at least one channel is ready (OS interrupt → wakes up)
    int readyCount = selector.select(); // equivalent to epoll_wait() on Linux
    if (readyCount == 0) continue;

    Set<SelectionKey> keys = selector.selectedKeys();
    for (SelectionKey key : keys) {
        if (key.isAcceptable()) {
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
            System.out.println("Client connected: " + client.getRemoteAddress());
        } else if (key.isReadable()) {
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buf = ByteBuffer.allocate(1024);
            int bytes = client.read(buf);
            if (bytes > 0) {
                buf.flip();
                System.out.println("Received: " + new String(buf.array(), 0, bytes));
            }
        }
    }
    keys.clear(); // must clear processed keys
}
2

Disk Scheduling Algorithms

Disk scheduling algorithms determine the order in which I/O requests are serviced to minimise total disk head seek time and improve throughput.

  • FCFS is fair but has the worst average seek time; never use it for high-throughput disk workloads.
  • SSTF minimises seek time on average but can starve requests at the extremes of the disk.
  • SCAN (elevator) prevents starvation by guaranteeing the head will eventually reach every position.
  • C-SCAN provides more uniform wait times than SCAN by only servicing in one direction.
  • SSDs have no moving parts, so seek time is negligible — disk scheduling algorithms are largely irrelevant for NVMe.
  • Linux's mq-deadline is the recommended scheduler for HDDs; use none for SSDs.
Java — FCFS, SSTF, SCAN disk scheduling calculation
import java.util.*;

int[] requests = {98, 183, 37, 122, 14, 124, 65, 67};
int head = 53;

// ── FCFS ─────────────────────────────────────────────────────────────────────
// Service in arrival order: 53→98→183→37→122→14→124→65→67
// Movement: |98-53|+|183-98|+|37-183|+|122-37|+|14-122|+|124-14|+|65-124|+|67-65|
//         =   45  +   85   +  146   +   85   +  108   +   110  +   59   +   2  = 640
int fcfsTotal = 0, cur = head;
for (int r : requests) { fcfsTotal += Math.abs(r - cur); cur = r; }
System.out.println("FCFS total movement: " + fcfsTotal); // 640

// ── SSTF ─────────────────────────────────────────────────────────────────────
// Always service closest request: 53→65→67→37→14→98→122→124→183
// Movement: 12+2+30+23+84+24+2+59 = 236
List<Integer> sstfQueue = new ArrayList<>();
for (int r : requests) sstfQueue.add(r);
int sstfTotal = 0, sstfCur = head;
while (!sstfQueue.isEmpty()) {
    int nearest = sstfQueue.stream()
        .min(Comparator.comparingInt(r -> Math.abs(r - sstfCur))).get();
    sstfTotal += Math.abs(nearest - sstfCur);
    sstfCur = nearest;
    sstfQueue.remove(Integer.valueOf(nearest));
}
System.out.println("SSTF total movement: " + sstfTotal); // 236

// ── SCAN (Elevator) ──────────────────────────────────────────────────────────
// Head moves toward higher tracks first: 53→65→67→98→122→124→183→37→14
// (reaches 183, reverses direction, services 37 then 14)
// Movement: 12+2+31+24+2+59+146+23 = 299 (approximately)
int[] scanOrder = {65, 67, 98, 122, 124, 183, 37, 14};
int scanTotal = 0, scanCur = head;
for (int r : scanOrder) { scanTotal += Math.abs(r - scanCur); scanCur = r; }
System.out.println("SCAN total movement: " + scanTotal); // 299
3

System Calls & Kernel Mode

System calls are the controlled interface through which user-space programs request privileged kernel services, requiring a CPU mode switch from user mode to kernel mode.

  • System calls are the only legal way for user-space programs to request kernel services; direct kernel access is forbidden.
  • A mode switch (user → kernel → user) costs ~100–1000 ns; minimising unnecessary system calls improves performance.
  • Buffered I/O (BufferedInputStream) reduces system call frequency by batching reads into large kernel buffer requests.
  • Java's FileInputStream, Thread.start(), and Socket operations all eventually invoke OS system calls via JNI.
  • ProcessHandle.current().pid() maps to getpid(); Files.createFile() maps to open() with O_CREAT.
  • strace on Linux and dtrace on macOS reveal exactly which system calls a Java application makes.
Java — System call mappings for common Java operations
// Java I/O ultimately triggers OS system calls
// Every FileInputStream.read() call eventually invokes sys_read

// You can observe this with strace on Linux:
//   strace -e trace=read,write,open java MyProgram

import java.io.*;
import java.nio.file.*;

// FileInputStream.read() → native method → sys_read(fd, buf, count)
try (var fis = new FileInputStream("/tmp/data.txt")) {
    byte[] buf = new byte[1024];
    int n = fis.read(buf);  // → JNI → sys_read() system call
    System.out.println("Read " + n + " bytes");
}

// Files.createFile() → sys_open(path, O_CREAT | O_WRONLY, mode)
Path p = Path.of("/tmp/newfile.txt");
Files.createFile(p);  // → sys_open / sys_creat system call

// Thread.start() → eventually → sys_clone() (Linux) / CreateThread (Windows)
Thread t = new Thread(() -> System.out.println("PID: " +
    ProcessHandle.current().pid())); // getpid() system call
t.start();

// InetSocketAddress / ServerSocket → sys_socket, sys_bind, sys_listen
// Socket.connect() → sys_connect
// Socket.accept() → sys_accept

// ProcessHandle.current().pid() → sys_getpid()
long pid = ProcessHandle.current().pid();
System.out.println("JVM PID: " + pid);

// System.currentTimeMillis() → sys_gettimeofday / clock_gettime
long now = System.currentTimeMillis();
4

Memory-Mapped I/O & Files

Memory-mapped files map file contents directly into a process's virtual address space, allowing file I/O to be performed as ordinary memory operations with OS-managed demand paging.

  • Memory-mapped files eliminate double-buffering by mapping the OS page cache directly into the process virtual address space.
  • The OS loads pages on demand via page faults — the entire file is never loaded at once, saving memory.
  • FileChannel.map() returns a MappedByteBuffer; use MapMode.READ_ONLY, READ_WRITE, or PRIVATE (copy-on-write).
  • Memory-mapped I/O is ideal for large files with random access patterns; sequential small files may not benefit.
  • MappedByteBuffer.force() is equivalent to msync() — it flushes modified mapped pages to disk.
  • High-performance Java libraries (Chronicle Map, Aeron, Disruptor) use memory-mapped files for sub-microsecond IPC.
Java — FileInputStream vs MappedByteBuffer performance comparison
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.io.*;

Path file = Path.of("/tmp/large.csv");

// ── Traditional: FileInputStream (double-buffered) ────────────────────────
long startTraditional = System.nanoTime();
try (var fis = new FileInputStream(file.toFile());
     var bis = new BufferedInputStream(fis, 65536)) {
    byte[] buf = new byte[65536];
    long totalBytes = 0;
    int n;
    while ((n = bis.read(buf)) != -1) totalBytes += n;
    System.out.println("Traditional: " + totalBytes + " bytes");
}
long traditionalMs = (System.nanoTime() - startTraditional) / 1_000_000;

// ── Memory-Mapped: FileChannel.map() (zero extra copy) ───────────────────
long startMapped = System.nanoTime();
try (FileChannel fc = FileChannel.open(file)) {
    long fileSize = fc.size();

    // Map entire file into virtual address space (READ_ONLY)
    // Mode: READ_ONLY, READ_WRITE, or PRIVATE (copy-on-write)
    MappedByteBuffer mapped = fc.map(
        FileChannel.MapMode.READ_ONLY,
        0,          // starting position in file
        fileSize    // number of bytes to map
    );
    // mapped now lives in virtual address space
    // OS pages in data on demand — no full load upfront

    long totalBytes = 0;
    while (mapped.hasRemaining()) {
        mapped.get();  // direct memory read — no syscall per byte
        totalBytes++;
    }
    System.out.println("Memory-mapped: " + totalBytes + " bytes");

    // Force all pages to be loaded and written to disk (if READ_WRITE)
    mapped.force(); // optional: flush modified mapped region to disk
}
long mappedMs = (System.nanoTime() - startMapped) / 1_000_000;

System.out.printf("Traditional: %dms | Memory-mapped: %dms%n",
    traditionalMs, mappedMs);
// Memory-mapped is typically 2-5x faster for large sequential reads
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/operating-systems