一、用户操作驱动设备的方式
1.1 字符设备接口
这是最常用的设备交互方式,适用于流式数据传输或复杂命令交互的设备(如串口、LED、传感器、显卡等)。
核心特点:
通过 /dev 目录下的设备节点(如 /dev/ttyS0、/dev/led0)操作;
驱动需实现 file_operations 结构体(open/read/write/ioctl 等);
支持阻塞 / 非阻塞读写、ioctl 命令控制、mmap 内存映射等复杂操作。
示例(用户态代码):
1 2 3 4 5 6 7 8 9 10 11 12 int fd = open("/dev/led0" , O_RDWR);if (fd < 0 ) { } write(fd, "255" , 3 ); ioctl(fd, LED_SET_MODE, MODE_BLINK); close(fd);
这种方式适用于哪些需要复杂交互(如命令控制、批量数据传输、异步操作)的设备。
1.2 sysfs 接口
这是基于虚拟文件系统的属性暴露机制,适用于简单的属性读写(如设备状态查询、参数配置)。
核心特点:
通过 /sys 目录下的属性文件(如 /sys/class/leds/led0/brightness)操作;
驱动只需定义 show/store 回调函数(无需完整 file_operations);
接口标准化,操作简单(通过 cat/echo 即可交互)。
示例(用户态操作):
1 2 3 4 5 # 读取亮度 cat /sys/class/leds/led0/brightness # 设置亮度 echo 255 > /sys/class/leds/led0/brightness
适用于简单的属性配置(如开关、亮度、模式切换),无需复杂命令交互的设备。
1.3 procfs 接口
基于 /proc 虚拟文件系统,主要用于内核 / 驱动的状态信息查询(如进程状态、内存使用、驱动调试信息)。
核心特点:
通过 /proc 目录下的文件(如 /proc/cpuinfo、/proc/driver/mydriver)操作;
驱动需注册 proc_ops 结构体(类似 file_operations);
更偏向 “信息展示”,而非设备控制(现代驱动中逐渐被 sysfs 替代)。
示例(用户态操作):
1 2 # 查看驱动调试信息 cat /proc/driver/mydriver/debug_info
适用于内核 / 驱动的调试信息输出、系统状态查询(如 /proc/net 网络信息)。
1.4 设备文件系统(devfs)
早期 Linux 用于管理设备节点的文件系统,现已被 udev + devtmpfs 替代,但概念上仍需了解。
核心特点:
自动创建 /dev 节点,无需手动 mknod;
现代系统中,udev 会根据设备的 subsystem、devtype 等属性动态生成 /dev 节点,并支持规则自定义(如 /etc/udev/rules.d/)。
示例(udev 规则自定义节点名称):
1 2 3 # /etc/udev/rules.d/70-led.rules SUBSYSTEM=="leds", ATTR{name}=="user-led", SYMLINK+="myled"# 生成 /dev/myled 符号链接,指向实际 LED 设备节点
一般用于设备节点的动态管理和命名规则自定义。
1.5 Netlink 套接字
基于内核与用户态的异步通信机制,适用于事件通知或高频数据交互(如热插拔事件、网络配置、实时监控)。
核心特点:
类似 socket 接口,支持双向通信和多播;
驱动需注册 netlink_kernel_cfg 结构体,用户态通过 socket API 操作;
效率高于 ioctl,适合传递结构化数据和异步事件。
示例(用户态框架):
1 2 3 int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_MYDRIVER);
适用于设备事件通知(如热插拔、状态变化)、内核与用户态的高频数据交换。
总结:选择原则
交互需求
推荐方式
典型设备/场景
复杂命令 / 流式数据
字符设备(/dev)
串口、显卡、传感器数据采集
简单属性读写
sysfs(/sys)
LED 亮度、温度传感器数值
调试信息 / 系统状态
procfs(/proc)
内核版本、驱动调试日志
设备节点动态管理
udev + devtmpfs
自动生成 / 命名 /dev 节点
异步事件 / 高频通信
Netlink 套接字
热插拔事件、实时监控数据
二、sysfs 相关目录文件
sysfs 的核心代码集中在 fs/sysfs/ 目录下,该目录包含了 sysfs 文件系统的底层实现(如 inode 管理、文件操作、目录结构维护等)。
2.1 核心实现目录
fs/sysfs/file.c
作用:实现 sysfs 中文件的核心操作逻辑,是 sysfs 与 VFS(虚拟文件系统)对接的关键。
核心内容:
定义 sysfs 的 file_operations 结构体(sysfs_file_operations),包含 open/read/write/llseek 等通用文件操作;
实现用户空间与内核空间的数据交互(如 sysfs_read/sysfs_write 函数,负责将用户读写转发给属性的 show/store 回调);
管理文件的打开 / 关闭状态,处理并发访问。
fs/sysfs/dir.c
作用:管理 sysfs 的目录结构,负责目录的创建、删除和层级关系维护。
核心内容:
定义 sysfs_dirent 结构体(sysfs 中目录和文件的统一表示,包含名称、类型、父节点指针等);
实现目录创建(sysfs_create_dir)、删除(sysfs_remove_dir)、重命名等操作;
维护 sysfs 的目录树结构,确保节点间的层级关系正确。
fs/sysfs/sysfs.h
作用:sysfs 内部的头文件,定义核心数据结构和内部函数声明。
核心内容:
声明 struct sysfs_dirent、struct sysfs_ops 等核心结构体;
定义 sysfs 内部使用的宏和辅助函数(如 sysfs_get/sysfs_put 用于引用计数管理)。
fs/sysfs/attr.c
作用:处理 sysfs 属性(attribute)的注册与管理,是驱动与 sysfs 交互的桥梁。
核心内容:
实现属性文件的创建(sysfs_create_file)、删除(sysfs_remove_file);
处理属性组(attribute group)的批量注册(sysfs_create_group);
关联属性的 show/store 回调函数与 sysfs 的文件操作。
fs/sysfs/mount.c
作用:负责 sysfs 文件系统的挂载初始化,是 sysfs 作为虚拟文件系统的入口。
核心内容:
定义 sysfs 的 file_system_type 结构体(sysfs_fs_type),向 VFS 注册 sysfs;
实现 sysfs 的挂载函数(sysfs_mount),初始化根目录和超级块(super block)。
2.2 与其它子系统的交互文件
sysfs 并非独立存在,而是与内核其他子系统(如设备模型、总线、驱动)深度集成,这些子系统通过特定文件定义自己的 sysfs 属性:
设备模型相关(include/linux/device.h)
定义 struct device_attribute(设备属性结构体)和 DEVICE_ATTR 宏,用于设备在 sysfs 中暴露属性;
提供 device_create_file/device_remove_file 等函数,简化设备属性的注册。
总线与驱动相关(include/linux/device.h、include/linux/module.h)
总线(如 platform 总线)和驱动通过 struct bus_attribute、struct driver_attribute 定义自己的 sysfs 属性;
例如,/sys/bus/platform/drivers/ 下的驱动属性文件,由 driver_create_file 函数创建。
类设备相关(include/linux/device.h)
类(struct class)通过 struct class_attribute 定义类级别的 sysfs 属性(如 /sys/class/leds/ 目录下的共性属性);
提供 class_create_file 等函数,用于类属性的注册。
2.3 用户态可见的 sysfs 目录结构
虽然不属于内核实现文件,但了解用户态可见的 sysfs 目录结构有助于理解其实现逻辑。sysfs 挂载在 /sys 目录下,核心子目录包括:
/sys/devices/:所有设备的底层表示,按硬件拓扑结构组织;
/sys/class/:按设备功能分类的目录(如 leds、tty、net),是用户访问的主要入口;
/sys/bus/:按总线类型分类(如 platform、usb),包含总线上的设备和驱动;
/sys/drivers/:系统中所有驱动的信息;
/sys/dev/:设备号与设备的映射关系。
三、从用户态看 sysfs
以 edge_led 为例:
1 ls /sys/bus/platform/devices/edge-led/
可以看到:
读取:
1 cat /sys/bus/platform/devices/edge-led/brightness
写入:
1 2 echo 1 | sudo tee /sys/bus/platform/devices/edge-led/brightnessecho 0 | sudo tee /sys/bus/platform/devices/edge-led/brightness
触发动作:
1 echo 2 | sudo tee /sys/bus/platform/devices/edge-led/blink
用户态看到的是普通文件,但内核里的路径是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 用户态 cat ↓ VFS / sysfs ↓ brightness_show() ↓ 驱动返回字符串 用户态 echo ↓ VFS / sysfs ↓ brightness_store() ↓ 驱动解析字符串并操作硬件
四、生成 sysfs 接口
4.1 kobject 模型
下面从最基础的接口讲起,不借助 class、device 等高级封装,只用 kobject 和 sysfs_create_file 在 /sys/ 下创建一个属性文件。
最终目标:在 /sys/kernel/kobj_lab/ 目录下创建一个文件 value,用户可以读写它:
1 2 3 4 5 6 7 8 # 读 $ cat /sys/kernel/kobj_lab/value 0# 写 $ echo 123 > /sys/kernel/kobj_lab/value$ cat /sys/kernel/kobj_lab/value 123
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <linux/kernel.h> #include <linux/kobject.h> #include <linux/module.h> #include <linux/sysfs.h> static struct kobject *kobj_lab ; static int value; static ssize_t value_show (struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sysfs_emit(buf, "%d\n" , value); }static ssize_t value_store (struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int ret; int new_value; ret = kstrtoint(buf, 10 , &new_value); if (ret) return ret; value = new_value; return count; }static struct kobj_attribute value_attr = __ATTR(value, 0644 , value_show, value_store);static int __init kobj_lab_init (void ) { int ret; kobj_lab = kobject_create_and_add("kobj_lab" , kernel_kobj); if (!kobj_lab) return -ENOMEM; ret = sysfs_create_file(kobj_lab, &value_attr.attr); if (ret) { kobject_put(kobj_lab); return ret; } pr_info("kobj_lab: /sys/kernel/kobj_lab/value ready\n" ); return 0 ; }static void __exit kobj_lab_exit (void ) { sysfs_remove_file(kobj_lab, &value_attr.attr); kobject_put(kobj_lab); } module_init(kobj_lab_init); module_exit(kobj_lab_exit); MODULE_LICENSE("GPL" );
测试结果如前面所示。
执行流程图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 insmod mydemo.ko │ ▼mydemo_init () │ ├─ kobject_create_and_add ("kobj_lab" , kernel_kobj) │ │ │ ▼ │ 在 /sys/kernel/ 下创建 kobj_lab/ 目录 │ └─ sysfs_create_file (kobj_lab, &value_attr.attr) │ ▼ 在 /sys/kernel/kobj_lab/ 下创建 value 文件 │ ▼ 用户空间可见:/sys/kernel/kobj_lab/value rmmod mydemo.ko │ ▼mydemo_exit () │ ├─ sysfs_remove_file (...) 删除文件 │ └─ kobject_put (kobj_lab) 删除目录,释放内存
补充:/sys/ 根目录下的直接子目录
你看到的每一个 /sys/xxx/yyy 路径,背后都对应一个 struct kobject。 不同路径只是这个 kobject 挂载的位置不同。
这些是内核预定义的顶层目录 ,每个对应一个内核中预先创建好的全局 kobject 或 kset。
路径
内核中对应的全局对象
头文件
典型用途
/sys/kernel/
kernel_kobj
<linux/kobject.h>
内核核心子系统、非设备的内核模块参数
/sys/module/
module_kset
<linux/module.h>
每个内核模块的信息和参数
/sys/devices/
devices_kset
<linux/device.h>
真实设备树 ,所有设备的物理/逻辑挂载点
/sys/class/
class_kset
<linux/device.h>
设备按功能分类的视图 (符号链接到 /sys/devices/)
/sys/bus/
bus_kset
<linux/device.h>
总线类型(PCI、USB、I2C等)及其上的驱动
/sys/fs/
fs_kobj
<linux/kobject.h>
文件系统相关的参数(如cgroup、fuse)
/sys/power/
power_kobj
<linux/kobject.h>
电源管理(休眠、唤醒等)
/sys/firmware/
firmware_kobj
<linux/kobject.h>
固件相关(ACPI、EFI、设备树)
/sys/block/
block_kset
<linux/blkdev.h>
块设备(已逐渐迁移到 /sys/class/block/)
/sys/fs/cgroup/
特殊(cgroup 子系统)
<linux/cgroup.h>
cgroup 控制组
重要 :以上这些全局对象,你不需要自己创建 ,它们是内核启动时就存在的。你只需要在编写内核模块时,把你自己创建的 kobject 挂到它们下面 即可。
4.2 多属性注册
当有多个属性时,有两种方法 :
多次调用 sysfs_create_file (最直接,但代码冗长)
使用 attribute_group + sysfs_create_group批量创建 (推荐,最常用)
方法一:多次调用 sysfs_create_file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /static int my_value1 = 0 ;static int my_value2 = 0 ;static int my_value3 = 0 ;static struct kobj_attribute value_attr1 = __ATTR(value1, 0644 , show1, store1);static struct kobj_attribute value_attr2 = __ATTR(value2, 0644 , show2, store2);static struct kobj_attribute value_attr3 = __ATTR(value3, 0644 , show3, store3);static int __init mydemo_init (void ) { kobj_lab = kobject_create_and_add("kobj_lab" , kernel_kobj); if (!kobj_lab) return -ENOMEM; sysfs_create_file(kobj_lab, &value_attr1.attr); sysfs_create_file(kobj_lab, &value_attr2.attr); sysfs_create_file(kobj_lab, &value_attr3.attr); return 0 ; }
方法二:使用 attribute_group 批量创建(推荐)
这是最常用的方法 ,将多个属性组织成一个组,一次性创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/kobject.h> #include <linux/sysfs.h> #include <linux/string.h> static int my_value1 = 0 ;static int my_value2 = 0 ;static int my_value3 = 0 ;static struct kobject *kobj_obj ;static ssize_t value1_show (struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf (buf, "%d\n" , my_value1); }static ssize_t value1_store (struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int ret = kstrtoint(buf, 10 , &my_value1); return ret ? ret : count; }static ssize_t value2_show (struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf (buf, "%d\n" , my_value2); }static ssize_t value2_store (struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int ret = kstrtoint(buf, 10 , &my_value2); return ret ? ret : count; }static ssize_t value3_show (struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf (buf, "%d\n" , my_value3); }static ssize_t value3_store (struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int ret = kstrtoint(buf, 10 , &my_value3); return ret ? ret : count; }static struct kobj_attribute value1_attr = __ATTR(value1, 0644 , value1_show, value1_store);static struct kobj_attribute value2_attr = __ATTR(value2, 0644 , value2_show, value2_store);static struct kobj_attribute value3_attr = __ATTR(value3, 0644 , value3_show, value3_store);static struct attribute *my_attrs [] = { &value1_attr.attr, &value2_attr.attr, &value3_attr.attr, NULL , };static struct attribute_group my_attr_group = { .name = NULL , .attrs = my_attrs, };static int __init kobj_lab_init (void ) { int ret; kobj_obj = kobject_create_and_add("kobj_lab" , kernel_kobj); if (!kobj_obj) return -ENOMEM; ret = sysfs_create_group(kobj_obj, &my_attr_group); if (ret) { kobject_put(kobj_obj); return ret; } printk(KERN_INFO "kobj_lab: created 3 files in /sys/kernel/kobj_lab/\n" ); return 0 ; }static void __exit kobj_lab_exit (void ) { sysfs_remove_group(kobj_obj, &my_attr_group); kobject_put(kobj_obj); } module_init(kobj_lab_init); module_exit(kobj_lab_exit); MODULE_LICENSE("GPL" );
核心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static struct attribute *my_attrs [] = { &value1_attr.attr, &value2_attr.attr, &value3_attr.attr, NULL , };static struct attribute_group my_attr_group = { .name = NULL , .attrs = my_attrs, }; sysfs_create_group(kobj_obj, &my_attr_group); sysfs_remove_group(kobj_obj, &my_attr_group);
如果你想将属性分组到不同的子目录 下,可以使用多个 attribute_group:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static struct attribute *config_attrs [] = { &value1_attr.attr, &value2_attr.attr, NULL , };static struct attribute_group config_group = { .name = "config" , .attrs = config_attrs, };static struct attribute *status_attrs [] = { &value3_attr.attr, &value4_attr.attr, NULL , };static struct attribute_group status_group = { .name = "status" , .attrs = status_attrs, }; sysfs_create_group(kobj_lab, &config_group); sysfs_create_group(kobj_lab, &status_group);
就会得到如下目录结构:
1 2 3 4 5 6 7 /sys/ kernel/mydemo/ ├── config/ │ ├── value1 │ └── value2 └── status/ ├── value3 └── value4
4.3 标准驱动模型
下例是标准设备驱动模型 的做法,会自动处理好 /sys/class/、/sys/devices/ 和 /dev/ 的联动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <linux/device.h> struct class *my_class = class_create(THIS_MODULE, "myclass" );struct device *my_dev = device_create(my_class, NULL , devt, NULL , "mydevice" ); device_create_file(my_dev, &dev_attr_xxx.attr);
这个方式下你不需要手动调用 kobject_create_and_add ,因为 class_create 和 device_create 已经帮你做了。
在标准驱动模型 下,创建 sysfs 属性文件主要分为三个层级:设备属性 、驱动属性 、类属性 。下面是完整的讲解和对照。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /sys/ ├── class/ ← 类(class)层级 │ └── <class_name > / ← 类目录 │ ├── <attribute > ← 类属性(所有设备共享) │ └── <device_name > / ← 设备目录 │ ├── <attribute > ← 设备属性(每个设备独立) │ └── ... ├── bus/ ← 总线(bus)层级 │ └── <bus_name > / │ ├── drivers/ │ │ └── <driver_name > / │ │ └── <attribute > ← 驱动属性 │ └── devices/... └── devices/ ← 真实设备树
4.3.1 设备属性定义
1 2 3 4 5 6 7 8 9 10 11 DEVICE_ATTR_RW(name); DEVICE_ATTR_RO(name); DEVICE_ATTR_WO(name); DEVICE_ATTR(name, mode, show, store);
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 static ssize_t enable_show (struct device *dev, struct device_attribute *attr, char *buf) { struct my_drv_data *data = dev_get_drvdata(dev); return sprintf (buf, "%d\n" , data->enable); }static ssize_t enable_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct my_drv_data *data = dev_get_drvdata(dev); int ret = kstrtoint(buf, 10 , &data->enable); return ret ? ret : count; }static DEVICE_ATTR_RW (enable) ;static struct attribute *my_dev_attrs [] = { &dev_attr_enable.attr, NULL , };static struct attribute_group my_dev_group = { .attrs = my_dev_attrs, };static struct attribute_group *my_dev_groups [] = { &my_dev_group, NULL , };
注册方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 struct class my_class = { .name = "myclass" , .dev_groups = my_dev_groups, };struct device *dev ; dev->groups = my_dev_groups; device_register(dev); device_create_file(dev, &dev_attr_enable);
4.3.2 驱动属性定义
1 2 3 DRIVER_ATTR_RW(name); DRIVER_ATTR_RO(name); DRIVER_ATTR_WO(name);
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 static int drv_debug_enable = 0 ;static ssize_t debug_show (struct device_driver *drv, char *buf) { return sprintf (buf, "%d\n" , drv_debug_enable); }static ssize_t debug_store (struct device_driver *drv, const char *buf, size_t count) { int ret = kstrtoint(buf, 10 , &drv_debug_enable); return ret ? ret : count; }static DRIVER_ATTR_RW (debug) ;static struct attribute *my_drv_attrs [] = { &driver_attr_debug.attr, NULL , };static struct attribute_group my_drv_group = { .attrs = my_drv_attrs, };static struct attribute_group *my_drv_groups [] = { &my_drv_group, NULL , };
注册方式:
1 2 3 4 5 struct device_driver my_driver = { .name = "mydrv" , .bus = &my_bus, .groups = my_drv_groups, };
4.3.3 类属性(所有设备共享)
1 2 3 CLASS_ATTR_RW(name); CLASS_ATTR_RO(name); CLASS_ATTR_WO(name);
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 static int class_global_param = 0 ;static ssize_t global_show (struct class *cls, struct class_attribute *attr, char *buf) { return sprintf (buf, "%d\n" , class_global_param); }static ssize_t global_store (struct class *cls, struct class_attribute *attr, const char *buf, size_t count) { int ret = kstrtoint(buf, 10 , &class_global_param); return ret ? ret : count; }static CLASS_ATTR_RW (global) ;static struct attribute *my_class_attrs [] = { &class_attr_global.attr, NULL , };static struct attribute_group my_class_group = { .attrs = my_class_attrs, };static struct attribute_group *my_class_groups [] = { &my_class_group, NULL , };
注册方式:
1 2 3 4 struct class my_class = { .name = "myclass" , .class_groups = my_class_groups, };
最终路径: /sys/class/myclass/global(不跟设备名)
下面看一个示例,在 /sys/class/myclass/mydevice/ 下创建多个属性文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 #include <linux/device.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/fs.h> struct my_device_data { int enable; int frequency; char version[32 ]; struct device *dev ; };static struct my_device_data g_data = { .enable = 1 , .frequency = 100 , .version = "1.0.0" , };static ssize_t enable_show (struct device *dev, struct device_attribute *attr, char *buf) { struct my_device_data *data = dev_get_drvdata(dev); return sprintf (buf, "%d\n" , data->enable); }static ssize_t enable_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct my_device_data *data = dev_get_drvdata(dev); int ret, val; ret = kstrtoint(buf, 10 , &val); if (ret) return ret; data->enable = val ? 1 : 0 ; return count; }static DEVICE_ATTR_RW (enable) ;static ssize_t frequency_show (struct device *dev, struct device_attribute *attr, char *buf) { struct my_device_data *data = dev_get_drvdata(dev); return sprintf (buf, "%d\n" , data->frequency); }static ssize_t frequency_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct my_device_data *data = dev_get_drvdata(dev); int ret, val; ret = kstrtoint(buf, 10 , &val); if (ret) return ret; data->frequency = val; return count; }static DEVICE_ATTR_RW (frequency) ;static ssize_t version_show (struct device *dev, struct device_attribute *attr, char *buf) { struct my_device_data *data = dev_get_drvdata(dev); return sprintf (buf, "%s\n" , data->version); }static DEVICE_ATTR_RO (version) ;static struct attribute *my_device_attrs [] = { &dev_attr_enable.attr, &dev_attr_frequency.attr, &dev_attr_version.attr, NULL , };static struct attribute_group my_device_group = { .name = NULL , .attrs = my_device_attrs, };static struct attribute_group *my_device_groups [] = { &my_device_group, NULL , };static struct class my_class = { .name = "myclass" , .owner = THIS_MODULE, .dev_groups = my_device_groups, };static struct device *my_dev ;static dev_t my_devt;static int __init my_init (void ) { int ret; ret = alloc_chrdev_region(&my_devt, 0 , 1 , "mydevice" ); if (ret) { pr_err("alloc_chrdev_region failed\n" ); return ret; } ret = class_register(&my_class); if (ret) { pr_err("class_register failed\n" ); goto out_unregister_region; } my_dev = device_create(&my_class, NULL , my_devt, NULL , "mydevice" ); if (IS_ERR(my_dev)) { ret = PTR_ERR(my_dev); pr_err("device_create failed\n" ); goto out_unregister_class; } dev_set_drvdata(my_dev, &g_data); pr_info("mydevice: module loaded\n" ); pr_info("sysfs files: /sys/class/myclass/mydevice/{enable, frequency, version}\n" ); return 0 ; out_unregister_class: class_unregister(&my_class); out_unregister_region: unregister_chrdev_region(my_devt, 1 ); return ret; }static void __exit my_exit (void ) { device_destroy(&my_class, my_devt); class_unregister(&my_class); unregister_chrdev_region(my_devt, 1 ); pr_info("mydevice: module unloaded\n" ); } module_init(my_init); module_exit(my_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Your Name" ); MODULE_DESCRIPTION("Sysfs attribute group example with class/device" );
五、总结
sysfs 确实更适合实现简单的属性读写操作,但通过合理设计,也能支持一定程度的复杂交互。不过受限于其 “属性文件” 的设计理念,它在复杂操作场景下存在明显局限性。
sysfs 诞生的核心目标是将内核对象(如设备、驱动、总线)的属性以文件形式暴露给用户空间,方便用户查询和配置这些属性(如设备状态、硬件参数、驱动配置等)。
其设计理念决定了它天然适合:
简单的键值对式属性(如 brightness = 255、temperature = 35);
单次读写即可完成的操作(如开关设备、修改参数);
无需复杂状态交互的场景(如查询设备版本、查看资源占用)。
尽管能做一些扩展,sysfs 仍有难以突破的限制,使其不适合复杂交互:
没有 “命令” 概念,仅支持 “属性读写”
sysfs 本质是 “文件”,只能通过 read/write 操作,无法像 ioctl 那样传递 “命令 + 参数”(如 IOCTL_SET_MODE(MODE_FAST))。复杂操作需要拆解为多个属性,或在 store 中解析字符串命令(如 echo “set_mode fast” > control),可读性和效率都很差。
不支持双向流交互
无法像字符设备那样通过 read/write 实现 “持续数据流”(如串口收发数据)。sysfs 的 read 通常是 “一次性返回当前状态”,而非 “阻塞等待新数据”。
缺乏并发控制和事务性
多个属性的读写无法保证原子性(如同时修改 on_ms 和 off_ms 可能导致中间状态异常);
内核对 sysfs 的并发访问控制有限,复杂场景下需驱动自行实现锁机制,容易出错。
性能限制
sysfs 每次读写都会涉及用户态 / 内核态切换,且文件操作本身有开销,不适合高频交互(如每秒数千次的参数修改)。
简而言之:
sysfs 的核心定位是 “属性暴露与简单配置” ,最适合实现键值对式的属性读写。虽然通过技巧能支持一定的复杂操作(如结构化数据、触发式动作),但受限于其 “文件属性” 的设计理念,在命令交互、流数据、并发控制等场景下存在天然缺陷。
驱动开发中应遵循 “简单属性用 sysfs,复杂交互用字符设备 / 其他接口” 的原则,避免过度使用 sysfs 实现不适合的功能。