2. Linux设备模型基础
Linux设备模型并不是一个“看得见”的驱动,而是内核中一套用于统一描述设备、驱动、总线及其相互关系的抽象框架。它解决的核心问题是:当系统中存在成百上千个设备与驱动时,内核如何以统一方式管理它们的生命周期、层次关系、用户空间可见性以及热插拔事件。理解kobject、kset、ktype、sysfs与uevent,是后续跨版本适配工作的地基。
一、核心对象:kobject、kset与ktype
kobject是设备模型的最小引用计数单元,它本身不描述具体设备,而是为其他结构提供“嵌入能力”。kobject内嵌了引用计数(kref)、父指针、名称、所属kset以及sysfs目录项等字段。任何希望被设备模型管理的结构,通常都会内嵌一个kobject,例如struct device、struct driver、struct bus_type。
kset是kobject的集合,用于把同类对象组织在一起,并形成层次结构。kset自身也内嵌一个kobject,因此kset可以嵌套。它主要承担两件事:一是作为同类对象的容器,二是作为uevent事件的广播入口。典型例子包括/sys/devices下的设备层次、/sys/bus下的总线集合。
ktype(kobj_type)描述某一类kobject的“行为”,包括释放函数、sysfs属性组、默认属性等。它决定了当kobject引用计数归零时如何释放资源,以及该对象在sysfs中默认暴露哪些属性。ktype是对象“类型语义”的承载者,而kobject是“实例语义”的承载者。
二、sysfs:设备模型在用户空间的投影
sysfs是内核设备模型在用户空间的“文件系统投影”。它通常挂载在/sys,把kobject层次映射为目录,把属性映射为文件。用户通过读写这些文件,可以查看设备状态、修改驱动参数、触发总线行为。sysfs不是普通文件系统,它不存储数据,而是实时反映内核对象状态。
sysfs中常见的顶层目录包括:/sys/devices(真实设备层次)、/sys/bus(按总线组织)、/sys/class(按功能分类)、/sys/block(块设备)、/sys/module(模块信息)。跨版本适配时,sysfs路径和属性名是最容易发生变化的接口之一,必须重点核对。
# 查看某个设备的 sysfs 层次
ls -l /sys/devices/platform/serial8250/
# 典型输出
# tty/ driver -> ../../../bus/platform/drivers/serial8250
# uevent modalias power/ subsystem -> ../../../bus/platform
# 读取 uevent 内容
cat /sys/devices/platform/serial8250/uevent
# DRIVER=serial8250
# MODALIAS=platform:serial8250
三、uevent:热插拔与用户空间通知
uevent是内核向用户空间发送的设备事件机制。当设备被添加、移除、状态改变或移动时,内核会通过netlink套接字广播uevent消息。用户空间的udev、mdev或systemd-udevd接收后,执行设备节点创建、权限设置、固件加载、模块加载等动作。
uevent消息包含ACTION、DEVPATH、SUBSYSTEM、MAJOR、MINOR等字段。内核侧通过kobject_uevent()触发,kset则作为广播的汇聚点。跨版本适配中,uevent的触发时机、环境变量集合、netlink消息格式都可能变化,尤其是从旧内核迁移到新内核时,udev规则需要同步调整。
四、设备模型在系统中的定位
设备模型位于内核核心与具体驱动之间,是“承上启下”的中间层。向上,它向用户空间提供sysfs与uevent;向下,它统一管理device、driver、bus、class等对象。它不直接操作硬件,而是定义硬件如何被描述、匹配、绑定和释放。
| 对象/机制 | 核心职责 | 典型位置 | 跨版本适配关注点 |
|---|---|---|---|
| kobject | 引用计数、父子关系、名称 | 内嵌于device/driver/bus | 初始化接口、释放回调、引用计数规则 |
| kset | 同类对象集合、uevent广播 | /sys/bus、/sys/class | 注册方式、uevent_ops变化 |
| ktype | 类型行为、属性组、释放函数 | kobj_type定义处 | default_attrs、sysfs_ops签名 |
| sysfs | 用户空间属性视图 | /sys/devices、/sys/class | 路径、属性名、权限、二进制属性 |
| uevent | 热插拔事件通知 | netlink → udev/mdev | 环境变量、ACTION集合、触发时机 |
五、案例与误区复盘
案例:某平台从Linux 4.19迁移到5.10后,发现/sys/class/net/eth0/device符号链接指向发生变化,udev规则中依赖的DEVPATH匹配失败,导致网卡命名异常。排查后发现是设备模型中device与bus的挂接层次调整,uevent中的DEVPATH前缀改变。解决方式是改用SUBSYSTEM与ID_NET_NAME等更稳定的属性匹配。
常见误区:
- 把kobject当成“设备对象”本身,忽略它只是嵌入的引用计数单元。
- 认为sysfs文件可以像普通文件一样持久存储,实际上它每次读写都直接调用内核回调。
- 在驱动中手动创建sysfs属性却忘记在remove时清理,导致悬空引用。
- 依赖uevent的字段顺序或非标准环境变量,跨版本后规则失效。
- 混淆kset与ktype:kset管“集合”,ktype管“类型行为”,二者职责不同。
掌握kobject、kset、ktype、sysfs与uevent,就相当于拿到了设备模型的“地图”。后续无论是分析驱动绑定失败、排查sysfs属性缺失,还是适配不同内核版本的uevent行为,都可以从这张地图出发,逐层定位问题所在。