Loading content...
When you plug in a USB drive, a new /dev/sdb file appears. When you connect a Bluetooth mouse, /dev/input/mouse0 materializes. When a Docker container starts, its /dev directory contains exactly the devices it needs. But how do these device files get created? Who decides their names, permissions, and major/minor numbers?
In the early Unix era, device files were created manually by administrators running mknod. A static /dev directory contained thousands of device files—most representing hardware that didn't exist on that particular system. This was wasteful, confusing, and made hotplug impossible.
devtmpfs solved this by creating a tmpfs instance where the kernel itself creates and removes device nodes dynamically as devices are detected. Combined with udev (which sets permissions and creates symlinks), devtmpfs provides a dynamic, minimal /dev that reflects only the actual hardware present.
By the end of this page, you will understand how devtmpfs works, its relationship with udev, the device number system (major/minor), and how modern Linux systems boot with a minimal /dev that grows dynamically. You'll see how this enables hotplug, containers, and embedded systems.
The evolution of /dev management reflects Linux's journey from static configuration to dynamic, hotplug-aware systems. Understanding this history illuminates why devtmpfs exists and how it fits into the broader device management ecosystem.
| Era | Approach | Mechanism | Limitations |
|---|---|---|---|
| 1970s-1990s | Static /dev | mknod commands, persistent on disk | Thousands of unused nodes, manual management |
| Early 2000s | devfs | Kernel creates nodes; first attempt at dynamic | Naming policy in kernel (bad design), removed in 2.6 |
| 2004-2009 | udev on tmpfs | udev creates nodes in response to kernel events | Race conditions at early boot; slow for many devices |
| 2009+ | devtmpfs + udev | Kernel creates nodes; udev adjusts permissions | Best of both: kernel speed + userspace flexibility |
The devfs mistake:
Linux 2.4 introduced devfs, which had the kernel create device nodes directly. However, this embedded naming policy into the kernel—a violation of Unix philosophy. Device names like disc0 instead of sda caused compatibility issues, and the code was unmaintainable.
The udev-only era:
dev 2.6 removed devfs and introduced udev—a userspace daemon that receives kernel device events and creates nodes accordingly. This was conceptually clean but had practical problems:
/dev (initramfs)The devtmpfs solution:
devtmpfs (introduced 2009, Linux 2.6.32) combines kernel efficiency with userspace flexibility:
devtmpfs demonstrates good system design: the kernel handles what it must (creating nodes when devices appear) while userspace handles policy (naming, permissions, symlinks). Neither duplicates the other's work.
Before diving into devtmpfs mechanics, we must understand what device nodes actually are—the fundamental interface between userspace and kernel device drivers.
A device node is a special file that provides a file-like interface to a device driver. When you read from or write to a device node, the kernel routes those operations to the appropriate driver. Device nodes are characterized by:
c) or block device (b)/dev)12345678910111213141516171819202122232425262728293031323334353637
# Viewing device nodes with their major:minor numbers$ ls -la /dev/sda /dev/tty0 /dev/null /dev/randombrw-rw---- 1 root disk 8, 0 Jan 16 10:00 /dev/sdacrw--w---- 1 root tty 4, 0 Jan 16 10:00 /dev/tty0 crw-rw-rw- 1 root root 1, 3 Jan 16 10:00 /dev/nullcrw-rw-rw- 1 root root 1, 8 Jan 16 10:00 /dev/random # Breaking down the output:# 'b' = block device, 'c' = character device# 8, 0 = major 8, minor 0 (SCSI disk driver, first disk)# 4, 0 = major 4, minor 0 (TTY driver, console 0)# 1, 3 = major 1, minor 3 (memory devices, null device)# 1, 8 = major 1, minor 8 (memory devices, random) # The kernel maintains a registry of major numbers$ cat /proc/devicesCharacter devices: 1 mem 4 /dev/vc/0 4 tty 5 /dev/tty ... Block devices: 8 sd 9 md 11 sr ... # Creating a device node manually (rarely needed today)$ sudo mknod /dev/test_device c 100 0$ ls -la /dev/test_devicecrw-r--r-- 1 root root 100, 0 Jan 16 10:00 /dev/test_device # What happens when you access a device?$ echo "hello" > /dev/null # Write routed to null driver$ cat /dev/random | head -c 16 | xxd # Read routed to random driverBlock vs Character devices:
Major/Minor number system:
Major numbers are assigned to drivers (often statically defined), while minor numbers distinguish instances. For example:
devtmpfs is a specialized tmpfs instance managed by the kernel. When kernel code calls device_add() to register a device, devtmpfs automatically creates the corresponding device node. When device_del() is called, the node is removed.
The devtmpfs lifecycle:
1234567891011121314151617181920212223242526272829303132333435363738394041424344
/* * Simplified view of devtmpfs device node creation * Actual code in drivers/base/devtmpfs.c */ /* Called when device_add() is invoked */int devtmpfs_create_node(struct device *dev){ const char *nodename; umode_t mode = 0; kuid_t uid = GLOBAL_ROOT_UID; kgid_t gid = GLOBAL_ROOT_GID; int err; /* Get the device name (e.g., "sda", "tty0") */ nodename = device_get_devnode(dev, &mode, &uid, &gid, &tmp); if (!nodename) return -ENOMEM; /* Determine the node type and create it */ if (S_ISBLK(mode)) err = vfs_mknod(/* path, mode, dev_t */); else err = vfs_create(/* path, mode */); /* Set ownership */ if (!err) err = vfs_chown(/* path, uid, gid */); return err;} /* Called when device_del() is invoked */int devtmpfs_delete_node(struct device *dev){ const char *nodename; nodename = device_get_devnode(dev, NULL, NULL, NULL, NULL); if (!nodename) return -ENOMEM; /* Simply unlink the file */ return vfs_unlink(/* path */);}Key implementation details:
kdevtmpfs) that handles node creation/deletiondevtmpfs is enabled via CONFIG_DEVTMPFS=y. The option CONFIG_DEVTMPFS_MOUNT=y makes the kernel mount devtmpfs at /dev automatically during boot, before init starts. This is essential for systems without initramfs.
devtmpfs and udev form a collaborative partnership. The kernel creates device nodes quickly; udev enhances them with proper permissions, ownership, and symlinks. Neither could work as well alone.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
# ═══════════════════════════════════════════════════════════════# See what devtmpfs creates vs what udev adds# ═══════════════════════════════════════════════════════════════ # Kernel-created nodes (devtmpfs)$ ls -la /dev/sdabrw-rw---- 1 root disk 8, 0 Jan 16 10:00 /dev/sda# Note: group 'disk' was set by udev, not kernel # udev-created symlinks$ ls -la /dev/disk/by-id/lrwxrwxrwx 1 root root 9 Jan 16 10:00 ata-Samsung_SSD_860_12345 -> ../../sdalrwxrwxrwx 1 root root 9 Jan 16 10:00 wwn-0x50000000001 -> ../../sda $ ls -la /dev/disk/by-uuid/lrwxrwxrwx 1 root root 10 Jan 16 10:00 a1b2c3d4-... -> ../../sda1 # ═══════════════════════════════════════════════════════════════# Example udev rule for a device# ═══════════════════════════════════════════════════════════════ # /etc/udev/rules.d/99-usb-serial.rules# Give specific USB serial device a consistent name and user accessSUBSYSTEM=="tty", ATTRS{idVendor}=="1234", ATTRS{idProduct}=="5678", \ SYMLINK+="arduino", MODE="0666", GROUP="dialout" # Result when this USB device is plugged in:# 1. Kernel creates /dev/ttyUSB0 (devtmpfs)# 2. udev receives uevent# 3. udev matches rule, applies:# - Creates symlink /dev/arduino -> ttyUSB0# - Sets mode 0666 (world writable)# - Sets group to dialout $ ls -la /dev/ttyUSB0 /dev/arduinocrw-rw-rw- 1 root dialout 188, 0 Jan 16 10:00 /dev/ttyUSB0lrwxrwxrwx 1 root root 7 Jan 16 10:00 /dev/arduino -> ttyUSB0 # ═══════════════════════════════════════════════════════════════# Timing: devtmpfs is immediate, udev takes time# ═══════════════════════════════════════════════════════════════ # Watch device creation in real-time$ udevadm monitor --property # Plug in USB device...KERNEL[123.456] add /devices/pci.../usb1/1-1 (usb)# [immediate] /dev/bus/usb/001/002 exists (devtmpfs) UDEV[123.567] add /devices/pci.../usb1/1-1 (usb) # [111ms later] symlinks created, permissions set # The ~100ms gap is when udev rules are processeddevtmpfs eliminated the classic race condition where a script tried to access /dev/sda before udev created it. Now the kernel guarantees the node exists immediately. udev might not have set permissions yet, but the node is there—applications can wait with inotify if needed.
One of devtmpfs's primary motivations was solving the early boot chicken-and-egg problem: you need devices to load init, but udev isn't running yet to create device nodes.
Boot scenarios:
Boot without initramfs (CONFIG_DEVTMPFS_MOUNT=y):
/dev automatically/devroot= parameter)/sbin/initThis works for simple configurations where disk drivers are built into the kernel. No initramfs complexity needed.
1234567891011121314151617181920
# Check if devtmpfs is mounted$ mount | grep devtmpfsdevtmpfs on /dev type devtmpfs (rw,nosuid,size=16345678k,nr_inodes=4086419,mode=755) # Check kernel configuration$ zcat /proc/config.gz | grep DEVTMPFSCONFIG_DEVTMPFS=yCONFIG_DEVTMPFS_MOUNT=y # See when devices were created (approximate, via dmesg)$ dmesg | grep -E "(sd|nvme)" | head -5[ 2.123456] sd 0:0:0:0: [sda] 1953525168 512-byte logical blocks[ 2.123789] sd 0:0:0:0: [sda] Attached SCSI disk # At this point, /dev/sda existed (before init started) # Check boot timing for udev$ systemd-analyze blame | grep udev 432ms systemd-udev-trigger.service 156ms systemd-udevd.serviceA modern Linux /dev directory contains both kernel-created nodes and udev-created symlinks/structure. Understanding this layout is essential for system administration and troubleshooting.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
# ═══════════════════════════════════════════════════════════════# Core device nodes (kernel-provided names)# ═══════════════════════════════════════════════════════════════ $ ls -la /dev/sd* # SCSI/SATA disksbrw-rw---- 1 root disk 8, 0 Jan 16 10:00 /dev/sdabrw-rw---- 1 root disk 8, 1 Jan 16 10:00 /dev/sda1brw-rw---- 1 root disk 8, 2 Jan 16 10:00 /dev/sda2 $ ls -la /dev/nvme* # NVMe SSDsbrw-rw---- 1 root disk 259, 0 Jan 16 10:00 /dev/nvme0n1brw-rw---- 1 root disk 259, 1 Jan 16 10:00 /dev/nvme0n1p1 $ ls -la /dev/tty* # Terminalscrw--w---- 1 root tty 4, 0 Jan 16 10:00 /dev/tty0crw--w---- 1 root tty 4, 1 Jan 16 10:00 /dev/tty1 # ═══════════════════════════════════════════════════════════════# Pseudo-device nodes (always present, memory-based)# ═══════════════════════════════════════════════════════════════ $ ls -la /dev/null /dev/zero /dev/random /dev/urandomcrw-rw-rw- 1 root root 1, 3 Jan 16 10:00 /dev/nullcrw-rw-rw- 1 root root 1, 5 Jan 16 10:00 /dev/zerocrw-rw-rw- 1 root root 1, 8 Jan 16 10:00 /dev/randomcrw-rw-rw- 1 root root 1, 9 Jan 16 10:00 /dev/urandom # ═══════════════════════════════════════════════════════════════# udev-created symlink directories (persistent names)# ═══════════════════════════════════════════════════════════════ $ ls /dev/disk/by-id by-label by-partuuid by-path by-uuid $ ls -la /dev/disk/by-id/lrwxrwxrwx 1 root root 9 ... ata-Samsung_SSD_860_EVO_S1234567 -> ../../sdalrwxrwxrwx 1 root root 10 ... ata-Samsung_SSD_860_EVO_S1234567-part1 -> ../../sda1lrwxrwxrwx 1 root root 9 ... nvme-Samsung_SSD_970_EVO_XXXX -> ../../nvme0n1lrwxrwxrwx 1 root root 9 ... wwn-0x5000000000000001 -> ../../sda $ ls -la /dev/disk/by-uuid/lrwxrwxrwx 1 root root 10 ... a1b2c3d4-e5f6-7890-abcd-ef1234567890 -> ../../sda1 # These symlinks remain consistent even if device order changes! # ═══════════════════════════════════════════════════════════════# Device subdirectories# ═══════════════════════════════════════════════════════════════ $ ls /dev/input/ # Input devices (mice, keyboards)by-id by-path event0 event1 event2 mice mouse0 $ ls /dev/dri/ # Graphics (DRM)by-path card0 renderD128 $ ls /dev/snd/ # Sound (ALSA)by-id by-path controlC0 hwC0D0 pcmC0D0c pcmC0D0p timer $ ls /dev/bus/usb/ # USB device nodes (for libusb)001 002 003 004| Path Pattern | Type | Description | Created By |
|---|---|---|---|
/dev/sd[a-z]* | Block | SCSI/SATA/USB disks | Kernel (devtmpfs) |
/dev/nvme* | Block | NVMe SSDs | Kernel (devtmpfs) |
/dev/tty* | Char | Terminals and consoles | Kernel (devtmpfs) |
/dev/pts/* | Char | Pseudo-terminals (SSH, terminals) | devpts filesystem |
/dev/null, /dev/zero | Char | Null sink, zero source | Kernel (always) |
/dev/random, /dev/urandom | Char | Random number generators | Kernel (always) |
/dev/disk/by-* | Symlinks | Persistent disk identifiers | udev rules |
/dev/input/event* | Char | Input event devices | Kernel (devtmpfs) |
/dev/dri/* | Char | GPU rendering nodes | Kernel (devtmpfs) |
/dev/shm | Directory | POSIX shared memory mount point | System config |
Containers don't use devtmpfs directly—instead, they receive a minimal, curated /dev directory. This isolation is crucial for security and resource management.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
# ═══════════════════════════════════════════════════════════════# Docker container's default /dev# ═══════════════════════════════════════════════════════════════ $ docker run --rm alpine ls -la /devtotal 4drwxr-xr-x 5 root root 360 Jan 16 10:00 .drwxr-xr-x 1 root root 6 Jan 16 10:00 ..crw--w---- 1 root tty 136, 0 Jan 16 10:00 consolelrwxrwxrwx 1 root root 11 Jan 16 10:00 core -> /proc/kcorelrwxrwxrwx 1 root root 13 Jan 16 10:00 fd -> /proc/self/fdcrw-rw-rw- 1 root root 1, 7 Jan 16 10:00 fulldrwxrwxrwt 2 root root 40 Jan 16 10:00 mqueuecrw-rw-rw- 1 root root 1, 3 Jan 16 10:00 nulllrwxrwxrwx 1 root root 8 Jan 16 10:00 ptmx -> pts/ptmxdrwxr-xr-x 2 root root 0 Jan 16 10:00 ptscrw-rw-rw- 1 root root 1, 8 Jan 16 10:00 randomdrwxrwxrwt 2 root root 40 Jan 16 10:00 shmlrwxrwxrwx 1 root root 15 Jan 16 10:00 stderr -> /proc/self/fd/2lrwxrwxrwx 1 root root 15 Jan 16 10:00 stdin -> /proc/self/fd/0lrwxrwxrwx 1 root root 15 Jan 16 10:00 stdout -> /proc/self/fd/1crw-rw-rw- 1 root root 5, 0 Jan 16 10:00 ttycrw-rw-rw- 1 root root 1, 9 Jan 16 10:00 urandomcrw-rw-rw- 1 root root 1, 5 Jan 16 10:00 zero # Note: No sda, tty0, etc. - only essential pseudo-devices # ═══════════════════════════════════════════════════════════════# Granting access to host devices# ═══════════════════════════════════════════════════════════════ # Add a single device$ docker run --device=/dev/video0 myapp # Add GPU access (NVIDIA)$ docker run --gpus all nvidia/cuda nvidia-smi # Add all devices (dangerous!)$ docker run --privileged ubuntu ls /dev# Shows entire host /dev - rarely appropriate # ═══════════════════════════════════════════════════════════════# How container runtimes create /dev# ═══════════════════════════════════════════════════════════════ # 1. Create tmpfs at container's /devmount -t tmpfs -o size=65536k,mode=755 tmpfs /container_root/dev # 2. Create essential device nodesmknod /container_root/dev/null c 1 3mknod /container_root/dev/zero c 1 5mknod /container_root/dev/full c 1 7mknod /container_root/dev/random c 1 8mknod /container_root/dev/urandom c 1 9mknod /container_root/dev/tty c 5 0... # 3. Create symbolic linksln -s /proc/self/fd /container_root/dev/fdln -s /proc/self/fd/0 /container_root/dev/stdin... # 4. Optionally bind-mount specific host devicesmount --bind /dev/nvidia0 /container_root/dev/nvidia0The minimal container /dev is a security boundary. Access to /dev/sda would allow reading the host's disk. Access to /dev/kmem would allow reading kernel memory. The curated /dev ensures containers can only interact with safe, isolated pseudo-devices unless explicitly granted access.
devtmpfs is particularly valuable for embedded systems and minimal Linux installations where running full udev is impractical or unnecessary.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
# ═══════════════════════════════════════════════════════════════# Minimal initramfs with devtmpfs only (no udev)# ═══════════════════════════════════════════════════════════════ # Kernel command lineroot=/dev/mmcblk0p2 rootfstype=ext4 rootwait # init script (minimal)#!/bin/sh# devtmpfs is auto-mounted by kernel (CONFIG_DEVTMPFS_MOUNT=y)# or mount manually:mount -t devtmpfs devtmpfs /dev # Mount other filesystemsmount -t proc proc /procmount -t sysfs sysfs /sys # Root device is now available at /dev/mmcblk0p2# Mount and switch to real rootmount /dev/mmcblk0p2 /mnt/rootexec switch_root /mnt/root /sbin/init # ═══════════════════════════════════════════════════════════════# Busybox mdev configuration# ═══════════════════════════════════════════════════════════════ # /etc/mdev.conf - simple rules# Format: <device regex> <uid>:<gid> <permissions> [=|>|!] [<path>] [@|$|*] <command> # Set permissions on specific devicesnull root:root 0666zero root:root 0666random root:root 0666urandom root:root 0666console root:tty 0600tty root:tty 0666tty[0-9]* root:tty 0620 # Block devicessd[a-z].* root:disk 0660 # USB serial adaptersttyUSB[0-9]* root:dialout 0660 # Create symlink for first USB serialttyUSB0 root:dialout 0660 =ttyUSB0 @ln -sf ttyUSB0 /dev/gps # ═══════════════════════════════════════════════════════════════# Enable mdev as hotplug handler# ═══════════════════════════════════════════════════════════════ # Tell kernel to call mdev for hotplug eventsecho /sbin/mdev > /proc/sys/kernel/hotplug # Or use netlink-based mdevd for async operationmdevd &For embedded devices with fixed hardware configurations, devtmpfs alone often suffices. The kernel creates all needed nodes. If you only need consistent permissions (e.g., /dev/ttyS0 as 0666), you can either chmod in init scripts or use minimal mdev rules.
devtmpfs revolutionized Linux device management by moving device node creation into the kernel while preserving userspace policy control.
What's next:
We've explored kernel-space virtual file systems that expose system information (/proc, /sys) and provide dynamic storage (tmpfs, devtmpfs). Our final page examines FUSE (Filesystem in Userspace)—the framework that lets ordinary programs become file system drivers, enabling everything from network file systems to archive mounting to custom virtual file systems.
You now understand how devtmpfs works with udev to provide dynamic device management, the device node system (major/minor numbers), boot process integration, and its role in containers and embedded systems. This knowledge is fundamental to Linux system administration and device driver development.