关于如何访问 sysfs 中信息的规则

内核导出的 sysfs 导出内部内核实现细节,并依赖于内部内核结构和布局。内核开发人员一致认为,Linux 内核不提供稳定的内部 API。因此,sysfs 接口的某些方面可能在内核版本之间不稳定。

为了最大限度地降低因新内核版本而破坏 sysfs 用户(在大多数情况下是低级用户空间应用程序)的风险,sysfs 用户必须遵循一些规则,以尽可能抽象的方式访问此文件系统。当前的 udev 和 HAL 程序已经实现了这一点,并鼓励用户尽可能接入这些程序提供的抽象,而不是直接访问 sysfs。

但是,如果您确实想要或需要直接访问 sysfs,请遵循以下规则,然后您的程序应该可以与未来版本的 sysfs 接口一起使用。

  • 不要使用 libsysfs

    它对 sysfs 做出了不正确的假设。其 API 不提供任何抽象,它在其自己的 API 中公开了所有内核驱动程序核心实现细节。因此,它并不比读取目录和自己打开文件更好。此外,它没有得到积极维护,无法反映当前的内核开发。为 sysfs 提供稳定接口的目标失败了;它引起的问题多于它解决的问题。它违反了本文档中的许多规则。

  • sysfs 始终位于 /sys

    解析 /proc/mounts 是在浪费时间。其他挂载点是您不应尝试解决的系统配置错误。对于测试用例,可能支持使用 SYSFS_PATH 环境变量来覆盖应用程序的行为,但切勿尝试搜索 sysfs。如果您不是早期引导脚本,则切勿尝试挂载它。

  • 设备只是“设备”

    在用户空间中,没有什么可以依赖的类、总线、物理设备、接口之类的东西。一切都只是简单的“设备”。类、总线、物理等类型只是内核实现细节,在 sysfs 中查找设备的应用程序不应期望这些细节。

    设备的属性是

    • devpath (/devices/pci0000:00/0000:00:1d.1/usb2/2-2/2-2:1.0)

      • 与内核在设备创建和删除时发送的事件中的 DEVPATH 值相同

      • 此时设备唯一的键

      • 不带前导 /sys 的设备目录的内核路径,并且始终以斜杠开头

      • devpath 的所有元素都必须是真实的目录。指向 /sys/devices 的符号链接必须始终解析为其真实目标,并且必须使用目标路径来访问设备。这样,设备的 devpath 与事件发生时使用的内核的 devpath 相匹配。

      • 在 devpath 字符串中使用或公开符号链接值是应用程序中的错误

    • 内核名称 (sda, tty, 0000:00:1f.2, ...)

      • 目录名称,与 devpath 的最后一个元素相同

      • 应用程序需要处理名称中的空格和 ! 之类的字符

    • 子系统 (block, tty, pci, ...)

      • 简单字符串,永远不是路径或链接

      • 通过读取“子系统”链接并仅使用目标路径的最后一个元素来检索

    • 驱动程序 (tg3, ata_piix, uhci_hcd)

      • 一个简单的字符串,可能包含空格,永远不是路径或链接

      • 通过读取“驱动程序”链接并仅使用目标路径的最后一个元素来检索

      • 没有“驱动程序”链接的设备只是没有驱动程序;在子设备上下文中复制驱动程序值是应用程序中的错误

    • 属性

      • 设备目录中的文件或同一设备目录的子目录下的文件

      • 访问通过指向另一个设备的符号链接(如“设备”链接)到达的属性是应用程序中的错误

    其他一切都只是内核驱动程序核心实现细节,不应假定在内核版本之间是稳定的。

  • 父设备的属性永远不属于子设备。

    始终查看父设备本身以确定设备上下文属性。如果设备 eth0sda 没有“驱动程序”链接,则此设备没有驱动程序。它的值为空。切勿将父设备的任何属性复制到子设备中。父设备属性可能会动态更改,而不会通知子设备。

  • 单个设备树中的层次结构

    在 sysfs 中,只有一个有效的位置可以检查层次结构,它位于以下位置之下:/sys/devices. 计划将所有设备目录最终都放在此目录下的树中。

  • 按子系统分类

    目前有三个位置用于对设备进行分类:/sys/block, /sys/class/sys/bus. 计划这些位置本身不包含任何设备目录,而仅包含指向统一的 /sys/devices 树的符号链接的平面列表。这三个位置在如何访问设备信息方面有完全不同的规则。计划将所有三个分类目录合并到 /sys/subsystem 中的一个位置,遵循总线目录的布局。所有总线和类,包括转换后的块子系统,都将显示在那里。属于子系统的设备将在 /sys/subsystem/<name>/devices 的“设备”目录中创建符号链接,

    如果 /sys/subsystem 存在,则可以忽略 /sys/bus/sys/class/sys/block。如果它不存在,则始终必须扫描所有三个位置,因为内核可以自由地将子系统从一个位置移动到另一个位置,只要设备仍然可以通过相同的子系统名称访问。

    假设 /sys/class/<subsystem>/sys/bus/<subsystem>,或 /sys/block/sys/class/block 不可互换是应用程序中的错误。

  • 转换后的块子系统(位于 /sys/class/block/sys/subsystem/block 中)将在同一级别包含磁盘和分区的链接,永远不会形成层次结构。假设块子系统仅包含磁盘而不包含同一平面列表中的分区设备是应用程序中的错误。

  • “device”链接和 <子系统>:<内核名称> 链接

    永远不要依赖 “device” 链接。“device” 链接是旧布局的权宜之计,在旧布局中,类设备不像总线设备那样在 /sys/devices/ 中创建。如果设备目录的链接解析没有以 /sys/devices/ 结尾,则可以使用 “device” 链接在 /sys/devices/ 中查找父设备。这是 “device” 链接的唯一有效用途;它绝不能作为任何路径中的元素出现。假设 /sys/devices/ 中的设备存在 “device” 链接是应用程序中的错误。访问 /sys/class/net/eth0/device 是应用程序中的错误。

    永远不要依赖返回到 /sys/class 目录的类特定链接。这些链接也是一个设计错误的权宜之计,即类设备没有在 /sys/devices 中创建。如果设备目录不包含子设备的目录,则可以使用这些链接在 /sys/class 中查找子设备。这是这些链接的唯一有效用途;它们绝不能作为任何路径中的元素出现。假设在 /sys/devices 树中是真实子设备目录的设备存在这些链接是应用程序中的错误。

    计划在所有类设备目录都位于 /sys/devices 中时删除所有这些链接。

  • 设备沿设备链的位置可能会改变。

    永远不要依赖于 devpath 中特定父设备的位置,或父设备的链。内核可以自由地将设备插入链中。您必须始终通过其子系统值请求您正在寻找的父设备。您需要向上遍历链,直到找到与预期子系统匹配的设备。依赖于父设备的特定位置或使用 ../ 暴露相对路径以访问父链是应用程序中的错误。

  • 在读取和写入 sysfs 设备属性文件时,尽可能避免依赖

    特定的错误代码。 这可以最大限度地减少与内核中错误处理实现的耦合。

    通常,读取或写入 sysfs 设备属性的失败应尽可能传播错误。 常见错误包括但不限于

    -EIO:不支持读取或存储操作,如果读取或存储指针为 NULL,通常由 sysfs 系统本身返回。

    -ENXIO:读取或存储操作失败

    错误代码不会无故更改,如果错误代码的更改导致用户空间中断,则会修复或撤销有问题的更改。

    但是,在给定属性的上下文中,如果不存在版本属性更改,用户空间应用程序可以期望属性文件的格式和内容保持一致。