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
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.
| 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 afterrmmod. - 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_operationsstruct on a kernel 5.6 or newer, which will fail to compile againstproc_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_filehelpers 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.

2 Comments