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_opsreplacedstruct file_operationsfor 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:
| /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
catloops (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
debugfsover 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, notstruct file_operations, for procfs callbacks. proc_mkdir()andproc_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.
Part 2 covers procfs read/write callbacks, live testing, and input validation.

2 Comments