In this lecture of our free Linux kernel programming course you will learn how procfs read write works inside a real kernel module. By the end of this tutorial you will be able to:
- Understand what procfs is and why kernel modules use it to talk to user space
- Create a proc entry with the modern
struct proc_opsinterface (Linux 5.6 and above) - Implement a procfs read callback using the seq_file API
- Implement a procfs write callback that safely copies data from user space
- Test your proc file read write interface from the shell using cat and echo
- Avoid common mistakes that break procfs read write behaviour on newer kernels
Before starting this procfs read write tutorial, you should already be comfortable with:
- Writing and loading a basic loadable kernel module (LKM)
- Compiling kernel modules with a Makefile and Kbuild
- Basic C pointers and the concept of user space versus kernel space
If you are new to kernel modules, check out our free Linux kernel programming course and free Linux device drivers course lectures first, then come back here.
What Is procfs and Why Do Kernel Modules Use It?
procfs (short for the proc file system) is a special, memory-only file system mounted at /proc. It does not store data on disk. Instead, every time a process reads or writes an entry under /proc, the kernel generates the content on the fly by calling functions inside your driver or module. This makes procfs read write operations a lightweight way to expose kernel state to user space, and to let user space adjust that state, without inventing a custom ioctl or a full character device.
Typical uses of a procfs read write interface include exposing debug counters, toggling a feature flag, dumping internal driver context, or accepting simple configuration values typed with echo. It is not meant for high-throughput data transfer; for that, a character device or sysfs binary attribute is a better fit.
cat / echo
read / write
proc_ops vs the Old file_operations Structure
If you have looked at older Linux kernel programming books or tutorials, you may see procfs entries registered using struct file_operations. That approach is outdated. Starting with Linux kernel 5.6, procfs callbacks moved to a dedicated, smaller structure called struct proc_ops. This tutorial uses the modern approach so your code compiles cleanly on current 6.x kernels.
| Aspect | Old (file_operations) | Modern (proc_ops, 5.6+) |
|---|---|---|
| Structure name | struct file_operations | struct proc_ops |
| Open callback field | .open | .proc_open |
| Read callback field | .read | .proc_read |
| Write callback field | .write | .proc_write |
| Memory footprint | Larger, generic for all VFS users | Smaller, procfs-specific, less overhead |
Step 1: Create the proc Entry
Every procfs read write module starts by registering an entry, usually inside your module’s init function, using proc_create(). This associates a name under /proc with your proc_ops structure.
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#define PROC_ENTRY_NAME "ep_led_level"
static unsigned int led_level;
static struct proc_dir_entry *ep_proc_file;
static const struct proc_ops ep_proc_fops = {
.proc_open = ep_proc_open,
.proc_read = seq_read,
.proc_write = ep_proc_write,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
static int __init ep_procfs_init(void)
{
ep_proc_file = proc_create(PROC_ENTRY_NAME, 0644, NULL, &ep_proc_fops);
if (!ep_proc_file)
return -ENOMEM;
pr_info("ep_procfs: /proc/%s created\n", PROC_ENTRY_NAME);
return 0;
}
The mode 0644 tells the kernel that the owner can read and write the file, while everyone else can only read it. Adjust this permission based on how sensitive the data is.
Step 2: Implement the Read Path with seq_file
Directly filling a buffer for procfs reads is error-prone once the output grows beyond a single page. The seq_file API solves this by letting the kernel handle pagination for you, so your job is only to describe what to print. We use single_open() when the output is a single, simple block of text rather than an iterated list.
static int ep_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "led_level=%u\n", led_level);
return 0;
}
static int ep_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, ep_proc_show, NULL);
}
Here, ep_proc_show() is the actual “producer” function. Every time a user runs cat /proc/ep_led_level, the kernel calls this function and streams whatever you write into m back to that process.
Step 3: Implement the Write Path Safely
The write side of a procfs read write interface must never trust the data coming from user space directly. Always copy it into a bounded kernel buffer with copy_from_user(), validate it, and only then update your internal state.
static ssize_t ep_proc_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
char kbuf[16];
unsigned int new_level;
int ret;
if (count == 0 || count >= sizeof(kbuf))
return -EINVAL;
if (copy_from_user(kbuf, ubuf, count))
return -EFAULT;
kbuf[count] = '\0';
ret = kstrtouint(kbuf, 0, &new_level);
if (ret)
return ret;
if (new_level > 255)
return -EINVAL;
led_level = new_level;
return count;
}
Notice three defensive checks here: bounding the input length before touching the buffer, checking the return value of copy_from_user(), and validating the parsed number against a sane range. Skipping any of these is one of the most common procfs read write mistakes beginners make.
Step 4: Clean Up on Module Exit
Every proc entry you create must be removed when the module unloads, otherwise the kernel keeps a dangling reference and a later access can crash the system.
static void __exit ep_procfs_exit(void)
{
proc_remove(ep_proc_file);
pr_info("ep_procfs: /proc/%s removed\n", PROC_ENTRY_NAME);
}
module_init(ep_procfs_init);
module_exit(ep_procfs_exit);
MODULE_LICENSE("GPL");
Testing the procfs Read Write Interface
Build and load the module, then try reading and writing the entry from a terminal:
$ sudo insmod ep_procfs.ko
$ cat /proc/ep_led_level
led_level=0
$ echo 120 | sudo tee /proc/ep_led_level
$ cat /proc/ep_led_level
led_level=120
$ sudo rmmod ep_procfs
If the write appears to succeed but the value never changes, double-check that your write callback is actually wired into .proc_write and that you are returning count on success, not 0.
Common Mistakes and Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Compile error on proc_ops | Using file_operations on kernel 5.6+ | Switch to struct proc_ops and .proc_* field names |
| echo returns “Permission denied” | Wrong mode passed to proc_create() | Use 0644 or 0664 instead of 0444 |
| Kernel oops on write | Missing bounds check before copy_from_user() | Always check count against your buffer size first |
| Module fails to unload cleanly | proc_remove() not called in exit function | Always pair proc_create() with proc_remove() |
Best Practices for procfs Read Write Modules
- Prefer sysfs for simple key-value attributes on real devices; reserve procfs for debugging and diagnostics
- Always use proc_ops on kernel 5.6 and later for a smaller, faster callback table
- Never trust the length or content of data coming from a write() call
- Keep a mutex around shared state if more than one process can read or write the entry concurrently
- Remove every proc entry you create, in the reverse order you created them
Security Considerations
A procfs write callback is a direct channel from an unprivileged or semi-privileged process into kernel memory. Treat every byte from copy_from_user() as hostile input. Set restrictive permissions on the proc entry with proc_create() when the data is sensitive, and consider checking capable(CAP_SYS_ADMIN) inside the write callback for anything that changes system-wide behaviour.
Performance Considerations
procfs read write paths go through the VFS layer and seq_file machinery, which adds a small but real overhead compared to a raw character device read/write. For debug counters and configuration knobs this overhead is irrelevant. Avoid using procfs for anything that needs to move large buffers repeatedly; use a character device with a proper file_operations table instead.
Real-World Use Cases
- Exposing a driver’s internal debug level, similar to what many staging and mainline drivers do under /proc
- Dumping statistics counters such as packets sent, errors, or retries
- Providing a simple runtime toggle for logging verbosity without reloading the module
Summary and Key Takeaways
- procfs read write support lets a kernel module expose state to user space through a simple file interface
- Use struct proc_ops, not the older file_operations, on kernel 5.6 and above
- seq_file with single_open() is the standard way to implement the read side safely
- Always validate and bound-check data in the write callback before touching kernel state
- Always remove the proc entry with proc_remove() in your module’s exit function
Conclusion
You have now built a complete procfs read write interface from scratch, using the modern proc_ops structure and the seq_file API, and you understand why it replaced the older file_operations approach for procfs specifically. This same pattern scales to multiple proc entries, richer output formatting, and driver debug interfaces you will build later in this free Linux kernel programming course.
Frequently Asked Questions
Q1. What is the difference between procfs and sysfs?
procfs is older and more free-form, good for debugging and diagnostics. sysfs enforces one value per file and is the preferred interface for exposing real device attributes.
Q2. Why do I get a compile error about proc_ops not being defined?
You are likely including an old tutorial’s file_operations-based code on a kernel newer than 5.6. Switch to struct proc_ops as shown in this tutorial.
Q3. Do I need seq_file for a simple procfs read write module?
For very small, one-page output you technically could avoid it, but seq_file is the recommended and safest approach even for small outputs, since it removes an entire class of buffer-overflow bugs.
Q4. Can multiple processes read and write the same proc file at once?
Yes, so you should protect any shared state your callbacks touch with a mutex or spinlock, depending on the context.
Q5. What permission should I use for a proc entry that only root should write to?
Use a mode like 0600 or 0644 combined with a capable(CAP_SYS_ADMIN) check inside the write callback for defense in depth.
Q6. Why does my write callback receive extra bytes like a trailing newline?
Shell commands like echo append a newline by default. Strip or account for it before calling kstrtouint() or similar parsing functions.
Q7. Is procfs still relevant on modern Linux kernels?
Yes. procfs is actively maintained and widely used for debugging, statistics, and diagnostics across mainline and staging drivers even on the latest 6.x kernels.
This lecture is part of EmbeddedPathashala’s free Linux kernel programming and Linux device drivers course.
Browse Full Course
3 Comments