3. 设备注册与注销:设备节点重复注册、驱动卸载时资源未完全释放导致系统崩溃
设备注册与注销,听起来像是基础操作对吧?但说实话,我见过太多系统崩溃,根源就在这两个环节。今天咱们就聊聊,为什么一个简单的register_device和unregister_device,能搞出这么多幺蛾子。
3.1 设备节点重复注册:你注册了两次,系统就懵了
先说说重复注册的问题。你想想看,一个设备节点就像一个人的身份证号。如果你给同一个人发了两个不同的身份证号,系统该信哪个?
在PX4里,设备节点通常通过register_driver或register_class_dev这类函数注册。如果你不小心调用了两次,内核会返回一个错误码,但很多新手直接忽略了返回值。
我遇到过这样一个案例:有个同事写了一个传感器驱动,在probe函数里注册了设备节点,又在init函数里注册了一次。结果系统启动时,第二个注册覆盖了第一个,但第一个注册的资源没有释放。最后导致设备节点指向了错误的内存地址,一读数据就崩溃。
正确的做法是什么?很简单:
/* 错误示范:不检查返回值 */
register_driver(&my_driver);
/* 正确示范:检查返回值 */
int ret = register_driver(&my_driver);
if (ret != 0) {
PX4_ERR("Failed to register driver: %d", ret);
return ret;
}
嗯,这里要注意一点:register_driver返回-EEXIST时,不代表系统还能正常工作。你得主动处理这个错误,要么卸载旧的驱动,要么直接报错退出。
3.2 资源未完全释放:你走了,但东西没带走
驱动卸载时资源未释放,这个问题更隐蔽。说白了,就是驱动被卸载了,但它占用的内存、中断、定时器、工作队列等资源还留在系统里。
为什么会这样?我总结了一下,主要有三个原因:
- 忘记释放动态分配的内存:用
malloc或new分配的内存,在remove函数里没有free或delete。 - 中断处理函数没注销:驱动卸载了,但中断还在触发,系统会跳转到已经释放的内存地址执行——直接崩溃。
- 工作队列或定时器还在运行:这些异步任务可能在驱动卸载后还在执行,访问已经释放的数据结构。
我曾经在调试一个GPS驱动时,发现卸载驱动后系统偶尔会死机。查了两天才找到原因:驱动里注册了一个定时器,用于定期读取GPS数据。但在remove函数里,我先释放了数据缓冲区,然后才注销定时器。结果定时器触发时,缓冲区已经没了,系统直接访问了非法地址。
正确的卸载顺序应该是:
static int my_driver_remove(struct device *dev)
{
/* 第一步:停止所有异步操作 */
hrt_cancel(&my_timer);
work_cancel(&my_work);
/* 第二步:注销中断 */
irq_unregister(my_irq);
/* 第三步:注销设备节点 */
unregister_driver(&my_driver);
/* 第四步:释放动态内存 */
free(my_data);
return 0;
}
3.3 一个典型的崩溃场景分析
咱们来看一个真实案例。有个飞控的PWM输出驱动,卸载时总是导致系统重启。我接手后,把代码翻了个底朝天,发现问题出在下面这个函数里:
static int pwm_remove(struct device *dev)
{
/* 先释放了pwm_channel数组 */
free(pwm_channels);
/* 然后才停止PWM输出 */
pwm_stop();
/* 最后注销中断 */
irq_unregister(pwm_irq);
return 0;
}
你看出来问题了吗?pwm_stop()函数内部会访问pwm_channels数组,但数组已经被释放了。这就是典型的“释放后使用”漏洞。
修复方法很简单:把free(pwm_channels)放到最后执行。
3.4 如何系统性地避免这些问题?
说了这么多坑,咱们来点实用的。我总结了一套检查流程,你可以直接拿来用:
- 注册时:检查所有注册函数的返回值,一个都不能漏。
- 卸载时:先停止所有异步操作(定时器、工作队列、DMA),再注销中断,然后注销设备节点,最后释放内存。
- 测试时:反复加载卸载驱动至少100次,观察系统是否稳定。
- 代码审查时:重点检查
probe和remove函数的对称性。
下面这张图,是我画的一个资源管理流程图,你可以参考一下:
说白了,设备注册与注销的核心就一句话:你怎么申请的资源,就怎么释放回去,顺序反过来。这个原则听起来简单,但实际做起来,需要你在写代码时时刻保持清醒。
嗯,最后再提醒一句:如果你在调试时发现系统莫名其妙地崩溃,先别急着怀疑硬件。回头检查一下你的remove函数,八成是资源没释放干净。