Linux Device Files: Complete Guide to Device Management

 

🖥️Linux Device Files: Complete Guide to Device Management

Understand how Linux treats hardware as files — a core UNIX philosophy
Topic 1 of 9
Device Files
Level
Beginner–Intermediate
Key Calls
stat(), mknod()

Key Terms:

Device File Character Device Block Device Major ID Minor ID /dev udev sysfs mknod() Device Driver

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.

Simple analogy: Think of a device file like a power socket. Every socket looks the same (same interface), but the wiring behind it (the driver) is different depending on location and purpose.

📊 How Device Files Work — Visual Flow
📝

Your Program
open(“/dev/sda”)

🔲

VFS Layer
Unified Interface

⚙️

Device Driver
Kernel Code

💾

Hardware
Physical Disk

⚡ Character vs Block Devices

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)
ls -l shows: c rwxrwxrwx
💿

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)
ls -l shows: b rwxrwxrwx

🔢 Major and Minor Device IDs

Every device file has two numbers:

Major ID
Identifies the type of device. The kernel uses this to find the right device driver. Example: all SCSI disks share the same major ID.
Minor ID
Identifies the specific device within that type. Example: /dev/sda (minor=0), /dev/sda1 (minor=1), /dev/sda2 (minor=2).
📌 On Linux 2.6+, major IDs use 12 bits and minor IDs use 20 bits — supporting far more devices than older 8-bit systems.
# 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)

💻 Code Example 1: Check Device Type Using stat()

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
*/

💻 Code Example 2: List All Device Files in /dev

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
*/

💻 Code Example 3: Create a Device File with mknod()

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));
*/

💻 Code Example 4: Reading from a Character Device (/dev/urandom)

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). */

🔧 udev: Dynamic Device Management

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.

Leave a Reply

Your email address will not be published. Required fields are marked *