procfs Linux Kernel Tutorial: Reading and Writing /proc Entries the Modern Way-Linux Device Driver Training

procfs Linux Kernel Tutorial: Reading and Writing /proc Entries the Modern Way

Free Linux Kernel Development Course • Free Linux Device Drivers Course

What You Will Learn

  • What the proc filesystem actually is and why its files show zero size
  • How to create a custom /proc entry in a modern kernel using proc_ops
  • How to read data from and write data into a /proc file safely
  • Why procfs is discouraged for new driver interfaces today
Prerequisites: basic C programming, a working Linux kernel module build environment, and familiarity with loading/unloading kernel modules with insmod and rmmod.

What Is procfs?

The proc filesystem, mounted at /proc, is a virtual filesystem. Nothing under it lives on a disk; every entry is generated on demand by kernel code and kept in RAM. That is why ls -l /proc almost always shows a size of zero next to each file: there is no fixed-size backing store, the content is produced the moment you read it. Numbered directories such as /proc/1234 represent live processes, while files like /proc/cpuinfo or /proc/meminfo expose system-wide state.

Historically, driver authors used procfs to expose custom status and configuration entries for their own devices. That practice is now discouraged by the upstream kernel community; procfs is meant to stay a kernel-internal reporting mechanism, and sysfs (covered in the next lecture) is the accepted place for driver attributes. Even so, understanding procfs is useful, both because a huge amount of existing tooling reads from it, and because interview questions still ask about it.

How a /proc Read Reaches Your Module
cat /proc/mydriver → VFS layer → proc_ops.proc_read → seq_printf output

proc_ops Replaced file_operations

Older tutorials wire a custom /proc entry to a plain file_operations structure. From Linux kernel version 5.6 onward, procfs entries in the mainline tree use a dedicated struct proc_ops instead. It is smaller and avoids VFS fields the proc code never needed, which slightly improves memory footprint and cache behaviour. If you are following a tutorial that still shows file_operations for /proc, treat that as outdated for any kernel newer than 5.6.

static struct proc_ops my_proc_fops = {
    .proc_open    = my_proc_open,
    .proc_read    = seq_read,
    .proc_write   = my_proc_write,
    .proc_lseek   = seq_lseek,
    .proc_release = single_release,
};

Creating a Simple /proc Entry

A minimal read-only entry that prints a counter value looks like this. The key call is proc_create(), which registers the entry under /proc and hands it a proc_ops table.

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>

static int counter = 0;

static int my_show(struct seq_file *m, void *v)
{
    seq_printf(m, "counter value = %d\n", counter);
    return 0;
}

static int my_open(struct inode *inode, struct file *file)
{
    return single_open(file, my_show, NULL);
}

static ssize_t my_write(struct file *file, const char __user *ubuf,
                         size_t len, loff_t *offset)
{
    char kbuf[16];

    if (len >= sizeof(kbuf))
        return -EINVAL;
    if (copy_from_user(kbuf, ubuf, len))
        return -EFAULT;
    kbuf[len] = '\0';

    if (kstrtoint(kbuf, 10, &counter))
        return -EINVAL;

    return len;
}

static const struct proc_ops my_proc_fops = {
    .proc_open    = my_open,
    .proc_read    = seq_read,
    .proc_write   = my_write,
    .proc_lseek   = seq_lseek,
    .proc_release = single_release,
};

static int __init my_init(void)
{
    proc_create("mycounter", 0644, NULL, &my_proc_fops);
    return 0;
}

static void __exit my_exit(void)
{
    remove_proc_entry("mycounter", NULL);
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

Once this module is loaded, running cat /proc/mycounter triggers my_show() through seq_read, and running echo 5 > /proc/mycounter triggers my_write(). Note that copy_from_user() is mandatory whenever data crosses from user space into the kernel; you can never dereference a user pointer directly.

Common Mistakes

  • Forgetting to call remove_proc_entry() in the module exit path, leaving a dangling entry after rmmod.
  • Reading or writing a user buffer without copy_to_user() / copy_from_user(), which crashes on architectures that separate address spaces strictly.
  • Not null-terminating the kernel buffer before passing it to kstrtoint() or similar parsing helpers.
  • Using the old file_operations struct on a kernel 5.6 or newer, which will fail to compile against proc_create().

Best Practices

  • Prefer sysfs for anything that is a genuine device attribute; reserve procfs for process/system-wide reporting.
  • Keep permissions tight; do not make writable proc entries world-writable unless there is a real reason.
  • Use seq_file helpers instead of hand-rolling buffer offsets for multi-line output.

FAQ

Q1: Why does /proc show file sizes as zero?

Because the content is generated on the fly by kernel code; there is no fixed backing store on disk.

Q2: What changed for /proc in kernel 5.6?

struct proc_ops replaced struct file_operations for registering proc entry callbacks.

Q3: Can I use procfs for a brand-new driver in 2026?

It will still work, but sysfs or debugfs is the accepted approach for new driver-specific interfaces.

Q4: What is seq_file and why is it used here?

It is a kernel helper that manages iteration and buffering for multi-line proc output, so you avoid handling read offsets manually.

Q5: Is copy_from_user always required?

Yes, any time your write handler touches a pointer supplied by user space, because kernel and user address spaces are not directly compatible.

procfs tutorial proc_ops linux kernel free linux kernel development course free linux device drivers course

2 Comments

Leave a Reply

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