Device Files
Beginner–Intermediate
stat(), mknod()
Key Terms:
What is a Device File?
In Linux, everything is a file — including hardware devices. A device file is a special file in the filesystem that represents a hardware device (like a hard disk, keyboard, or mouse). When a program reads or writes to a device file, the kernel routes the operation to the correct device driver.
This design allows programs to use the same system calls (open(), read(), write()) on devices as on regular files. You don’t need to write special code per device — the driver handles it.
Your Program
open(“/dev/sda”)
VFS Layer
Unified Interface
Device Driver
Kernel Code
Hardware
Physical Disk
Linux has two types of device files:
Character Devices
Handle data one byte at a time in sequence. No seeking allowed.
- Terminals (
/dev/tty) - Keyboard (
/dev/input/event0) - Serial ports (
/dev/ttyS0) - Null device (
/dev/null)
Block Devices
Handle data in fixed-size blocks (typically 512 bytes). Random access supported.
- Hard disks (
/dev/sda) - Partitions (
/dev/sda1) - USB drives (
/dev/sdb) - CD-ROM (
/dev/cdrom)
Every device file has two numbers:
Identifies the type of device. The kernel uses this to find the right device driver. Example: all SCSI disks share the same major ID.
Identifies the specific device within that type. Example: /dev/sda (minor=0), /dev/sda1 (minor=1), /dev/sda2 (minor=2).
# Run this command to see major/minor IDs:
$ ls -l /dev/sda*
brw-rw---- 1 root disk 8, 0 Jan 1 12:00 /dev/sda
brw-rw---- 1 root disk 8, 1 Jan 1 12:00 /dev/sda1
brw-rw---- 1 root disk 8, 2 Jan 1 12:00 /dev/sda2
# ^ ^
# | Minor ID (specific partition)
# Major ID (8 = SCSI disk driver)
The stat() system call fills a struct stat with file metadata. We can use S_ISCHR() and S_ISBLK() macros to identify device types.
#include <stdio.h>
#include <sys/stat.h>
#include <sys/sysmacros.h> /* For major(), minor() macros */
int main(int argc, char *argv[]) {
struct stat sb;
if (argc != 2) {
fprintf(stderr, "Usage: %s <path>\n", argv[0]);
return 1;
}
/* stat() fills sb with file info */
if (stat(argv[1], &sb) == -1) {
perror("stat");
return 1;
}
/* Check device type using mode bits */
if (S_ISCHR(sb.st_mode)) {
printf("%s is a CHARACTER device\n", argv[1]);
} else if (S_ISBLK(sb.st_mode)) {
printf("%s is a BLOCK device\n", argv[1]);
} else if (S_ISREG(sb.st_mode)) {
printf("%s is a REGULAR file\n", argv[1]);
} else if (S_ISDIR(sb.st_mode)) {
printf("%s is a DIRECTORY\n", argv[1]);
} else {
printf("%s is some other file type\n", argv[1]);
}
/* For device files, print major and minor IDs */
if (S_ISCHR(sb.st_mode) || S_ISBLK(sb.st_mode)) {
printf(" Major ID: %lu\n", (unsigned long) major(sb.st_rdev));
printf(" Minor ID: %lu\n", (unsigned long) minor(sb.st_rdev));
}
return 0;
}
/* Compile: gcc -o check_dev check_dev.c
Run:
./check_dev /dev/null -> CHARACTER device
./check_dev /dev/sda -> BLOCK device
./check_dev /etc/passwd -> REGULAR file
*/
Use opendir() + readdir() + lstat() to scan /dev and categorise each entry.
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <string.h>
#define DEV_DIR "/dev"
int main(void) {
DIR *dp;
struct dirent *entry;
struct stat sb;
char path[512];
dp = opendir(DEV_DIR);
if (dp == NULL) {
perror("opendir");
return 1;
}
printf("%-25s %-12s %5s %5s\n",
"Name", "Type", "Major", "Minor");
printf("%-25s %-12s %5s %5s\n",
"----", "----", "-----", "-----");
while ((entry = readdir(dp)) != NULL) {
/* Build full path */
snprintf(path, sizeof(path), "%s/%s",
DEV_DIR, entry->d_name);
/* lstat() does NOT follow symlinks */
if (lstat(path, &sb) == -1)
continue;
if (S_ISCHR(sb.st_mode)) {
printf("%-25s %-12s %5lu %5lu\n",
entry->d_name, "char",
(unsigned long)major(sb.st_rdev),
(unsigned long)minor(sb.st_rdev));
} else if (S_ISBLK(sb.st_mode)) {
printf("%-25s %-12s %5lu %5lu\n",
entry->d_name, "block",
(unsigned long)major(sb.st_rdev),
(unsigned long)minor(sb.st_rdev));
}
}
closedir(dp);
return 0;
}
/* Sample output:
Name Type Major Minor
---- ---- ----- -----
null char 1 3
zero char 1 5
sda block 8 0
sda1 block 8 1
tty char 5 0
*/
mknod() creates device special files. Requires root (CAP_MKNOD). Normally used by system tools, not application code.
#include <stdio.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
int main(void) {
dev_t dev;
int ret;
/* Create a character device with major=1, minor=3 (like /dev/null) */
/* makedev() combines major and minor into a dev_t */
dev = makedev(1, 3);
/* mknod(path, mode|type, dev) */
/* S_IFCHR = character device, 0666 = permissions */
ret = mknod("/tmp/my_null_dev", S_IFCHR | 0666, dev);
if (ret == -1) {
perror("mknod"); /* Need to run as root! */
return 1;
}
printf("Device file created at /tmp/my_null_dev\n");
printf("It behaves just like /dev/null\n");
/* Test it: echo hello > /tmp/my_null_dev (discards data) */
return 0;
}
/* For block device instead:
mknod("/tmp/my_blk", S_IFBLK | 0660, makedev(8, 0));
*/
Device files work with standard open()/read()/write() — same API as regular files.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd;
unsigned char buf[8];
int i;
/* Open the random number generator device */
fd = open("/dev/urandom", O_RDONLY);
if (fd == -1) {
perror("open /dev/urandom");
return 1;
}
/* Read 8 random bytes — same API as any file! */
if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
perror("read");
close(fd);
return 1;
}
printf("8 random bytes: ");
for (i = 0; i < 8; i++)
printf("%02x ", buf[i]);
printf("\n");
close(fd);
return 0;
}
/* /dev/urandom never blocks and gives random bytes.
/dev/random blocks if entropy pool is empty.
Both are character devices (major=1, minor=8 and 9). */
Older Linux systems had /dev filled with thousands of device files, even for hardware not present. udev (Linux 2.6+) solves this by creating device files only for hardware that is actually connected.
❌ Old way (static /dev)
- Thousands of unused entries
- Slow to scan
- No hot-plug awareness
✅ udev (dynamic /dev)
- Only real hardware has entries
- Fast to scan
- Hot-plug: plug USB → /dev/sdb appears
udev reads hardware info from /sys (the sysfs filesystem), which the kernel populates automatically with info about connected devices.
# Check udev rules directory:
ls /etc/udev/rules.d/
# Watch udev events in real-time (plug/unplug a USB drive):
udevadm monitor
# Get info about a specific device:
udevadm info --query=all --name=/dev/sda
# sysfs exposes kernel device data under /sys:
ls /sys/block/sda/
cat /sys/block/sda/size # Size in 512-byte blocks
cat /sys/block/sda/removable # 0 = fixed, 1 = removable
🎯 Interview Questions — Device Files
Q1. What is a device file in Linux? Why does it exist?
A device file is a special filesystem entry that represents a hardware device. It exists to uphold the UNIX philosophy of “everything is a file” — programs use the same open/read/write system calls on devices as on regular files. The kernel routes these calls to the appropriate device driver.
Q2. What is the difference between a character device and a block device?
A character device transfers data one byte at a time sequentially (e.g., keyboard, serial port). A block device transfers data in fixed-size blocks and supports random access (e.g., hard disk, USB drive). In ls -l output, character devices show ‘c’ and block devices show ‘b’.
Q3. What are major and minor device IDs?
The major ID identifies the device type/driver (e.g., 8 = SCSI disk). The kernel uses it to find the right driver. The minor ID identifies the specific device instance within that driver (e.g., sda=0, sda1=1, sda2=2). Both are stored in the device file’s i-node.
Q4. Which system call creates a device file? What privilege does it need?
mknod() creates device special files. It requires CAP_MKNOD capability (root privilege). The shell command mknod(8) does the same thing.
Q5. What is udev and how does it improve on static /dev?
udev is a userspace daemon that dynamically creates/removes device files in /dev as hardware is connected/disconnected. Old Linux systems pre-populated /dev with entries for all possible devices, wasting space and slowing directory scans. udev reads hardware info from the sysfs filesystem (/sys) and only creates files for hardware that actually exists.
Q6. What macros do you use to check device type from struct stat?
S_ISCHR(sb.st_mode) returns true for character devices. S_ISBLK(sb.st_mode) returns true for block devices. S_ISREG() for regular files, S_ISDIR() for directories. The major() and minor() macros extract the IDs from sb.st_rdev.
Q7. What is the difference between /dev/null, /dev/zero, and /dev/urandom?
/dev/null discards all writes; reads return EOF. /dev/zero returns infinite null bytes on read. /dev/urandom returns random bytes and never blocks. All three are character devices and show this UNIX design pattern: hardware-like interfaces for software abstractions.
