访问 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 字符串中将符号链接值用作或暴露为元素是应用程序中的一个 bug

    • 内核名称(sdatty0000:00:1f.2、...)

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

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

    • 子系统(blockttypci、...)

      • 简单的字符串,绝不是路径或链接

      • 通过读取 “subsystem” 链接并仅使用目标路径的最后一个元素来获取

    • 驱动(tg3ata_piixuhci_hcd

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

      • 它是通过读取 “driver” 链接并仅使用目标路径的最后一个元素来获取的

      • 没有 “driver” 链接的设备就是没有驱动;在子设备上下文中复制驱动值是应用程序中的一个 bug

    • attributes

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

      • 访问由指向另一个设备的符号链接(如 “device” 链接)所到达的属性是应用程序中的一个 bug

    其余所有内容都只是内核驱动核心的实现细节,不应假设其在不同的内核版本之间是稳定的。

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

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

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

    在 sysfs 中,只有一个可以检查层次结构的有效地方,即:/sys/devices. 按计划,所有设备目录最终都将位于该目录下的树中。

  • 按子系统分类

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

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

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

  • 块设备

    /sys/class/block/sys/subsystem/block 处转换后的块子系统将在同一级别包含磁盘和分区的链接,绝不会呈层次结构。假设块子系统只包含磁盘而在同一个扁平列表中不包含分区设备是应用程序中的一个 bug。

  • “device” 链接和 <subsystem>:<kernel name> 链接

    切勿依赖 “device” 链接。“device” 链接是对旧布局的一种变通方案,在旧布局中,类设备不像总线设备那样在 /sys/devices/ 中创建。如果设备目录的链接解析不以 /sys/devices/ 结尾,你可以使用 “device” 链接在 /sys/devices/ 中查找父设备。这是 “device” 链接唯一合法的使用方式;它绝不能作为任何路径中的元素出现。假设 /sys/devices/ 中的设备存在 “device” 链接是应用程序中的一个 bug。访问 /sys/class/net/eth0/device 是应用程序中的一个 bug。

    切勿依赖返回 /sys/class 目录的特定于类的链接。这些链接同样是对类设备未在 /sys/devices 中创建这一设计失误的变通方案。如果设备目录不包含子设备的目录,则可以使用这些链接在 /sys/class 中查找子设备。这是这些链接唯一合法的使用方式;它们绝不能作为任何路径中的元素出现。对于 /sys/devices 树中作为真正的子设备目录的设备,假设存在这些链接是应用程序中的一个 bug。

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

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

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

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

    避免依赖特定的错误代码。这可以最大程度地减少与内核内部错误处理实现的耦合。

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

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

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

    错误代码不会无故更改,如果错误代码的更改导致用户空间损坏,则会对其进行修复,或者将回滚引起问题的更改。

    然而,在给定的属性上下文中,如果没有版本属性的改变,用户空间应用程序可以期望属性文件的格式和内容保持一致。