How to Create a procfs File in a Linux Kernel Module (2026 Guide)-Free Linux Device Drivers Course

How to Create a procfs File in a Linux Kernel Module (2026 Guide)
Free Linux Kernel Programming Course — Module Interfacing Series, Part 1

If you are searching for a way to create a procfs file in a kernel module, you have landed on the right lecture. The /proc filesystem is one of the oldest and most widely used mechanisms for letting a Linux kernel module talk to user space without writing a single line of ioctl code. In this free Linux kernel development course lecture, we build a working procfs interface from scratch, on a current mainline kernel, using the modern struct proc_ops API.

By the end of this lecture you will have a kernel module that creates its own directory under /proc, exposes a pseudo-file that user space can read from and write to, and understands exactly what happens internally every time someone runs cat or echo against that file.

What You Will Learn

  • What procfs is and why kernel modules use it for a simple user-kernel interface
  • How to create a procfs directory with proc_mkdir()
  • How to create a procfs file with proc_create() on a current kernel
  • Why struct proc_ops replaced struct file_operations for procfs callbacks
  • How default ownership and permission bits work for procfs entries
  • How to clean up procfs entries safely in your module’s exit path

Prerequisites

This lecture assumes you can already build and load a basic loadable kernel module (LKM) with insmod and rmmod, and that you are comfortable reading kernel log output with dmesg. If you have not covered that yet, go through the earlier lectures in this free embedded systems course before continuing here.

What Is procfs and Why Use It in a Kernel Module?

procfs is a virtual, memory-only filesystem that is conventionally mounted at /proc. Nothing under /proc lives on disk; every file and directory you see there is generated on demand by kernel code the moment a process opens, reads, or writes it. Originally it existed to expose process information (hence the name), but the kernel long ago generalised it into a lightweight, general-purpose channel for driver and subsystem authors to expose runtime state and simple controls to user space.

Compared to writing a full character device with an ioctl interface, a procfs entry is far less code, and it plays nicely with ordinary shell tools such as cat and echo, which makes it an excellent teaching tool and a genuinely useful debugging aid in production drivers.

The procfs Entries We Will Build

Across this two-part lecture, our sample module called procdemo will create one directory and two pseudo-files underneath it. Here is the layout we are aiming for:

procdemo procfs Layout
/proc/procdemo/  (directory, created with proc_mkdir)
procdemo_loglevel  —  permissions 0644  —  read current log level, write a new log level
procdemo_devstate  —  permissions 0440  —  read-only snapshot of the driver’s internal state

Every entry you create under /proc is, by default, owned by root:root. The permission bits you pass into proc_create() control who can read or write the pseudo-file, exactly like permissions on a regular file, but they have no bearing on disk storage since nothing is stored — the numbers are read back from kernel memory (or a small buffer) every single time.

procfs Entry Read Behaviour Write Behaviour Permissions
procdemo_loglevel Returns the current in-memory log_level value Validates and updates log_level 0644
procdemo_devstate Prints a snapshot of an internal struct device_state No write callback registered 0440

Important: struct proc_ops, Not struct file_operations

A lot of older tutorials and books still show proc_create() being handed a struct file_operations pointer. That was correct on kernels before 5.6, but current mainline kernels expect a struct proc_ops instead. The field names are different, and mixing the two up is one of the most common build errors people hit when following outdated material on a current kernel.

Old field (file_operations, pre-5.6) Current field (proc_ops, 5.6+)
.owner not required — proc_ops tracks module lifetime automatically
.read .proc_read
.write .proc_write
.open .proc_open
.llseek .proc_lseek

Tip: If you copy-paste a procfs example from a book or blog dated before 2020 and it refuses to compile with errors about unknown field names, this table is almost always the reason. Rewrite the struct as proc_ops and the field names accordingly.

Step 1: Declaring the proc_ops Structure

Here is the original example we will use throughout this two-part lecture. It defines the callback table for our procdemo_loglevel file:

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

#define PROCDEMO_DIR      "procdemo"
#define LOGLEVEL_FILE     "procdemo_loglevel"
#define LOGLEVEL_PERMS    0644

static struct proc_dir_entry *procdemo_dir;
static int log_level;   /* valid range: 0 (none) to 3 (verbose) */

static ssize_t loglevel_read(struct file *filp, char __user *ubuf,
                              size_t count, loff_t *offp);
static ssize_t loglevel_write(struct file *filp, const char __user *ubuf,
                               size_t count, loff_t *offp);

static const struct proc_ops loglevel_fops = {
    .proc_read  = loglevel_read,
    .proc_write = loglevel_write,
};

Notice there is no .owner field here — with proc_ops the kernel takes care of module reference counting for you, which removes a class of bugs that older file_operations-based procfs code had to handle manually.

Step 2: Creating the Directory and the File in module_init

Inside your module’s init function, create the parent directory first, then create the file inside it:

static int __init procdemo_init(void)
{
    procdemo_dir = proc_mkdir(PROCDEMO_DIR, NULL);
    if (!procdemo_dir) {
        pr_err("procdemo: failed to create /proc/%s\n", PROCDEMO_DIR);
        return -ENOMEM;
    }

    if (!proc_create(LOGLEVEL_FILE, LOGLEVEL_PERMS, procdemo_dir,
                      &loglevel_fops)) {
        pr_err("procdemo: failed to create %s\n", LOGLEVEL_FILE);
        remove_proc_entry(PROCDEMO_DIR, NULL);
        return -ENOMEM;
    }

    pr_info("procdemo: loaded, /proc/%s/%s is ready\n",
            PROCDEMO_DIR, LOGLEVEL_FILE);
    return 0;
}
module_init(procdemo_init);

Two things worth calling out here. First, proc_mkdir() returns NULL on failure rather than an error pointer, so a simple truthiness check is enough. Second, if the file creation fails after the directory already exists, we clean up the directory before returning, otherwise a failed insmod would leave a stray, non-functional directory sitting under /proc.

Step 3: Cleaning Up in module_exit

procfs entries do not disappear on their own. If you forget to remove them, unloading your module will leave dangling entries that panic the kernel the moment something touches them. Always remove what you created, in the reverse order:

static void __exit procdemo_exit(void)
{
    remove_proc_subtree(PROCDEMO_DIR, NULL);
    pr_info("procdemo: unloaded, /proc/%s removed\n", PROCDEMO_DIR);
}
module_exit(procdemo_exit);

remove_proc_subtree() is the safer choice once you have more than one file under a directory, because it removes the directory and everything inside it in a single call, rather than requiring you to remember every individual remove_proc_entry() call.

Common Mistakes When Creating procfs Files

  • Using file_operations on a current kernel — leads to compiler errors about unknown struct fields; use proc_ops instead.
  • Forgetting to check the return value of proc_mkdir() / proc_create() — both can legitimately fail under memory pressure.
  • Not removing the proc directory on module exit — causes stale entries and can crash processes that later access them.
  • Assuming procfs files behave exactly like regular files — there is no real file size, and read callbacks must handle the offset argument correctly to avoid infinite cat loops (covered in Part 2).

Best Practices

  • Keep your procfs interface minimal — it is meant for debugging and simple control, not a general-purpose data channel.
  • Prefer debugfs over procfs for information that is purely for kernel developers, and reserve procfs for things end users or system administrators genuinely need.
  • Always validate input in your write callback; never trust the bytes coming from user space.
  • Set permission bits deliberately — a writable procfs file that changes driver behaviour should not be world-writable in a production system.

Security Considerations

Because procfs entries are reachable by any local process that has the right permissions, treat every write callback as untrusted input, exactly as you would a network-facing interface. Restrictive permission bits (like the 0440 we used for procdemo_devstate) are your first line of defence, and strict bounds checking in the write handler is your second.

Key Takeaways

  • procfs gives a kernel module a lightweight way to expose state and controls to user space without a full character device.
  • Current kernels use struct proc_ops, not struct file_operations, for procfs callbacks.
  • proc_mkdir() and proc_create() create the directory and files; remove_proc_subtree() cleans them up safely.
  • Permission bits on a procfs file work like ordinary Unix permissions and should be chosen deliberately.

Conclusion

You now have a module that creates a procfs directory and a pseudo-file with the modern proc_ops API, and you understand why this differs from what older books show. In Part 2 of this free Linux kernel development course lecture, we will implement the actual loglevel_read() and loglevel_write() callbacks, test the interface live with cat and echo, and add input validation so an out-of-range value is safely rejected.

Continue the Free Linux Kernel Programming Course

Part 2 covers procfs read/write callbacks, live testing, and input validation.

2 Comments

Leave a Reply

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