字符设备驱动故障处理案例
案例 1:设备节点未自动创建
现象:insmod 成功,但 /dev/ 下没有设备节点。
排查步骤:
bash
# 1. 确认设备号已分配
cat /proc/devices | grep mydev
# 2. 检查 sysfs 是否有设备
ls /sys/class/myclass/
# 3. 查看 udev 日志
udevadm monitor --udev
# 重新加载模块,观察是否有事件
# 4. 手动触发 udev
udevadm trigger常见原因:
- 未调用
device_create(),只调用了cdev_add() class_create()失败但未检查返回值- udev 规则冲突
解决方法:
c
/* 确保完整的设备创建流程 */
mydev_class = class_create(CLASS_NAME);
if (IS_ERR(mydev_class)) {
pr_err("class_create failed\n");
return PTR_ERR(mydev_class);
}
device = device_create(mydev_class, NULL, devt, NULL, "mydev");
if (IS_ERR(device)) {
pr_err("device_create failed\n");
class_destroy(mydev_class);
return PTR_ERR(device);
}案例 2:copy_to_user 导致 Oops
现象:read() 时内核崩溃,dmesg 显示 Unable to handle kernel paging request。
错误代码:
c
/* ❌ 直接写用户空间指针 */
static ssize_t mydev_read(struct file *filp, char __user *buf, ...)
{
memcpy(buf, priv->data, len); /* 崩溃! */
return len;
}正确代码:
c
/* ✅ 使用 copy_to_user */
if (copy_to_user(buf, priv->data, len))
return -EFAULT;
return len;调试技巧:
bash
# 查看崩溃地址
dmesg | grep "PC is at"
# 反汇编定位代码行
addr2line -e mydriver.ko <地址>案例 3:设备忙无法卸载
现象:rmmod 报错 Device or resource busy。
排查:
bash
# 查看模块引用计数
lsmod | grep mydev
# 查找打开设备的进程
lsof | grep /dev/mydev
fuser -v /dev/mydev
# 强制杀死进程
fuser -k /dev/mydev常见原因:
- 用户空间进程未关闭文件描述符
- 驱动中
module_get()未配对module_put() - 循环引用(如 sysfs 文件未删除)
案例 4:ioctl 返回 ENOTTY
现象:用户空间调用 ioctl() 返回 -1,errno = ENOTTY。
排查:
c
/* 在驱动中添加调试 */
static long mydev_ioctl(struct file *filp, unsigned int cmd, ...)
{
pr_info("ioctl: cmd=0x%08x, type=%c, nr=%d\n",
cmd, _IOC_TYPE(cmd), _IOC_NR(cmd));
/* ... */
}常见原因:
- 用户空间与驱动的 ioctl 命令定义不一致(魔数或编号错误)
- 32 位用户空间 + 64 位内核,结构体对齐问题
- 未实现
.compat_ioctl(32 位程序在 64 位内核上运行)
解决方法:
c
/* 确保用户空间与内核使用相同的头文件 */
#define MY_IOC_MAGIC 'X'
#define MY_CMD _IOW(MY_IOC_MAGIC, 1, int)
/* 64 位内核支持 32 位程序 */
static const struct file_operations mydev_fops = {
.unlocked_ioctl = mydev_ioctl,
.compat_ioctl = mydev_ioctl, /* 简单情况可直接复用 */
};案例 5:poll 不工作
现象:用户空间 select() 或 poll() 永远阻塞。
错误代码:
c
/* ❌ 忘记调用 poll_wait */
static __poll_t mydev_poll(struct file *filp, poll_table *wait)
{
if (data_ready)
return EPOLLIN;
return 0;
}正确代码:
c
/* ✅ 必须注册等待队列 */
static __poll_t mydev_poll(struct file *filp, poll_table *wait)
{
struct mydev_priv *priv = filp->private_data;
poll_wait(filp, &priv->read_wq, wait); /* 关键! */
if (data_ready(priv))
return EPOLLIN | EPOLLRDNORM;
return 0;
}
/* 数据到达时唤醒 */
wake_up_interruptible(&priv->read_wq);案例 6:内存泄漏
现象:长时间运行后 free 内存持续下降。
排查工具:
bash
# kmemleak(需 CONFIG_DEBUG_KMEMLEAK=y)
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
# slabtop 查看 slab 使用
slabtop -o
# 查看模块内存占用
cat /proc/slabinfo | grep mydev常见原因:
open()中分配内存,release()中忘记释放- 中断处理函数中分配内存但未释放
- 循环引用导致对象无法释放
解决方法:
c
/* 使用 devm_* 系列函数自动管理 */
priv->buf = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
/* 设备解绑时自动释放,无需手动 kfree */案例 7:竞态条件导致数据损坏
现象:多进程同时读写设备时数据错乱。
错误代码:
c
/* ❌ 无锁保护 */
static ssize_t mydev_write(struct file *filp, ...)
{
memcpy(priv->buf, ubuf, len); /* 竞态! */
priv->len = len;
return len;
}正确代码:
c
/* ✅ 使用互斥锁 */
static ssize_t mydev_write(struct file *filp, ...)
{
if (mutex_lock_interruptible(&priv->lock))
return -ERESTARTSYS;
memcpy(priv->buf, ubuf, len);
priv->len = len;
mutex_unlock(&priv->lock);
return len;
}调试技巧:
bash
# lockdep 检测死锁(需 CONFIG_LOCKDEP=y)
dmesg | grep "possible circular locking"