Linux驱动——向应用层提供sysf接口

一、用户操作驱动设备的方式

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) { /* 错误处理 */ }

// 写入数据(如控制 LED 亮度)
write(fd, "255", 3);

// 发送命令(如通过 ioctl 设置模式)
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 设备节点

一般用于设备节点的动态管理和命名规则自定义。

基于内核与用户态的异步通信机制,适用于事件通知或高频数据交互(如热插拔事件、网络配置、实时监控)。

核心特点:

  • 类似 socket 接口,支持双向通信和多播;
  • 驱动需注册 netlink_kernel_cfg 结构体,用户态通过 socket API 操作;
  • 效率高于 ioctl,适合传递结构化数据和异步事件。

示例(用户态框架):

1
2
3
// 创建 netlink 套接字
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 核心实现目录

  1. fs/sysfs/file.c
    • 作用:实现 sysfs 中文件的核心操作逻辑,是 sysfs 与 VFS(虚拟文件系统)对接的关键。
    • 核心内容:
      • 定义 sysfs 的 file_operations 结构体(sysfs_file_operations),包含 open/read/write/llseek 等通用文件操作;
      • 实现用户空间与内核空间的数据交互(如 sysfs_read/sysfs_write 函数,负责将用户读写转发给属性的 show/store 回调);
      • 管理文件的打开 / 关闭状态,处理并发访问。
  2. fs/sysfs/dir.c
    • 作用:管理 sysfs 的目录结构,负责目录的创建、删除和层级关系维护。
    • 核心内容:
      • 定义 sysfs_dirent 结构体(sysfs 中目录和文件的统一表示,包含名称、类型、父节点指针等);
      • 实现目录创建(sysfs_create_dir)、删除(sysfs_remove_dir)、重命名等操作;
      • 维护 sysfs 的目录树结构,确保节点间的层级关系正确。
  3. fs/sysfs/sysfs.h
    • 作用:sysfs 内部的头文件,定义核心数据结构和内部函数声明。
    • 核心内容:
      • 声明 struct sysfs_dirent、struct sysfs_ops 等核心结构体;
      • 定义 sysfs 内部使用的宏和辅助函数(如 sysfs_get/sysfs_put 用于引用计数管理)。
  4. fs/sysfs/attr.c
    • 作用:处理 sysfs 属性(attribute)的注册与管理,是驱动与 sysfs 交互的桥梁。
    • 核心内容:
      • 实现属性文件的创建(sysfs_create_file)、删除(sysfs_remove_file);
      • 处理属性组(attribute group)的批量注册(sysfs_create_group);
      • 关联属性的 show/store 回调函数与 sysfs 的文件操作。
  5. fs/sysfs/mount.c
    • 作用:负责 sysfs 文件系统的挂载初始化,是 sysfs 作为虚拟文件系统的入口。
    • 核心内容:
      • 定义 sysfs 的 file_system_type 结构体(sysfs_fs_type),向 VFS 注册 sysfs;
      • 实现 sysfs 的挂载函数(sysfs_mount),初始化根目录和超级块(super block)。

2.2 与其它子系统的交互文件

sysfs 并非独立存在,而是与内核其他子系统(如设备模型、总线、驱动)深度集成,这些子系统通过特定文件定义自己的 sysfs 属性:

  1. 设备模型相关(include/linux/device.h)
    • 定义 struct device_attribute(设备属性结构体)和 DEVICE_ATTR 宏,用于设备在 sysfs 中暴露属性;
    • 提供 device_create_file/device_remove_file 等函数,简化设备属性的注册。
  2. 总线与驱动相关(include/linux/device.h、include/linux/module.h)
    • 总线(如 platform 总线)和驱动通过 struct bus_attribute、struct driver_attribute 定义自己的 sysfs 属性;
    • 例如,/sys/bus/platform/drivers/ 下的驱动属性文件,由 driver_create_file 函数创建。
  3. 类设备相关(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
2
brightness
blink

读取:

1
cat /sys/bus/platform/devices/edge-led/brightness

写入:

1
2
echo 1 | sudo tee /sys/bus/platform/devices/edge-led/brightness
echo 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; // /sys/kernel/kobj_lab/value 目录
static int value; // 要暴露的数据

// 读函数:用户 cat 时调用
static ssize_t value_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%d\n", value);
}

// 写函数:用户 echo 时调用
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;
}

// 定义一个属性,名称为value,权限0644,并绑定上面的读写函数
static struct kobj_attribute value_attr =
__ATTR(value, 0644, value_show, value_store);

// 模块加载时执行
static int __init kobj_lab_init(void)
{
int ret;

// 在 /sys/kernel/ 下创建 kobj_lab 目录
kobj_lab = kobject_create_and_add("kobj_lab", kernel_kobj);
if (!kobj_lab)
return -ENOMEM;

// 在 kobj_lab 下创建 value 文件
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)
{
// 删除文件(可选,kobject_put 会清理子文件)
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 多属性注册

当有多个属性时,有两种方法:

  1. 多次调用 sysfs_create_file(最直接,但代码冗长)
  2. 使用 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;

// 三个属性的 show/store 函数
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, // 必须以 NULL 结尾
};

// 定义属性组
static struct attribute_group my_attr_group = {
.name = NULL, // 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
// 1. 定义属性数组,以 NULL 结尾
static struct attribute *my_attrs[] = {
&value1_attr.attr,
&value2_attr.attr,
&value3_attr.attr,
NULL,
};

// 2. 定义属性组
static struct attribute_group my_attr_group = {
.name = NULL, // NULL = 文件直接放在当前目录
.attrs = my_attrs,
};

// 3. 创建
sysfs_create_group(kobj_obj, &my_attr_group);

// 4. 删除
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", // 创建 config/ 子目录
.attrs = config_attrs,
};

// 第二组:状态属性
static struct attribute *status_attrs[] = {
&value3_attr.attr,
&value4_attr.attr,
NULL,
};

static struct attribute_group status_group = {
.name = "status", // 创建 status/ 子目录
.attrs = status_attrs,
};

// 创建
sysfs_create_group(kobj_lab, &config_group); // /sys/kernel/kobj_lab/config/value1
sysfs_create_group(kobj_lab, &status_group); // /sys/kernel/kobj_lab/status/value3

就会得到如下目录结构:

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>

// 1. 创建一个类(在 /sys/class/ 下出现)
struct class *my_class = class_create(THIS_MODULE, "myclass");
// 此时出现:/sys/class/myclass/

// 2. 创建设备(在 /sys/devices/ 和 /sys/class/myclass/ 下都出现)
struct device *my_dev = device_create(my_class, NULL, devt, NULL, "mydevice");
// 出现两个位置:
// - /sys/devices/.../mydevice/ (真实位置)
// - /sys/class/myclass/mydevice/ (符号链接)

// 3. 在设备目录下创建属性文件
device_create_file(my_dev, &dev_attr_xxx.attr);
// 结果路径:/sys/class/myclass/mydevice/xxx

这个方式下你不需要手动调用 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); // 需要实现 name_show 和 name_store

// 只读属性
DEVICE_ATTR_RO(name); // 只需实现 name_show

// 只写属性
DEVICE_ATTR_WO(name); // 只需实现 name_store

// 自定义权限
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
/* 1. 实现 show/store 函数 */
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;
}

/* 2. 用宏定义属性 */
static DEVICE_ATTR_RW(enable);

/* 3. 收集到属性数组 */
static struct attribute *my_dev_attrs[] = {
&dev_attr_enable.attr,
NULL,
};

/* 4. 定义属性组 */
static struct attribute_group my_dev_group = {
.attrs = my_dev_attrs,
};

/* 5. 属性组指针数组 */
static struct attribute_group *my_dev_groups[] = {
&my_dev_group,
NULL,
};

注册方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 方式 A:通过 class 注册(推荐,自动为每个设备创建)
struct class my_class = {
.name = "myclass",
.dev_groups = my_dev_groups, // ← 每个设备自动获得这些属性
};

// 方式 B:直接挂到 device 上(手动创建设备时)
struct device *dev;
dev->groups = my_dev_groups; // 在 device_register() 之前设置
device_register(dev);

// 方式 C:动态添加(不推荐,需要手动管理)
device_create_file(dev, &dev_attr_enable);

4.3.2 驱动属性定义

1
2
3
DRIVER_ATTR_RW(name);    // 需要实现 name_show 和 name_store
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);    // 需要实现 name_show 和 name_store
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, // ← 类注册时创建属性(注意是 .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;
};

/* 全局数据实例(实际驱动中可能从 dev 获取) */
static struct my_device_data g_data = {
.enable = 1,
.frequency = 100,
.version = "1.0.0",
};

/* ========== 1. 定义属性及 show/store 函数 ========== */

/* enable 属性:读写 */
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);

/* frequency 属性:读写 */
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);

/* version 属性:只读 */
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);

/* ========== 2. 收集属性到数组(必须以 NULL 结尾) ========== */
static struct attribute *my_device_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_frequency.attr,
&dev_attr_version.attr,
NULL,
};

/* ========== 3. 定义属性组 ========== */
/* name = NULL 表示文件直接放在设备目录下,不创建子目录 */
static struct attribute_group my_device_group = {
.name = NULL,
.attrs = my_device_attrs,
};

/* ========== 4. 属性组指针数组(供 .dev_groups 使用) ========== */
static struct attribute_group *my_device_groups[] = {
&my_device_group,
NULL,
};

/* ========== 5. 定义 Class ========== */
/* 关键:.dev_groups 指向属性组数组,内核会自动为每个设备创建这些属性 */
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;

/* 1. 分配设备号 */
ret = alloc_chrdev_region(&my_devt, 0, 1, "mydevice");
if (ret) {
pr_err("alloc_chrdev_region failed\n");
return ret;
}

/* 2. 注册 class */
ret = class_register(&my_class);
if (ret) {
pr_err("class_register failed\n");
goto out_unregister_region;
}

/* 3. 创建设备 */
/* 注意:这里不需要手动调用 device_create_file!
* 内核会在 device_create 内部自动遍历 dev_groups 创建所有属性
*/
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;
}

/* 4. 设置私有数据(供 show/store 使用) */
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 仍有难以突破的限制,使其不适合复杂交互:

  1. 没有 “命令” 概念,仅支持 “属性读写”
    • sysfs 本质是 “文件”,只能通过 read/write 操作,无法像 ioctl 那样传递 “命令 + 参数”(如 IOCTL_SET_MODE(MODE_FAST))。复杂操作需要拆解为多个属性,或在 store 中解析字符串命令(如 echo “set_mode fast” > control),可读性和效率都很差。
  2. 不支持双向流交互
    • 无法像字符设备那样通过 read/write 实现 “持续数据流”(如串口收发数据)。sysfs 的 read 通常是 “一次性返回当前状态”,而非 “阻塞等待新数据”。
  3. 缺乏并发控制和事务性
    • 多个属性的读写无法保证原子性(如同时修改 on_ms 和 off_ms 可能导致中间状态异常);
      内核对 sysfs 的并发访问控制有限,复杂场景下需驱动自行实现锁机制,容易出错。
  4. 性能限制
    • sysfs 每次读写都会涉及用户态 / 内核态切换,且文件操作本身有开销,不适合高频交互(如每秒数千次的参数修改)。

简而言之:

sysfs 的核心定位是 “属性暴露与简单配置”,最适合实现键值对式的属性读写。虽然通过技巧能支持一定的复杂操作(如结构化数据、触发式动作),但受限于其 “文件属性” 的设计理念,在命令交互、流数据、并发控制等场景下存在天然缺陷。

驱动开发中应遵循 “简单属性用 sysfs,复杂交互用字符设备 / 其他接口” 的原则,避免过度使用 sysfs 实现不适合的功能。


Linux驱动——向应用层提供sysf接口
http://example.com/2026/08/07/Linux驱动——向应用层提供sysf接口/
作者
Yu xin
发布于
2026年8月7日
许可协议