Linux内核模块错误清理需手动回滚已分配资源,采用goto跳转至集中清理段,按LIFO顺序精准撤销初始化动作;模块卸载则用module_exit定义反向清理函数。

在 Linux 内核模块或底层 C 程序中,发生错误时执行清理函数的关键是手动回滚已成功完成的初始化步骤,不能依赖系统自动回收。内核不维护“某模块注册了哪些资源”的记录,所以出错后必须自己撤销。
核心原则:先做、再查、错则退
每一步资源申请(如内存分配、设备注册、文件打开、锁获取)之后,立即检查返回值;一旦失败,跳转到统一清理段,只释放此前已成功获取的资源。
用 goto 实现集中式错误清理
这是内核中最常用、最推荐的方式。结构清晰、无重复、易维护。
static int __init my_module_init(void)
{
int err;
struct device *dev = NULL;
int *buffer = NULL;
buffer = kmalloc(4096, GFP_KERNEL);
if (!buffer)
goto fail_buffer;
dev = register_chrdev_region(MKDEV(200, 0), 1, "mydev");
if (IS_ERR(dev)) {
err = PTR_ERR(dev);
goto fail_dev;
}
err = my_driver_register(&my_driver);
if (err)
goto fail_driver;
return 0; // 全部成功
fail_driver:
unregister_chrdev_region(MKDEV(200, 0), 1);
fail_dev:
kfree(buffer);
fail_buffer:
return err;
}
- 每个
goto label只对应一个已成功执行的步骤; - 清理顺序与分配顺序严格相反(LIFO),避免资源依赖问题;
- 所有清理代码集中在末尾,逻辑一目了然。
清理函数要做什么
不是简单“关掉一切”,而是**精准撤销本模块已完成的注册/分配动作**:
- 调用对应注销函数:
unregister_chrdev_region()、unregister_netdev()、device_unregister()等; - 释放内存:
kfree()、vfree()、free_pages(); - 关闭文件描述符或设备句柄(用户空间常见);
- 释放自旋锁、互斥体、完成量等同步原语(如果已初始化);
- 将指针置为
NULL、fd 置为-1,防止二次释放。
注意:不要在清理段调用可能失败或阻塞的函数(如 mutex_lock() 在中断上下文中不可用)。
模块卸载时的清理函数
这和错误清理不同,是模块正常退出时的收尾工作,用 `module_exit` 声明:
static void __exit my_module_exit(void)
{
my_driver_unregister(&my_driver);
unregister_chrdev_region(MKDEV(200, 0), 1);
kfree(global_buffer);
}
module_exit(my_module_exit);
- 必须与初始化顺序相反;
- 使用
__exit标记,模块被内置进内核时该函数会被丢弃; - 若未定义
module_exit,内核禁止卸载模块。
用户空间程序的类比做法
虽然不用 `goto` 强制要求,但思路一致:资源按需申请、逐项检查、失败即退。
int do_something(void) {
FILE *fp = NULL;
int *arr = NULL;
int fd = -1;
arr = malloc(1024);
if (!arr) goto cleanup;
fp = fopen("/tmp/data", "r");
if (!fp) goto cleanup;
fd = open("/dev/xyz", O_RDWR);
if (fd = 0) close(fd);
if (fp) fclose(fp);
free(arr);
return -1;
}
不复杂但容易忽略。











