Frontend Labs
⋯

Part IX — Hardware & driver programming

Everything through Part VIII runs on top of an OS that already manages memory, files, and processes for you. This Part is what changes when your C code has to talk directly to a physical chip — a sensor, an LED, a USB device — either from a normal process, or from inside the kernel itself. This is the layer most job postings mean by "systems" or "embedded" experience, and where a senior engineer is expected to be able to go from a chip's datasheet to working code.

A note on testing. Every code sample elsewhere in this guide was compiled and run before being written down. The /dev/mem-mmap and serial/termios examples below are ordinary POSIX C and were verified the same way. The Linux-kernel-specific examples (kernel modules, ioctl calls against <linux/i2c-dev.h>/<linux/spi/spidev.h>) cannot be compiled on the machine this guide was written on — a kernel module builds against your running kernel's exact header tree (/lib/modules/$(uname -r)/build), not a generic C toolchain, and linux/*.h headers don't exist outside Linux. What's shown is the standard, extremely stable API every Linux driver tutorial and textbook has used for well over a decade; treat it as accurate boilerplate to adapt, and build/test it on a real Linux box, a Raspberry Pi, or a QEMU VM before trusting it on real hardware.

47. Two worlds: user-space hardware I/O vs. writing a kernel driver

The phrase "write a driver" gets used for two very different jobs, and conflating them is the single most common confusion senior candidates run into in interviews for embedded/systems roles.

User-space hardware I/O Kernel driver
Runs as A normal process Code loaded into the kernel itself
Talks to hardware via A /dev node the kernel already provides (/dev/i2c-1, /dev/spidev0.0, /dev/ttyUSB0, /dev/gpiochip0) Directly: memory-mapped registers, interrupts, DMA
Crash blast radius That one process dies; the OS is fine Can panic the entire machine — there's no memory protection between your bug and the kernel
When you need it The overwhelming majority of real "hardware" work: reading a sensor, driving a motor controller, talking to a USB device, flashing firmware The kernel doesn't already expose the hardware as a /dev node, or you need interrupt-tight integration with a kernel subsystem (a new bus type, a new filesystem, a new network device)
Debugging gdb/lldb, exactly like any other program in this guide printk+dmesg, kernel oops messages, ftrace, sometimes a JTAG debugger or QEMU

Real-world example. A team building a robotics product needs to read an IMU (inertial measurement unit) over I2C and drive motor PWM signals. Neither needs a kernel driver — the SoC vendor's Linux distribution already exposes I2C and PWM as /dev nodes and sysfs files; the actual "driver" work is a user-space C program doing ioctl() calls against those nodes (Section 48). The team only reaches for actual kernel code when they add a custom FPGA-based sensor board with no existing driver and genuinely new interrupt-driven behavior the kernel needs to know about (Sections 50–53). This 90/10 split — mostly user-space I/O, rarely a real kernel module — holds for almost every "hardware" job description you'll see; this Part covers both halves so you can tell, for a given task, which one you actually need.


Generated from real-life-c-guide.md · 8 Oct 2026 16:36