2. Linux设备模型基础

Linux设备模型并不是一个“看得见”的驱动,而是内核中一套用于统一描述设备、驱动、总线及其相互关系的抽象框架。它解决的核心问题是:当系统中存在成百上千个设备与驱动时,内核如何以统一方式管理它们的生命周期、层次关系、用户空间可见性以及热插拔事件。理解kobject、kset、ktype、sysfs与uevent,是后续跨版本适配工作的地基。

设备模型的目标可以概括为四件事:统一表示(用对象描述设备与驱动)、层次组织(父子与归属关系)、用户可见(通过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是“实例语义”的承载者。

Linux 设备模型核心对象与用户空间接口 kobject 引用计数 kref 父指针 parent 名称 name / kset kset 同类对象集合 内嵌 kobject uevent 广播入口 ktype release 释放函数 sysfs_ops 属性操作 default_attrs 属性组 归属 类型绑定 struct device / struct device_driver / struct bus_type 均内嵌 kobject,从而被设备模型统一管理 sysfs 用户空间视图 /sys/devices /sys/bus /sys/class 属性文件读写 → 内核对象 uevent 事件通道 add / remove / change / move netlink → udev / mdev kobject 提供实例与引用计数,kset 提供集合与层次,ktype 提供类型行为,sysfs 与 uevent 提供用户空间接口

二、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规则需要同步调整。

记忆口诀:kobject是“砖”,kset是“墙”,ktype是“图纸”,sysfs是“窗户”,uevent是“门铃”。砖与墙构成结构,图纸决定行为,窗户让用户看见,门铃通知用户处理。

四、设备模型在系统中的定位

设备模型位于内核核心与具体驱动之间,是“承上启下”的中间层。向上,它向用户空间提供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管“类型行为”,二者职责不同。
跨版本适配时,优先检查三类接口:sysfs属性名与路径、uevent环境变量集合、kobject/ktype初始化与释放API。这三处是设备模型中最容易发生不兼容变化的地方。

掌握kobject、kset、ktype、sysfs与uevent,就相当于拿到了设备模型的“地图”。后续无论是分析驱动绑定失败、排查sysfs属性缺失,还是适配不同内核版本的uevent行为,都可以从这张地图出发,逐层定位问题所在。