,

Contents · Device drivers and syscalls


User–Kernel Boundary and Syscalls

  • Syscalls are the stable ABI between user space and the kernel. They pass arguments via registers and memory and return results or errno.
  • VFS routes file-like syscalls (open/read/write/ioctl/mmap) to the right filesystem or driver.
  • Socket API routes to protocol stacks and network drivers.
// Trace a syscall path (conceptual)
function readSyscall(fd, buf, len){
  // 1) validate args & permissions
  // 2) resolve file ops table
  // 3) call fops.read() implemented by FS/driver
}

Driver Model and Types

  • Bus drivers (PCI/USB/I2C/SPI) enumerate devices and match them to device drivers via IDs.
  • Subsystems expose common ops tables: file ops, netdev ops, block ops, DRM, input, ALSA, etc.
  • udev (userspace) names devices and manages permissions via rules; devfs/sysfs expose metadata.

Character, Block, and Network Drivers

  • Char drivers: stream/byte interfaces; implement read/write/ioctl/poll; examples: serial, input, misc.
  • Block drivers: request-based; queues, schedulers, barriers; examples: NVMe, SCSI.
  • Net drivers: NAPI, RX/TX rings, checksums/TSO/GSO; multi-queue with MSI-X and RSS.
// Minimal file-ops concept (pseudo)
const fops = {
  read(file, buf, len){ /* copy_to_user from device buffer */ },
  write(file, buf, len){ /* copy_from_user into device buffer */ },
  ioctl(file, cmd, arg){ /* dispatch control ops */ },
  mmap(file, vma){ /* map device or DMA buffer into user */ }
};

ioctl, ioctl alternatives, and uAPI design

  • ioctl: device-specific control commands; flexible but hard to version and audit.
  • Prefer typed netlink, configfs, or dedicated syscalls for complex configuration.
  • Design stable uAPIs: fixed-size structs with version fields; no pointers; document endian and alignment.

mmap, DMA, and zero-copy paths

  • mmap maps device memory or DMA buffers into user space to avoid copies; needs proper page fault handlers.
  • IOMMU enables safe device-visible mappings; pinning pages prevents migration during DMA.
  • Zero-copy often pairs with rings (io_uring, AF_XDP, SPDK) to avoid per-IO syscalls.
// Zero-copy read sketch
function zeroCopyRead(ring){
  // user posts buffers to a ring; driver DMA-fills them; completion events notify user
}

io_uring and async syscalls

  • io_uring provides submission/completion queues shared between user and kernel.
  • Supports files, sockets, timeouts, accept/connect, and more, reducing context switches.
  • Registered buffers and files reduce per-op overhead; SQPOLL enables kernel polling.
// EMAT-style estimate with io_uring batching
function opsPerSec(syscallCost, batch, deviceLatency){
  const perOp = (syscallCost/batch) + deviceLatency; return 1/perOp;
}

Concurrency, interrupts, and reference counts

  • Split top-half (hard IRQ) and bottom-half (softirq/tasklet/NAPI) to minimize IRQ latency.
  • Protect shared rings and state with spinlocks in IRQ context; use mutexes in process context.
  • Refcount all exposed objects; use RCU for read-mostly tables and wait for grace periods before freeing.

Exercises

  1. Design a char device uAPI: define structs, versioning, and an ioctl set; discuss future-proofing.
  2. Simulate a ring-based driver with IRQ and NAPI-like polling; vary coalescing and batch size.
  3. Prototype zero-copy using mmap'd buffers and measure vs. read/write throughput.
Good driver uAPIs are stable, typed, and copy-safe; high performance hinges on DMA, batching, and careful concurrency.