Over the last couple of posts in this series I’ve been digging deeper into building small kernel modules against mainline, getting them to run cleanly under QEMU, and slowly building up the muscle memory for day-to-day kernel work. Up to this point I’ve only been dealing with pr_* output or simple tracepoints or kprobes, so I figured the next logical step was to expose data from a module to user space. As I understand it the easiest place to start with that is the proc filesystem.
This post walks through creating a small read-only /proc entry using the modern, accepted interfaces in the Linux kernel. No deprecated APIs, no shortcuts, and nothing that would get laughed out of a patch review. Hopefully.
Understanding /proc at a high level
The important thing to remember is that nothing in /proc actually lives on disk. The proc filesystem is a virtual filesystem created by the kernel. When you read a file in /proc, the kernel runs code that generates the content on the fly.
That makes proc files useful for state dumps, diagnostics, or anything where you want to give user space a quick window into whatever your module is doing.
The modern way of creating proc entries uses two pieces:
proc_create() with a set of struct proc_ops
The seq_file API, usually through the single_open() helper
This is the pattern that upstream kernel developers expect. Older APIs still exist in tree history, but you should not use them in new code. As I understand it.
The module
Here’s the complete module. This creates a /proc/gs_proc_demo entry that returns a message plus the current value of jiffies.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#define PROC_NAME "gs_proc_demo"
static int gs_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "hello from %s\n", PROC_NAME);
seq_printf(m, "jiffies: %lu\n", jiffies);
return 0;
}
static int gs_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, gs_proc_show, NULL);
}
static const struct proc_ops gs_proc_ops = {
.proc_open = gs_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
static int __init gs_proc_init(void)
{
proc_create(PROC_NAME, 0, NULL, &gs_proc_ops);
pr_info("%s: loaded\n", PROC_NAME);
return 0;
}
static void __exit gs_proc_exit(void)
{
remove_proc_entry(PROC_NAME, NULL);
pr_info("%s: unloaded\n", PROC_NAME);
}
module_init(gs_proc_init);
module_exit(gs_proc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("adam");
MODULE_DESCRIPTION("Proc filesystem demo");
A couple of quick notes that helped me understand what’s going on:
single_open() sets up the seq_file context and ensures your show() callback runs exactly once per read. That means you don’t need to think about offsets or partial reads.
Your show() function should never call printk. You write to the seq_file using seq_printf(). In general it seems that one should never call printk, and instead should use the pr_* macros.
The contents are generated every time user space reads the file. Nothing is cached.
Building against mainline
Same workflow as the earlier modules:
make -C /path/to/torvalds/kernel M=$PWD modules
Boot into your QEMU VM that’s running the same kernel image.
Insert the module:
insmod gs_proc_demo.ko
Read from the proc entry:
cat /proc/gs_proc_demo
Output looks something like:
hello from gs_proc_demo
jiffies: 12345678
Remove it:
rmmod gs_proc_demo
Why this matters
Creating a /proc entry is one of those small steps that teaches you several deeper kernel development concepts at the same time: how the VFS layer works, how seq_file buffers output safely, how live kernel data is exposed to user space, and how kernel modules interact cleanly with filesystem interfaces.
It’s also one of the first things you’ll run into when reading real-world kernel code. Most subsystems have at least one proc entry for debugging or state dumping. Being comfortable with this pattern means you’re finally out of “hello world” territory and starting to write modules with actual interfaces.
Next up? I don't know honestly, maybe a quick comparison of /proc, sysfs, and debugfs, and where each one is appropriate.
Thanks for reading and as always if you've any suggestions I'd love to hear them.
Top comments (0)