2. Linux驱动基础回顾

量产问题定位的第一步,不是急着看日志,而是把“驱动到底以什么形态存在、通过什么路径被内核识别、又通过什么接口暴露给用户态”这条链路重新梳理清楚。Linux 驱动并不是一个孤立文件,它通常由内核模块加载机制、设备模型、设备树描述、用户态文件系统接口以及具体子系统框架共同构成。任何一环理解偏差,都会让后续排障方向跑偏。

一、内核模块:驱动的装载与生命周期

Linux 驱动最常见的存在形式是可加载内核模块(.ko)。模块通过 insmod 或 modprobe 加载,通过 rmmod 卸载。模块入口 module_init() 负责注册驱动、申请资源、创建设备节点或注册子系统;出口 module_exit() 负责反向释放。量产环境中最容易出问题的是:模块加载成功但 probe 失败、模块依赖顺序不对、模块参数写错、以及卸载时资源未释放导致 rmmod 卡死。

# 查看模块是否加载
lsmod | grep my_drv

# 查看模块信息与参数
modinfo my_drv.ko

# 带参数加载
insmod my_drv.ko debug=1 mode=2

# 查看内核日志
dmesg | tail -50

定位模块问题时,优先确认三件事:模块是否真的加载、init 是否返回成功、dmesg 中是否有明确的 -ENODEV、-EPROBE_DEFER、-EINVAL 等错误码。很多“驱动没起来”的问题,其实在模块入口阶段就已经失败。

二、字符设备与设备节点:用户态访问入口

字符设备是驱动向用户态暴露能力的基础方式之一。驱动通过 alloc_chrdev_region() 或 register_chrdev() 申请设备号,通过 cdev_add() 注册字符设备,再配合 class_create() 与 device_create() 自动生成 /dev 节点。用户态通过 open/read/write/ioctl 进入驱动实现的 file_operations 回调。

static const struct file_operations my_fops = {
    .owner   = THIS_MODULE,
    .open    = my_open,
    .read    = my_read,
    .write   = my_write,
    .unlocked_ioctl = my_ioctl,
    .release = my_release,
};

量产排障中,字符设备常见问题包括:设备号冲突、/dev 节点未生成、权限不对、ioctl 命令字不匹配、以及并发访问未加锁导致数据错乱。看到应用报 No such device 或 Invalid argument 时,应同时检查 /dev 节点、驱动注册日志和 ioctl 参数定义。

三、平台设备、设备树与设备模型

平台设备(platform device)用于描述那些不通过总线枚举、但需要与 CPU 直接连接的片上外设。传统写法是分别定义 platform_device 和 platform_driver,通过名称匹配;现代 ARM/Linux 系统则普遍使用设备树(Device Tree)描述硬件资源,驱动通过 of_match_table 与设备树节点匹配,匹配成功后进入 probe()。

my_device: my_device@10000000 {
    compatible = "vendor,my-device";
    reg = <0x10000000 0x1000>;
    interrupts = <0 42 4>;
    clocks = <&clk 12>;
    status = "okay";
};

设备树是量产问题的高发区:compatible 拼写错误、reg 地址与手册不符、interrupts 中断号或触发方式错误、status 为 disabled、时钟或电源未使能,都会导致驱动无法 probe 或 probe 后功能异常。定位时应先确认设备树是否被正确编译进 DTB,再确认内核启动日志中是否有对应节点解析记录。

四、sysfs、procfs 与常见驱动子系统

sysfs 与 procfs 是内核向用户态暴露信息的重要窗口。sysfs 通常挂载在 /sys,用于展示设备、驱动、总线、类等设备模型层次;procfs 挂载在 /proc,更多用于进程、内存、中断、模块等运行时信息。量产排障时,/sys/class/、/sys/devices/、/proc/interrupts、/proc/iomem、/proc/devices 都是高频入口。

接口/机制 典型路径或命令 量产排障用途
内核模块 lsmod、modinfo、dmesg 确认驱动是否加载、参数是否正确、init 是否失败
字符设备 /dev/*、/proc/devices 确认设备号、节点、权限与用户态访问入口
平台设备 /sys/bus/platform/devices/ 确认 platform device 是否注册、是否与 driver 匹配
设备树 /proc/device-tree/、dtc 确认 compatible、reg、interrupts、status 等属性
sysfs /sys/class/、/sys/devices/ 查看设备层次、驱动绑定状态、属性值
procfs /proc/interrupts、/proc/iomem 确认中断、内存映射、资源占用情况

常见驱动子系统包括:输入子系统(input)、I2C/SPI 子系统、GPIO 子系统、LED 子系统、IIO、V4L2、ALSA、网络设备、块设备、MTD、Regmap、Pinctrl、Clock、Regulator 等。量产问题往往不是“驱动完全没写”,而是驱动与子系统框架的对接点出错,例如 input 设备未上报事件、I2C 传输超时、GPIO 申请失败、时钟未使能、 regulator 电压不对。

排障顺序建议:先看 dmesg 确认模块与 probe 状态,再看 /sys 确认设备模型绑定关系,再看 /proc 确认中断与资源,最后回到设备树和驱动代码核对参数。不要一上来就改代码,先确认“内核到底看到了什么”。

常见误区:把 insmod 成功等同于驱动工作正常;只看应用日志不看内核日志;忽略 EPROBE_DEFER 导致的延迟 probe;设备树改了但没重新编译 DTB;以及 /dev 节点存在但实际未与驱动绑定。这些都会让定位方向严重偏离。

五、从基础到量产定位:一张图看清驱动链路

下面这张图把内核模块、字符设备、平台设备、设备树、sysfs/procfs 以及驱动子系统之间的关系串起来。量产排障时,可以沿着“加载 → 匹配 → probe → 注册 → 用户态访问”这条链路逐段确认,快速判断问题出在描述、匹配、资源还是接口层。

Linux 驱动基础链路:从加载到用户态访问 内核模块 .ko insmod / modprobe module_init / exit 设备树 DTB compatible / reg interrupts / clocks 平台设备/驱动 platform_driver of_match_table probe() 申请资源 注册子系统 字符设备 cdev_add / class_create /dev/my_dev open/read/write/ioctl sysfs /sys/class/ /sys/devices/ 设备-驱动绑定关系 procfs /proc/interrupts /proc/iomem /proc/devices 量产排障检查点 dmesg 错误码 → /sys 绑定状态 → /proc 资源占用 → 设备树属性 → 子系统注册结果 → 用户态接口 常见错误:-ENODEV、-EPROBE_DEFER、-EINVAL、-EBUSY、节点未生成、ioctl 不匹配

把这张链路图记住,后续遇到“驱动加载了但设备不工作”“应用打不开节点”“中断没触发”“I2C 超时”等问题时,就能按层定位,而不是在代码里盲目搜索。量产问题定位的核心,是先确认内核看到了什么、匹配了什么、注册了什么,再判断驱动逻辑是否正确。