sysfs - 用于导出内核对象的 _The_ 文件系统

Patrick Mochel <mochel@osdl.org>

Mike Murphy <mamurph@cs.clemson.edu>

修订日期:

2011 年 8 月 16 日

原始日期:

2003 年 1 月 10 日

它是什么

sysfs 是一个基于 RAM 的文件系统,最初基于 ramfs。它提供了一种将内核数据结构、它们的属性以及它们之间的链接导出到用户空间的方法。

sysfs 本质上与 kobject 基础结构绑定。请阅读 关于 kobject、kset 和 ktype 的所有您不想知道的事情 以获取有关 kobject 接口的更多信息。

使用 sysfs

如果定义了 CONFIG_SYSFS,则始终会编译 sysfs。您可以通过执行以下操作来访问它

mount -t sysfs sysfs /sys

目录创建

对于在系统中注册的每个 kobject,都会在 sysfs 中为其创建一个目录。该目录创建为 kobject 父目录的子目录,从而向用户空间表达内部对象层次结构。sysfs 中的顶层目录表示对象层次结构的公共祖先;即,对象所属的子系统。

sysfs 在内部存储一个指向 kobject 的指针,该 kobject 在与目录关联的 kernfs_node 对象中实现目录。过去,sysfs 一直使用此 kobject 指针直接对 kobject 进行引用计数,无论何时打开或关闭该文件。在当前的 sysfs 实现中,kobject 引用计数仅由函数 sysfs_schedule_callback() 直接修改。

属性

可以以文件系统中常规文件的形式导出 kobject 的属性。sysfs 将文件 I/O 操作转发到为属性定义的方法,从而提供了一种读取和写入内核属性的方法。

属性应该是 ASCII 文本文件,最好每个文件只有一个值。需要注意的是,每个文件仅包含一个值可能效率不高,因此可以接受表达相同类型的多个值数组。

混合类型、表达多行数据以及对数据进行花哨的格式化都非常不赞成。这样做可能会使您公开受辱,并且您的代码会在不通知的情况下被重写。

属性定义很简单

struct attribute {
        char                    *name;
        struct module           *owner;
        umode_t                 mode;
};


int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);

裸属性不包含读取或写入属性值的方法。鼓励子系统定义自己的属性结构和包装函数,以添加和删除特定对象类型的属性。

例如,驱动程序模型定义了 struct device_attribute,如下所示

struct device_attribute {
        struct attribute    attr;
        ssize_t (*show)(struct device *dev, struct device_attribute *attr,
                        char *buf);
        ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                        const char *buf, size_t count);
};

int device_create_file(struct device *, const struct device_attribute *);
void device_remove_file(struct device *, const struct device_attribute *);

它还定义了这个辅助函数来定义设备属性

#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

例如,声明

static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);

等同于执行

static struct device_attribute dev_attr_foo = {
        .attr = {
                .name = "foo",
                .mode = S_IWUSR | S_IRUGO,
        },
        .show = show_foo,
        .store = store_foo,
};

请注意,正如 include/linux/kernel.h 中所述的“OTHER_WRITABLE?通常被认为是一个坏主意”,因此尝试为所有人设置一个可写的 sysfs 文件将会失败,并恢复为“其他”用户的 RO 模式。

对于常见的情况,sysfs.h 提供了便捷的宏,以简化属性的定义,并使代码更加简洁易读。上述情况可以缩短为

static struct device_attribute dev_attr_foo = __ATTR_RW(foo);

可用于定义包装函数的帮助程序列表如下

__ATTR_RO(name)

假定默认的 name_show 和模式 0444

__ATTR_WO(name)

假定只有 name_store,并且限制为模式 0200,即只有 root 写入权限。

__ATTR_RO_MODE(name, mode)

用于更严格的 RO 访问;当前唯一用例是 EFI 系统资源表(请参阅 drivers/firmware/efi/esrt.c)

__ATTR_RW(name)

假定默认的 name_show、name_store 并将模式设置为 0644。

__ATTR_NULL

将名称设置为 NULL,并用作列表结束指示符(请参阅:kernel/workqueue.c)

子系统特定的回调

当子系统定义新的属性类型时,它必须实现一组 sysfs 操作,以便将读取和写入调用转发到属性所有者的 show 和 store 方法

struct sysfs_ops {
        ssize_t (*show)(struct kobject *, struct attribute *, char *);
        ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

[ 子系统应该已经定义了一个 struct kobj_type 作为此类型的描述符,sysfs_ops 指针存储在该描述符中。有关更多信息,请参阅 kobject 文档。]

当读取或写入文件时,sysfs 会调用该类型的相应方法。然后,该方法会将通用的 struct kobject 和 struct attribute 指针转换为适当的指针类型,并调用关联的方法。

为了说明

#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)

static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
                            char *buf)
{
        struct device_attribute *dev_attr = to_dev_attr(attr);
        struct device *dev = kobj_to_dev(kobj);
        ssize_t ret = -EIO;

        if (dev_attr->show)
                ret = dev_attr->show(dev, dev_attr, buf);
        if (ret >= (ssize_t)PAGE_SIZE) {
                printk("dev_attr_show: %pS returned bad count\n",
                                dev_attr->show);
        }
        return ret;
}

读取/写入属性数据

要读取或写入属性,在声明属性时必须指定 show() 或 store() 方法。方法类型应该与为设备属性定义的方法类型一样简单

ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                const char *buf, size_t count);

也就是说,它们应该只接受一个对象、一个属性和一个缓冲区作为参数。

sysfs 分配一个大小为 (PAGE_SIZE) 的缓冲区并将其传递给该方法。sysfs 会在每次读取或写入时精确地调用该方法一次。这会在方法实现上强制执行以下行为

  • 在 read(2) 上,show() 方法应该填充整个缓冲区。请记住,属性应该只导出一个值或一个相似的值数组,因此这不应该那么昂贵。

    这允许用户空间随意地对整个文件执行部分读取和正向查找。如果用户空间查找回零或执行偏移量为“0”的 pread(2),则会再次调用 show() 方法,并重新武装以填充缓冲区。

  • 在 write(2) 上,sysfs 希望在第一次写入期间传递整个缓冲区。然后,sysfs 将整个缓冲区传递给 store() 方法。在存储的数据之后添加一个终止 null。这使得像 sysfs_streq() 这样的函数可以安全使用。

    在写入 sysfs 文件时,用户空间进程应首先读取整个文件,修改它希望更改的值,然后将整个缓冲区写回。

    属性方法实现在读取和写入值时应使用相同的缓冲区。

其他注意事项

  • 写入会导致 show() 方法重新武装,而与当前文件位置无关。

  • 缓冲区的长度始终为 PAGE_SIZE 字节。在 x86 上,此值为 4096。

  • show() 方法应返回打印到缓冲区中的字节数。

  • show() 在格式化要返回给用户空间的值时,应仅使用 sysfs_emit()sysfs_emit_at()

  • store() 应返回缓冲区中使用的字节数。如果已使用整个缓冲区,则只需返回 count 参数。

  • show() 或 store() 始终可以返回错误。如果出现错误的值,请务必返回错误。

  • 通过 sysfs 引用计数其嵌入式对象,传递给该方法的对象将在内存中被固定。但是,该对象所代表的物理实体(例如,设备)可能不存在。如果需要,请务必有一种方法来检查这一点。

设备属性的一个非常简单(且天真)的实现是

static ssize_t show_name(struct device *dev, struct device_attribute *attr,
                        char *buf)
{
        return sysfs_emit(buf, "%s\n", dev->name);
}

static ssize_t store_name(struct device *dev, struct device_attribute *attr,
                        const char *buf, size_t count)
{
        snprintf(dev->name, sizeof(dev->name), "%.*s",
                (int)min(count, sizeof(dev->name) - 1), buf);
        return count;
}

static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);

(请注意,实际的实现不允许用户空间设置设备的名称。)

顶层目录布局

sysfs 目录排列公开了内核数据结构的关系。

顶层 sysfs 目录如下所示

block/
bus/
class/
dev/
devices/
firmware/
fs/
hypervisor/
kernel/
module/
net/
power/

devices/ 包含设备树的文件系统表示。它直接映射到内部内核设备树,该设备树是 struct device 的层次结构。

bus/ 包含内核中各种总线类型的扁平目录布局。每个总线的目录包含两个子目录

devices/
drivers/

devices/ 包含系统中发现的每个设备的符号链接,这些链接指向 root/ 下设备的目录。

drivers/ 包含为该特定总线上的设备加载的每个设备驱动程序的目录(这假设驱动程序不跨越多个总线类型)。

fs/ 目录包含一些文件系统的目录。目前,每个想要导出属性的文件系统都必须在 fs/ 下创建自己的层级结构(例如,请参阅 ./fuse.rst)。

module/ 目录包含所有已加载系统模块(包括内置模块和可加载模块)的参数值和状态信息。

dev/ 目录包含两个子目录:char/ 和 block/。在这两个子目录中,都有名为 <major>:<minor> 的符号链接。这些符号链接指向给定设备的 sysfs 目录。/sys/dev 提供了一种从 stat(2) 操作的结果中快速查找设备 sysfs 接口的方法。

有关驱动程序模型特定功能的更多信息,请参阅 Documentation/driver-api/driver-model/。

待办事项:完成此部分。

当前接口

sysfs 中目前存在以下接口层。

devices (include/linux/device.h)

结构

struct device_attribute {
        struct attribute    attr;
        ssize_t (*show)(struct device *dev, struct device_attribute *attr,
                        char *buf);
        ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                        const char *buf, size_t count);
};

声明

DEVICE_ATTR(_name, _mode, _show, _store);

创建/删除

int device_create_file(struct device *dev, const struct device_attribute * attr);
void device_remove_file(struct device *dev, const struct device_attribute * attr);

总线驱动程序 (include/linux/device.h)

结构

struct bus_attribute {
        struct attribute        attr;
        ssize_t (*show)(const struct bus_type *, char * buf);
        ssize_t (*store)(const struct bus_type *, const char * buf, size_t count);
};

声明

static BUS_ATTR_RW(name);
static BUS_ATTR_RO(name);
static BUS_ATTR_WO(name);

创建/删除

int bus_create_file(struct bus_type *, struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);

设备驱动程序 (include/linux/device.h)

结构

struct driver_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct device_driver *, char * buf);
        ssize_t (*store)(struct device_driver *, const char * buf,
                        size_t count);
};

声明

DRIVER_ATTR_RO(_name)
DRIVER_ATTR_RW(_name)

创建/删除

int driver_create_file(struct device_driver *, const struct driver_attribute *);
void driver_remove_file(struct device_driver *, const struct driver_attribute *);

文档

sysfs 目录结构和每个目录中的属性定义了内核和用户空间之间的 ABI(应用程序二进制接口)。与任何 ABI 一样,此 ABI 必须稳定并妥善记录。所有新的 sysfs 属性必须记录在 Documentation/ABI 中。有关更多信息,另请参阅 Documentation/ABI/README。