Linux 设备驱动中的 Sysfs 与 Kobject 子系统:用户空间属性控制文件实战

在 Linux 嵌入式设备驱动开发中,除了传统的字符设备节点(/dev/my_device + ioctl())之外,统一设备模型(Linux Unified Device Model) 通过虚拟内存文件系统 sysfs(挂载在 /sys 目录下),向用户空间暴露了一套标准、清晰、面向对象的设备层级控制接口。
在现代嵌入式 Linux 系统中:
- 调节板载 LED 指示灯的亮度(echo 255 > /sys/class/leds/sys_led/brightness);
- 切换 CPU 的调频策略(echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor);
- 读取硬件温度传感器的实时数值(cat /sys/bus/iio/devices/iio:device0/in_temp_raw)。
这些优雅的人机交互全部依赖于 sysfs 虚拟属性文件(Attribute Files)。
sysfs 绝不是一个普通的磁盘文件系统,其底层的核心基石是 Linux 内核对象管理引擎——struct kobject、struct kset 与 struct attribute。
深入掌握 Kobject 的引用计数(Reference Counting / kref)、层级树状目录自动生成机制 以及 DEVICE_ATTR_RW / DEVICE_ATTR_RO 属性文件的读写回调实现,是编写符合 Linux 主线标准(Upstream Standard)现代化驱动的必备基本功。
Sysfs 与 Kobject 体系的底层微观拓扑
在 Linux 内核中,每一个在 /sys 下可见的目录,物理上都严格对应着内核中的一个 struct kobject 结构体;而目录下的每一个可读写属性文件,都对应着挂接在该 Kobject 上的一个 struct attribute 描述符:
Linux Sysfs 与 Kobject 微观对象树拓扑:
【内核统一设备模型对象树 (Object Hierarchy)】
struct kset (如 /sys/class/custom_sensor 总目录容器)
└── struct kobject (子目录: /sys/class/custom_sensor/sensor_0)
├── kref: 原子引用计数器 (记录该对象被引用的次数,归 0 时自动释放内存)
├── parent: 指向父级 kobject (形成目录层级链条)
└── struct ktype *ktype
└── struct sysfs_ops *sysfs_ops (统一的 show / store 总分发函数)
│
├──► [ 属性文件 1: "sampling_rate" (DEVICE_ATTR_RW) ]
│ ├── show() 回调 ──► 读取当前采样率 (cat 命令触发!)
│ └── store() 回调 ──► 修改当前采样率 (echo 命令触发!)
│
└──► [ 属性文件 2: "raw_temperature" (DEVICE_ATTR_RO / 只读) ]
└── show() 回调 ──► 读取实时硬件 ADC 温度
Kobject 的核心数据结构与引用计数(kref)
struct kobject {
const char *name; // 目录名称
struct list_head entry; // 挂载在父 kset 链表中的节点
struct kobject *parent; // 父目录指针
struct kset *kset; // 所属集合
const struct kobj_type *ktype; // 核心方法表 (包含 sysfs_ops 和 release 回调)
struct kernfs_node *sd; // 对应的 sysfs 虚拟目录项
struct kref kref; // 原子引用计数器
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
};
引用计数生命周期铁律:
- 每次获取对象指针调用 kobject_get(kobj),kref 原子加 1;
- 每次释放对象调用 kobject_put(kobj),kref 原子减 1;
- 当 kref 归零瞬间,内核自动触发 ktype->release() 函数,安全释放其占用的物理内存,彻底消灭野指针与内存泄漏!
工业级 Sysfs 属性控制驱动 C 源码实战
编写一个向 /sys/class/industrial_device/ 暴露模式控制与温度读取属性文件的完整驱动:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
static struct class *industrial_class = NULL;
static struct device *industrial_dev = NULL;
// 驱动内部私有业务状态
static int g_work_mode = 1; // 工作模式: 1 为高性能, 0 为低功耗
static int g_hardware_temp = 425; // 温度原始读数 (42.5 ℃)
// =====================================================================
// 1. 属性 1: "mode" (可读可写属性: DEVICE_ATTR_RW)
// =====================================================================
// show 回调: 当用户执行 "cat /sys/class/industrial_device/dev0/mode" 时被内核调用
static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf) {
// 核心规范: 将内核数据格式化写入 buf 缓冲区 (必须返回写入的字节数,包含换行符 \\n)
return sprintf(buf, "%d\\n", g_work_mode);
}
// store 回调: 当用户执行 "echo 0 > /sys/class/industrial_device/dev0/mode" 时被调用
static ssize_t mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
int val;
int ret = kstrtoint(buf, 10, &val); // 安全字符串转整型
if (ret < 0) return ret;
if (val != 0 && val != 1) {
dev_err(dev, "Invalid mode value! Must be 0 (Low-Power) or 1 (High-Performance).\\n");
return -EINVAL;
}
g_work_mode = val;
dev_info(dev, "Industrial Work Mode switched to: %s\\n", (val == 1) ? "High-Performance" : "Low-Power");
return count; // 必须返回成功消耗的字节数 count!
}
// 核心宏: 声明 dev_attr_mode 属性描述符 (权限: 0644 读写)
static DEVICE_ATTR_RW(mode);
// =====================================================================
// 2. 属性 2: "temperature" (只读属性: DEVICE_ATTR_RO)
// =====================================================================
static ssize_t temperature_show(struct device *dev, struct device_attribute *attr, char *buf) {
// 读取硬件最新传感器温度并输出
return sprintf(buf, "%.1f C\\n", g_hardware_temp / 10.0f);
}
static DEVICE_ATTR_RO(temperature);
// =====================================================================
// 3. 属性组打包 (Attribute Group)
// =====================================================================
static struct attribute *industrial_attrs[] = {
&dev_attr_mode.attr,
&dev_attr_temperature.attr,
NULL, // 哨兵末尾
};
static const struct attribute_group industrial_attr_group = {
.attrs = industrial_attrs,
};
static const struct attribute_group *industrial_attr_groups[] = {
&industrial_attr_group,
NULL,
};
// =====================================================================
// 4. 驱动模块初始化
// =====================================================================
static int __init industrial_sysfs_init(void) {
// a. 在 /sys/class/ 下创建类目录: /sys/class/industrial_device/
industrial_class = class_create(THIS_MODULE, "industrial_device");
if (IS_ERR(industrial_class)) return PTR_ERR(industrial_class);
// b. 核心 API: 创建设备并在该目录下【一键自动挂载属性组文件】!
// 自动在 /sys/class/industrial_device/ 下生成 dev0/ 目录及其内部的 mode 与 temperature 文件!
industrial_dev = device_create_with_groups(
industrial_class,
NULL,
MKDEV(0, 0),
NULL,
industrial_attr_groups,
"dev0"
);
if (IS_ERR(industrial_dev)) {
class_destroy(industrial_class);
return PTR_ERR(industrial_dev);
}
pr_info("[SYSFS DRIVER] /sys/class/industrial_device/dev0/ successfully registered!\\n");
return 0;
}
static void __exit industrial_sysfs_exit(void) {
// 销毁设备与类 (自动触发 kobject_put,清理所有 sysfs 文件与目录)
device_destroy(industrial_class, MKDEV(0, 0));
class_destroy(industrial_class);
pr_info("[SYSFS DRIVER] Driver unloaded and sysfs nodes destroyed.\\n");
}
module_init(industrial_sysfs_init);
module_exit(industrial_sysfs_exit);
MODULE_LICENSE("GPL");
Sysfs 属性编写三大黄金军规
Sysfs 属性设计铁律:
1. 【一文件一值原则 (One Value Per File)】:
每一个 sysfs 属性文件【必须且只能表达一个单纯的物理量或状态】!
严禁将 10 个配置参数拼成一个复杂的 JSON 或字符串塞进同一个 sysfs 文件中!
(复杂二进制数据流应使用 /dev 字符设备或 debugfs/configfs 处理)
2. 【严格控制输出尺寸 (PAGE_SIZE 限制)】:
show() 回调函数写入 buf 缓冲区的总数据量【绝对不能超过一个内存页 (PAGE_SIZE = 4096 字节)】!
3. 【使用标准的 kstrtox 安全转换函数】:
在 store() 中解析用户输入时,严禁使用不安全的 atoi() 或 sscanf(),
必须且只能使用 kstrtoint(), kstrtoul(), kstrtobool() 等内核安全转换函数防范缓冲区溢出!
透视 Kobject 在内核树状拓扑中的引用计数管理,规范实现 DEVICE_ATTR 属性文件,驱动开发者才能为上层应用和自动化测试脚本提供最标准、最健壮的工业级系统控制接口。



