Linux 统一设备模型

https://www.binss.me/blog/sysfs-udev-and-Linux-Unified-Device-Model/

引子 —— sysfs 诞生之前
一切皆文件,这是 Linux 的哲学之一。设备当然也不例外,它们往往被抽象成文件,存放在 /dev 目录下供用户进程进行操作。用户通过这些设备文件,可以实现对硬件进行相应的操作。而这些设备文件,需要由对应的设备文件系统来负责管理。

在 kernel 2.6 之前,完成这一使命的是 devfs。devfs 是 Linux 2.4 引入的一个虚拟的文件系统,挂载在 /dev 目录下。可以动态地为设备在 /dev 下创建或删除相应的设备文件,只生成存在设备的节点。

然而它存在以下缺点:

可分配的设备号数目 (major / minor) 受到限制
设备映射不确定,一个设备所对应的设备文件可能发生改变
设备名称在内核或模块中写死,违反了内核开发的原则
缺乏热插拔机制
随着 kernel 的发展,从 Linux 2.6 起,devfs 被 sysfs + udev 所取代。sysfs + udev 在设计哲学和现实中的易用性都比 devfs 更优,自此 sysfs + udev 的组合走上 mainline ,直至目前(4.9.40),依然作为 Linux 的设备管理手段。

sysfs
sysfs 是一个基于内存的虚拟的文件系统,由 kernel 提供,挂载到 /sys 目录下(用 mount 查看得到 sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)),负责以设备树的形式向 user space 提供直观的设备和驱动信息。

sysfs 以不同的视角展示当前系统接入的设备:

/sys/block 历史遗留问题,存放块设备,提供以设备名 (如 sda) 到 / sys/devices 的符号链接

/sys/bus 按总线类型分类,在某个总线目录之下可以找到连接该总线的设备的符号链接,指向 / sys/devices。

某个总线目录之下的 drivers 目录包含了该总线所需的所有驱动的符号链接

对应 kernel 中的 struct bus_type

/sys/class 按设备功能分类,如输入设备在 /sys/class/input 之下,图形设备在 /sys/class/graphics 之下,是指向 /sys/devices 目录下对应设备的符号链接

对应 kernel 中的 struct class

/sys/dev 按设备驱动程序分层(字符设备 / 块设备),提供以 major:minor 为名到 /sys/devices 的符号链接

对应 kernel 中的 struct device_driver

/sys/devices 包含所有被发现的注册在各种总线上的各种物理设备。

所有的物理设备都按其在总线上的拓扑结构来显示,除了 platform devices 和 system devices 。

platform devices 一般是挂在芯片内部高速或者低速总线上的各种控制器和外设,能被 CPU 直接寻址。

system devices 不是外设,他是芯片内部的核心结构,比如 CPU,timer 等,他们一般没有相关的 driver,但是会有一些体系结构相关的代码来配置他们

对应 kernel 中的 struct device

/sys/firmware 提供对固件的查询和操作接口(关于固件有专用于固件加载的一套 API)。

/sys/fs 描述当前加载的文件系统,提供文件系统和文件系统已挂载设备信息。

/sys/hypervisor 如果开启了 Xen,这个目录下会提供相关属性文件。

/sys/kernel 提供 kernel 所有可调整参数,但大多数可调整参数依然存放在 sysctl(/proc/sys/kernel)。

/sys/module 所有加载模块 (包括内联、编译进 kernel、外部的模块) 的信息,按模块类型分类。

/sys/power 电源选项,可用于控制整个机器的电源状态,如写入控制命令进行关机、重启等。

sysfs 支持多视角查看,通过符号链接,同样的信息可以出现在多个目录下。

以硬盘 sda 为例,既可以在块设备目录 /sys/block/ 下找到,又可以在所有设备目录 /sys/devices/pci0000:00/0000:00:10.0/host32/target32:0:0/ 下找到,

查看 sda1 设备目录下的内容:

$ ll /sys/block/sda/
drwxr-xr-x 11 root root 0 Feb 3 04:32 ./
drwxr-xr-x 3 root root 0 Feb 3 04:32 …/
-r–r--r-- 1 root root 4096 Feb 3 04:32 alignment_offset
lrwxrwxrwx 1 root root 0 Feb 3 04:32 bdi -> …/…/…/…/…/…/…/virtual/bdi/8:0/
-r–r--r-- 1 root root 4096 Feb 3 04:32 capability
-r–r--r-- 1 root root 4096 Feb 3 04:32 dev
lrwxrwxrwx 1 root root 0 Feb 3 04:32 device -> …/…/…/2:0:0:0/
-r–r--r-- 1 root root 4096 Feb 3 04:32 discard_alignment
-r–r--r-- 1 root root 4096 Feb 3 04:32 events
-r–r--r-- 1 root root 4096 Feb 3 04:32 events_async
-rw-r–r-- 1 root root 4096 Feb 3 04:32 events_poll_msecs
-r–r--r-- 1 root root 4096 Feb 3 04:32 ext_range
drwxr-xr-x 2 root root 0 Feb 3 04:32 holders/
-r–r--r-- 1 root root 4096 Feb 3 04:32 inflight
drwxr-xr-x 2 root root 0 Feb 3 04:32 integrity/
drwxr-xr-x 2 root root 0 Feb 3 04:32 power/
drwxr-xr-x 3 root root 0 Feb 3 04:32 queue/
-r–r--r-- 1 root root 4096 Feb 3 04:32 range
-r–r--r-- 1 root root 4096 Feb 3 04:32 removable
-r–r--r-- 1 root root 4096 Feb 3 04:32 ro
drwxr-xr-x 5 root root 0 Feb 3 04:32 sda1/
drwxr-xr-x 5 root root 0 Feb 3 04:32 sda2/
drwxr-xr-x 5 root root 0 Feb 3 04:32 sda5/
-r–r--r-- 1 root root 4096 Feb 3 04:32 size
drwxr-xr-x 2 root root 0 Feb 3 04:32 slaves/
-r–r--r-- 1 root root 4096 Feb 3 04:32 stat
lrwxrwxrwx 1 root root 0 Feb 3 04:32 subsystem -> …/…/…/…/…/…/…/…/class/block/
drwxr-xr-x 2 root root 0 Feb 3 04:32 trace/
-rw-r–r-- 1 root root 4096 Feb 3 04:32 uevent
目录以文件的形式提供了设备的信息,比如 dev 记录了主设备号和次设备号,size 记录了分区大小,uevent 存放了 uevent 的标识符等:

$ cat /sys/block/sda/size
41943040
统一设备模型
sysfs 的功能基于 Linux 的统一设备模型,其由以下结构构成:

kobject
统一设备模型中最基本的对象。

struct kobject {
const char *name; // 名称,将在 sysfs 中作为文件名
struct list_head entry; // 加入 kset 链表的结构
struct kobject *parent; // 父节点指针,构成树状结构
struct kset *kset; // 指向所属 kset
struct kobj_type *ktype; // 类型
struct kernfs_node *sd; // 指向所属 (sysfs) 目录项
struct kref kref; // 引用计数
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1; // 是否已经初始化
unsigned int state_in_sysfs:1; // 是否已在 sysfs 中显示
unsigned int state_add_uevent_sent:1; // 是否已经向 user space 发送 ADD uevent
unsigned int state_remove_uevent_sent:1; // 是否已经向 user space 发送 REMOVE uevent
unsigned int uevent_suppress:1; // 是否忽略上报(不上报 uevent)
};
其中, kobj_type 结构如下:

struct kobj_type {
void (*release)(struct kobject *kobj); // 析构函数,kobject 的引用计数为 0 时调用
const struct sysfs_ops *sysfs_ops; // 操作函数,当用户读取 sysfs 属性时调用 show(),写入 sysfs 属性时调用 store()
struct attribute **default_attrs; // 默认属性,体现为该 kobject 目录下的文件
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); // namespace 操作函数
const void *(*namespace)(struct kobject *kobj);
};
实际上这里实现的类似于对 kobject 的派生,包含不同 kobj_type 的 kobject 可以看做不同的子类。通过实现相同的函数来实现多态。在这的设计下,每一个内嵌 Kobject 的数据结构(如 kset、device、device_driver 等),都要实现自己的 kobj_type ,并实现其中的函数。

kobj_type 的定义会如实地在 sysfs 中反应,其中的属性 attribute 会以 attribute.name 为文件名在该目录下创建文件,对该文件进行读写会调用 sysfs_ops 中定义的 show() 和 store()

kset
kobject 的容器,维护了其包含的 kobject 链表,链表的最后一项执向 kset.kobj 。用于表示某一类型的 kobject 。

// kobject.h
struct kset {
struct list_head list; // kobject 链表头
spinlock_t list_lock; // 自旋锁,保障操作安全
struct kobject kobj; // 自身的 kobject
const struct kset_uevent_ops *uevent_ops; // uevent 操作函数集。kobject 发送 uevent 时会调用所属 kset 的 uevent_ops
};
注意和 kobj_type 的关联,kobject 会利用成员 kset 找到自已所属的 kset,设置自身的 ktype 为 kset.kobj.ktype 。当没有指定 kset 成员时,才会用 ktype 来建立关系。

此外,kobject 调用的是它所属 kset 的 uevent 操作函数来发送 uevent,如果 kobject 不属于任何 kset ,则无法发送 uevent。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值