1. 什么是 sysfs伪文件系统?
sysfs
是 Linux 内核提供的 伪文件系统,用于向用户空间暴露内核对象的信息和控制接口。它是 procfs
的补充,主要用于管理 设备、驱动、内核子系统 等信息,使用户可以通过文件操作(如用户空间中的open函数、write函数、read函数、 cat
命令、echo
命令)与内核交互【对文件的操作其实直接转换为调用相应的内核函数】,而无需编写内核代码。
- 挂载位置:通常挂载在
/sys
目录下。 - 作用:
- 提供系统信息(如 CPU、设备、驱动程序、总线)。
- 提供设备控制接口(如 GPIO、PWM、I2C、SPI 等)。
- 支持热插拔设备管理(如 USB、PCI、SATA 设备)。
sysfs伪文件系统实际上和设备文件很类似,它们都是用户空间与内核空间交互的媒介,不同的是设备文件是内核空间中驱动程序中的概念,而sysfs伪文件系统则是内核空间中别的需要向用户空间暴露的内核对象的信息和控制接口。所以它的使用实际上和设备文件很类似。
在终端中使用sysfs伪文件系统时,表面上我们是在用相关函数或cat
、echo
命令进行读或写相关的文件,实际上会转换为对某个内核函数的调用。具体的例子见本篇博文后面列出的“一个实际的例子分析”
而在程序代码中使用sysfs伪文件系统时,就和操作设备文件差不多。具体的例子见 https://blog.csdn.net/wenhao_ir/article/details/145459006【搜索“led_init(void)”】
2. sysfs 的目录结构
sysfs 目录下的结构主要分为以下几部分:
/sys/├── block/ # 块设备(如磁盘、分区)├── bus/ # 设备总线(如 i2c, usb, spi, pci, platform)├── class/ # 设备类(如 gpio, net, tty, input)├── devices/ # 设备层次结构├── firmware/ # 固件接口├── kernel/ # 内核参数├── module/ # 加载的内核模块└── power/ # 电源管理
3. sysfs 目录详解
(1)/sys/class/
-
组织 设备 按类别分类(如 GPIO、网络、输入设备)。
-
例子:
ls /sys/class/
输出:
gpio/ net/ input/ sound/ thermal/ tty/ usb_device/
-
GPIO 设备(使用 sysfs 方式操作 GPIO)
echo 131 > /sys/class/gpio/export # 申请 GPIO 131 echo out > /sys/class/gpio/gpio131/direction # 设置方向 echo 1 > /sys/class/gpio/gpio131/value # 设为高电平
我后面会对这三句命令作详细解释,其实就是以这三句命令活生生地展现 sysfs 伪文件系统是怎么实现与内核的交互的。
-
网卡信息
ls /sys/class/net/ eth0 lo wlan0
cat /sys/class/net/eth0/address
# 查看 MAC 地址cat /sys/class/net/eth0/statistics/rx_bytes
# 接收的字节数
(2)/sys/bus/
-
组织 设备按总线分类(如 PCI、USB、I2C、SPI)。
-
例子:
ls /sys/bus/
输出:
i2c/ pci/ platform/ scsi/ spi/ usb/
/sys/bus/i2c/devices/
# I2C 设备/sys/bus/usb/devices/
# USB 设备/sys/bus/pci/devices/
# PCI 设备
-
示例:列出所有 USB 设备
ls /sys/bus/usb/devices/
(3)/sys/devices/
-
按 设备拓扑结构 组织设备信息(如
/sys/devices/pci0000:00/0000:00:1f.2/
)。 -
例子:
ls /sys/devices/
system/ virtual/ pci0000:00/
-
查看 CPU 信息
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
(4)/sys/module/
- 显示 已加载的内核模块 及其参数。
- 例子:
ls /sys/module/
cat /sys/module/gpio/parameters/
cat /sys/module/usbcore/parameters/autosuspend
(5)/sys/kernel/
-
提供与 内核相关的配置信息。
-
例子:
ls /sys/kernel/
debug/ security/ tracing/
-
查看系统调试信息
cat /sys/kernel/debug/gpio
4. sysfs 的工作机制
- sysfs 由
kobject
和kset
组成:kobject
代表 内核对象(如设备、驱动)。kset
是kobject
的集合(如devices
、bus
)。
- 设备驱动注册时,sysfs 自动创建目录和文件。
- 用户空间可以通过
cat
、echo
直接访问内核数据。
示例:
设备驱动代码:
static struct class *my_class;
my_class = class_create(THIS_MODULE, "my_device");
device_create(my_class, NULL, MKDEV(200, 0), NULL, "mydev0");
sysfs 生成的目录:
/sys/class/my_device/mydev0/
5. sysfs 的优缺点
优点 | 缺点 |
---|---|
允许用户空间与内核交互 | 只能操作文本,效率低 |
结构清晰,按类别组织 | 不适用于高频操作 |
可用于设备驱动调试 | 已被 libgpiod 等替代 |
6. sysfs 与 udev、procfs 的区别
文件系统 | 作用 | 特点 |
---|---|---|
sysfs (/sys/ ) | 设备、驱动、总线信息 | 设备管理,与 kobject 相关 |
procfs (/proc/ ) | 进程、内核信息 | 进程管理,虚拟文件 |
udev (/dev/ ) | 设备文件 | 动态创建 /dev/ 设备节点 |
7. 结论
- sysfs 是 Linux 设备模型的核心部分,用于暴露设备和驱动信息。
- 用户空间可以直接
cat
读取信息,echo
进行控制,简化了驱动开发。 - sysfs 已逐渐被
libgpiod
、udev
替代,但仍然广泛使用,特别是在嵌入式系统中。
8.一个实际的例子分析
我们可以通过下面的三条命令实现将编号为131的GPIO口(GPIO5_IO03)的值设为逻辑1值,如果GPIO没有被标记为低电平有效,则逻辑1值就是物理上的高电平,否则就是物理上的低电平,在这里,我实测了一下,GPIO5_IO03
并没有被标记为低电平有效。即运行完成后在下面的原理图基础上,灯灭了。
echo 131 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio131/direction
echo 1 > /sys/class/gpio/gpio131/value
这三条命令是通过 sysfs 接口 操作 GPIO 口 131,本质上是用户空间通过 echo
命令向 sysfs 伪文件系统写入数据,触发内核的 GPIO 相关操作【实际上就是去调用内核中的相关GPIO子系统的函数】。
在看下面的分析前可以参考下博文 https://blog.csdn.net/wenhao_ir/article/details/145119224 是如何利用GPIO子系统实现LED灯的驱动的,实际上这篇博文中调用的函数也就是下面三条echo
命令触发的内核函数。
1. echo
命令与 sysfs GPIO 实现的关系
echo
命令的作用是 向文件写入内容,但当目标文件是 sysfs 文件时,写入的值会被内核的 GPIO 处理函数拦截,并执行相应的 GPIO 操作。
下面具体分析你的 echo
命令是如何作用到 GPIO 上的:
(1)导出 GPIO
echo 131 > /sys/class/gpio/export
-
作用:请求使用 GPIO 131,让内核创建
/sys/class/gpio/gpio131/
目录。 -
sysfs 机制:
echo 131 > /sys/class/gpio/export
相当于向export
文件 写入131
。- 内核中的
export_store()
处理该写入,并调用gpio_request(131, NULL)
申请 GPIO 资源。 - 申请成功后,内核会创建
/sys/class/gpio/gpio131/
目录,用于控制 GPIO 131。
-
相关的内核代码(在
drivers/gpio/gpiolib-sysfs.c
中):static ssize_t export_store(struct class *class,struct class_attribute *attr,const char *buf, size_t count) {int gpio;int ret;ret = kstrtoint(buf, 0, &gpio);if (ret)return ret;ret = gpio_request(gpio, NULL);if (ret)return ret;return count; }
(2)设置 GPIO 方向
echo out > /sys/class/gpio/gpio131/direction
-
作用:设置 GPIO 131 为 输出模式。
-
sysfs 机制:
echo out > /sys/class/gpio/gpio131/direction
相当于向direction
文件 写入字符串"out"
。gpio_direction_output(131, 0)
被调用,将 GPIO 131 配置为 输出模式(初始值 0)。- 内核 GPIO 驱动最终会调用
gpio_chip->direction_output()
这个函数,该函数是具体的 GPIO 控制器驱动 提供的,执行底层的寄存器操作,配置 GPIO 方向。
-
相关的内核代码(在
drivers/gpio/gpiolib-sysfs.c
中):static ssize_t direction_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) {struct gpio_desc *desc = dev_get_drvdata(dev);if (!strncmp(buf, "out", 3))gpiod_direction_output(desc, 0);elsegpiod_direction_input(desc);return count; }
(3)设置 GPIO 输出值
echo 1 > /sys/class/gpio/gpio131/value
-
作用:设置 GPIO 131 输出 高电平(1)。
-
sysfs 机制:
echo 1 > /sys/class/gpio/gpio131/value
向value
文件 写入"1"
。gpio_set_value(131, 1)
被调用,更新 GPIO 131 的输出电平。- 底层驱动(
gpio_chip->set()
)负责具体的寄存器操作,将 GPIO 置高。
-
相关的内核代码(在
drivers/gpio/gpiolib-sysfs.c
中):static ssize_t value_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) {struct gpio_desc *desc = dev_get_drvdata(dev);int value;if (buf[0] == '0')value = 0;elsevalue = 1;gpiod_set_value(desc, value);return count; }
2. sysfs GPIO 实现的局限
虽然 sysfs 提供了一种 简单的方式 来访问 GPIO,但它有以下局限性:
- 效率低:sysfs 通过文件操作控制 GPIO,比
ioctl
或mmap
慢。 - 无法高效处理中断:sysfs 方式不适合监听 GPIO 变化(如按键)。
- 已被
libgpiod
取代:新版本的 Linux 内核推荐使用libgpiod
(基于ioctl
的 GPIO 操作)。
3. 结论
🔹 sysfs 方式是 Linux 早期的 GPIO 控制方法,echo
命令本质上是向 sysfs 伪文件系统写入数据,触发内核调用 GPIO 操作函数。
🔹 每个 echo
命令最终都会调用 gpiolib
提供的 API,如 gpio_request()
、gpio_direction_output()
和 gpio_set_value()
,再由底层 GPIO 驱动完成实际的硬件操作。
🔹 sysfs 方式简单直观,但不适用于高效 GPIO 访问,现代 Linux 推荐使用 libgpiod
代替 sysfs GPIO
。(实际上在Linux 5.10以上版本中已经把sysfs
这种对GPIO的操作方式废弃了)
比如上面的三条命令等效于下面这条gpiod API
的命令:
gpioset gpiochip0 131=1