Beginner → Intermediate
14 minutes
6.x (LTS)
If you are learning Linux device driver development, understanding the sysfs interface in Linux kernel programming is one of the most practical skills you can build. Sysfs is the modern, structured way for a kernel module to expose tunable values and status information to user space, and it has replaced most ad-hoc procfs usage in real drivers. In this lesson, part of our free Linux kernel development course, we build a working sysfs interface in Linux kernel style from first principles, using APIs that are current on kernel 6.x, and explain every design decision along the way.
- What a sysfs interface in Linux kernel space actually is, and why it replaced ad-hoc procfs files
- How kobjects and kset hierarchies back every sysfs directory
- How to declare attributes with
kobj_attributeand register them withsysfs_create_group() - Why modern kernels require
sysfs_emit()instead ofsprintf()/snprintf() - How to build, load, and test a sysfs-backed kernel module safely
- Common mistakes, security pitfalls, and performance considerations
- Comfort writing and loading a basic Loadable Kernel Module (LKM)
- Working knowledge of C pointers and structures
- A Linux VM or test machine running kernel 5.10 or later (kernel 6.x recommended)
- Kernel headers installed for your running kernel
Sysfs is a virtual, in-memory filesystem, conventionally mounted at /sys, that exposes the kernel’s internal object model to user space as a tree of directories and files. Every kobject registered in the kernel gets a corresponding directory in sysfs, and every attribute attached to that kobject becomes a plain text file underneath it. When a user space program reads that file, the kernel runs a “show” callback; when it writes to the file, the kernel runs a “store” callback. That is the entire mental model you need to build a sysfs interface in Linux kernel drivers.
Compared to older mechanisms, sysfs enforces a strict convention: one value per file, human-readable text, and no complex ioctl-style protocols. This is exactly why the kernel community has pushed driver authors toward sysfs for configuration and status reporting over the last two decades.
cat /sys/…/value
show() callback
New learners often confuse the three virtual filesystems. Here is a straightforward comparison table you can keep as a reference while building your own drivers.
| Filesystem | Intended Purpose | Convention |
|---|---|---|
| sysfs (/sys) | Structured device/driver attributes tied to the kobject model | One value per file, ABI-stable |
| procfs (/proc) | Process info and legacy kernel tunables | Free-form text, less structured |
| debugfs (/sys/kernel/debug) | Developer-only debugging aids | No ABI stability guarantee |
Tip: If a value is meant for scripts, monitoring tools, or udev rules, it belongs in sysfs. If it is purely for kernel developers debugging a driver, debugfs is the right home instead.
Every directory you see under /sys is backed by a struct kobject somewhere in the kernel. A kobject provides reference counting, a name, and a parent link, which is exactly what sysfs needs to build a directory tree. When a driver wants its own subdirectory under /sys/kernel, it typically calls kobject_create_and_add() and then attaches an attribute group to that kobject.
Let’s build a small, original example: a kernel module that exposes a single tunable integer called fan_speed under /sys/kernel/ep_demo/. We will use the modern kobj_attribute style with __ATTR, which is the lightweight approach recommended when you don’t already have a struct device to hang attributes off.
First, declare the backing variable and the show/store callbacks:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/mutex.h>
static struct kobject *ep_demo_kobj;
static int fan_speed = 50;
static DEFINE_MUTEX(ep_demo_lock);
static ssize_t fan_speed_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
int val;
mutex_lock(&ep_demo_lock);
val = fan_speed;
mutex_unlock(&ep_demo_lock);
return sysfs_emit(buf, "%d\n", val);
}
Notice the use of sysfs_emit() instead of snprintf(buf, PAGE_SIZE, ...). This is a kernel 5.10+ helper that always writes safely within a single PAGE_SIZE buffer and is now the recommended way to implement any sysfs interface in Linux kernel show callback. Older tutorials that still use raw sprintf() are teaching a pattern the kernel community has moved away from.
| sysfs_emit() | Bounds-checked, buffer must be the sysfs page pointer, recommended since 5.10 |
| sprintf() / snprintf() | No enforced page-size awareness, considered legacy in new sysfs code |
Now the store callback, which validates and updates the value:
static ssize_t fan_speed_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int new_val, ret;
ret = kstrtoint(buf, 10, &new_val);
if (ret < 0)
return ret;
if (new_val < 0 || new_val > 100)
return -EINVAL;
mutex_lock(&ep_demo_lock);
fan_speed = new_val;
mutex_unlock(&ep_demo_lock);
return count;
}
static struct kobj_attribute fan_speed_attr =
__ATTR(fan_speed, 0664, fan_speed_show, fan_speed_store);
Always validate input with kstrtoint() or a related helper before trusting a value coming from user space, and always enforce sane range limits. A sysfs interface in Linux kernel drivers is still kernel code — a bad value here can crash a live system.
With the attribute declared, group it and register it against a dedicated kobject in the module’s init function:
static struct attribute *ep_demo_attrs[] = {
&fan_speed_attr.attr,
NULL,
};
static struct attribute_group ep_demo_attr_group = {
.attrs = ep_demo_attrs,
};
static int __init ep_demo_init(void)
{
int ret;
ep_demo_kobj = kobject_create_and_add("ep_demo", kernel_kobj);
if (!ep_demo_kobj)
return -ENOMEM;
ret = sysfs_create_group(ep_demo_kobj, &ep_demo_attr_group);
if (ret) {
kobject_put(ep_demo_kobj);
return ret;
}
pr_info("ep_demo: sysfs interface ready at /sys/kernel/ep_demo/fan_speed\n");
return 0;
}
static void __exit ep_demo_exit(void)
{
sysfs_remove_group(ep_demo_kobj, &ep_demo_attr_group);
kobject_put(ep_demo_kobj);
}
module_init(ep_demo_init);
module_exit(ep_demo_exit);
MODULE_LICENSE("GPL");
Using sysfs_create_group() instead of registering attributes one at a time keeps cleanup simple: a single sysfs_remove_group() call in the exit path removes every file you created, which avoids the common bug of forgetting to remove one attribute on unload.
Once compiled and loaded with insmod, you can interact with your sysfs interface in Linux kernel space exactly like any other file:
$ make
$ sudo insmod ep_demo.ko
$ cat /sys/kernel/ep_demo/fan_speed
50
$ sudo sh -c "echo 75 > /sys/kernel/ep_demo/fan_speed"
$ cat /sys/kernel/ep_demo/fan_speed
75
$ sudo rmmod ep_demo
If the write silently fails or the file doesn’t appear, check dmesg first — most sysfs registration failures show up there immediately as a non-zero return from sysfs_create_group().
- Exposing multiple values in one file: sysfs convention is strictly one value per attribute file; don’t pack several fields into one string.
- Skipping locking: show/store callbacks can run concurrently from multiple user space processes; always protect shared state with a mutex or spinlock.
- Forgetting input validation in store(): never trust the buffer contents blindly; use
kstrtoint()/kstrtoul()and range-check the result. - Not cleaning up on module exit: every
kobject_create_and_add()needs a matchingkobject_put(), and everysysfs_create_group()needs a matchingsysfs_remove_group(). - Using raw sprintf() in show(): prefer
sysfs_emit()on kernel 5.10 and later.
- Treat every sysfs file you ship as a stable user space ABI — once released, avoid renaming or removing it.
- Document new attributes under
Documentation/ABI/in the kernel source tree, following upstream convention. - Keep permissions conservative: read-only (
0444) unless a value genuinely needs to be writable. - Group related attributes under one attribute_group rather than scattering separate kobjects.
- Prefer
DEVICE_ATTR_RW/DEVICE_ATTR_ROstyle macros when the attribute is naturally tied to astruct device, andkobj_attributestyle when it is a standalone driver-level knob.
Sysfs files are reachable by any local user with sufficient permissions, so treat store() callbacks as untrusted input boundaries. Enforce strict length checks on the incoming buffer, reject anything that fails numeric parsing, and never use user-supplied data to compute a memory offset or size without validation. Where a value affects hardware state (voltage, frequency, power), consider whether it truly needs to be writable by non-root users at all.
Sysfs reads and writes are not a hot path — the kernel expects them to be occasional configuration or status operations, not something called in a tight loop. Avoid doing expensive work (long computations, blocking I/O) inside a show() or store() callback; if a value is genuinely expensive to compute, cache it and refresh it periodically instead of recalculating it on every read.
- Exposing device power state and brightness for backlight drivers
- Reporting battery charge level and health for power supply drivers
- Tuning fan curves and thermal thresholds on embedded boards
- Publishing network interface statistics consumed by monitoring tools
- Providing udev with attributes it can match against for device rules
- A sysfs interface in Linux kernel drivers is built from kobjects, attribute structures, and show/store callbacks.
- Modern kernels expect
sysfs_emit()in show callbacks instead of rawsprintf(). sysfs_create_group()andsysfs_remove_group()keep registration and cleanup symmetric and safe.- Always lock shared state and validate every value coming from store().
- Sysfs files are a user space ABI — design them to be stable from day one.
Learning to build a correct sysfs interface in Linux kernel modules is a foundational skill for anyone working through a free Linux device drivers course or preparing for embedded Linux driver roles. Start with the small kobject-based example in this lesson, then extend it with additional attributes, proper locking, and ABI documentation as your driver grows. In the next lesson of this free Linux kernel development course, we will look at attribute groups with dynamic sysfs entries and how they interact with the driver model.
Q1. What is the difference between sysfs and procfs?
Sysfs is structured around the kernel’s kobject/device model with a strict one-value-per-file rule, while procfs is older, less structured, and mostly used today for process information and legacy tunables.
Q2. Do I need a struct device to create a sysfs interface in Linux kernel code?
No. You can create a standalone sysfs directory using kobject_create_and_add() and kobj_attribute, as shown in this lesson, without any backing device.
Q3. Why use sysfs_emit() instead of sprintf() in the show callback?
sysfs_emit() is the current recommended helper starting kernel 5.10; it safely respects the single-page buffer sysfs provides and avoids classes of overflow bugs common with raw sprintf().
Q4. Can a sysfs attribute be write-only?
Yes, by setting the permission bits and only implementing a store() callback, though most attributes are either read-only or read-write.
Q5. What permission value should I use for a read-write attribute?
0664 is the common convention, giving read/write to owner and group and read-only to others; adjust based on your security requirements.
Q6. Is sysfs available in embedded Linux systems?
Yes, sysfs is a core part of the Linux kernel and is present on virtually every embedded Linux build, including Yocto and Buildroot-based systems.
Q7. What happens if I forget to call sysfs_remove_group() on module unload?
The sysfs entries can be left in an inconsistent state and may cause a crash or warning the next time the module is loaded, since the kobject is never properly released.
Q8. How do I document a new sysfs attribute properly?
Follow the upstream kernel convention of adding a file under Documentation/ABI/ describing the attribute’s path, date, contact, and description.
This lesson is part of EmbeddedPathashala’s free Linux device drivers and kernel development course. Explore more lessons on kobjects, character drivers, and kernel synchronization.

2 Comments