Linux设备驱动程序学习----5.模块的初始化和关闭

模块的初始化和关闭

更多内容请参考Linux设备驱动程序学习----目录

1. 初始化函数

  模块的初始化函数负责注册模块所提供的任何设施,即可以被应用程序访问的新功能,可能是一个完整的驱动程序或者仅仅是一个新的软件抽象。初始化函数的定义通常如下所示:

static int __init initialization_function(void)
{
    // 初始化代码
    
    return 0;
}
module_init(initialization_function);

  初始化函数被声明为static,因为初始化函数在特定文件之外没有其他意义。__init标记表明该函数仅在初始化期间使用。在模块被装载之后,模块装载器就会将初始化函数扔掉,可将该函数占用的内存释放出来。

  注意:不要在结束初始化之后仍要使用的函数或数据结构上使用__init和__initdata标记。对于__devinit和__devinitdata,只有在内核未被配置为支持热插拔设备的情况下,才会被翻译为__init和__initdata。

  module_init()宏的使用是强制性的,会在模块的目标代码中增加一个特殊的段,用于说明内核初始化函数所在的位置。如果没有这个定义,初始化函数永远不会被调用。

2. 清除函数

  每个模块都需要一个清除函数,在模块被移除前注销接口并向系统中返回所有资源。该函数定义如下:

static void __exit cleanup_function(void)
{
    // 清除代码
}
module_exit(cleanup_function);

  清除函数没有返回值,__exit修饰词标记该代码仅用于模块卸载,编译器会把该函数放在特殊的ELF段中。如果模块被直接编译到内核中,或者内核配置不允许卸载模块,则被标记为__exit的函数将被直接丢弃。所以被标记为__exit的函数只能在模块被卸载或者系统关闭时被调用,其他任何用法都是错的。module_exit()声明对于内核找到模块的清除函数是必需的。如果一个模块未定义清除函数,则内核不允许卸载该模块。

3. 初始化过程中的错误处理

  在内核中注册设施时,注册可能会失败。即使最简单的动作,都需要内存分配,而所需的内存可能无法获得。因此模块代码必须始终检查返回值,并确保所请求的操作已真正执行成功。如果在注册设施时遇到错误,首先要判断模块是否可以继续初始化,只要可能,模块应该继续向前并尽可能提供其功能。

  如果在发生了某个特定类型的错误之后无法继续装载模块,则要将出错之前的所有注册工作都撤销掉。即当模块的初始化出现错误之后,模块必须自行撤销已注册的设施。如果未能撤销已注册的设施,则内核会处于一种不稳定状态,这时,唯一有效的解决办法就是重新引导系统。所以必须在初始化过程出现错误时认真完成正确的工作。

  错误恢复的处理有时使用goto语句非常有效。正常情况下,很少使用goto,但是唯一在错误处理时却非常有效。内核经常使用goto来处理错误。如下例子所示:

int __init my_init_function(void)
{
    int err;
    
    // 使用指针和名称注册
    err = register_this(ptr1, "skull");
    if (err)
        goto fail_this;
    
    err = register_that(ptr2, "skull");
    if (err)
        goto fail_that;
        
    err = register_those(ptr3, "skull");
    if (err)
        goto fail_those;
    
    return 0;   // 成功
    
fail_those:
    unregister_that(ptr2, "skull");
fail_that:
    unregister_that(ptr1, "skull");
fail_this:
    return err; // 返回错误
}

在出错的时候使用goto语句,将只撤销出错时刻以前所成功注册的那些设施。

  另一种方法是,记录任何成功注册的设施,在出错的时候调用模块的清除函数。清除函数将仅仅回滚已成功完成的步骤。这种方法需要更多的代码和CPU时间,因此在追求效率的代码中使用goto语句是最好的错误恢复机制。

  在Linux内核中错误编码是定义在<linux/errno.h>头文件中的负整数,如果不想使用其他函数返回的错误码,应该包含<linux/errno.h>头文件,以使用如:-ENODEV、-ENOMEM之类的符号值。每次返回核时的错误编码是个好习惯,因为用户程序可以通过perror()函数或类似途径将错误符号转换为有意义的字符串。

  模块的清除函数需要撤销初始化函数所注册的所有设施,并且习惯上以相反于注册的顺序撤销设施,如下所示:

void __exit my_cleanup_function(void)
{
    unregister_those(ptr3, "skull");
    unregister_that(ptr2, "skull");
    unregister_this(ptr1, "skull");
    
    return;
}

  如果初始化和清除工作涉及很多设施,则goto方法可能难以管理,因为所有用于清除设施的代码在初始化函数中给重复,同时一些标号交织在一起。

  每次发生错误时从初始化函数中调用清除函数,将减少代码的重复并且时代码更清晰、更有条理。清除函数必须在撤销每项设施的注册之前检查它的状态。如下示例:

struct something *item1;
struct somethingelse *item2;

void my_cleanup(void)
{
    if (item1)
        release_thing(item1);
    if (item2)
        release_thing2(item2);
    if (stuff_ok)
        unregister_stuff();
        
    return;
}

int __init my_init(void)
{
    int err = -ENOMEM;
    
    item1 = allocate_thing(arguments);
    item2 = allocate_thing2(arguments2);
    if (!item1 || !item2)
        goto fail;
    err = register_stuff(item1, item2);
    if (!err)
        stuff_ok = 1;
    else
        goto fail;
    
    return 0;   // 返回成功

fail:
    my_cleanup();
    return err; // 返回错误
}

  如上代码所示,根据调用的注册/分配函数的语义,可以使用或不使用外部标记来标记每个初始化步骤的成功。这种方式的初始化能很好地扩展到对大量设施的支持。注意:因为清除函数被非退出代码调用,因此不能将清除函数标记为__exit;

4. 模块装载竞争

  模块装载中也存在竞态。在模块注册完成之前,内核的某些部分可能会立即使用我们刚刚注册的任何设施,即在初始化函数还在运行的时候,内核就完全可能会调用我们的模块。因此,在首次注册完成之后,代码就应该准备好被内核的其他部分调用;在支持某个设施的所有内部初始化完成之前,不要注册任何设施。

  当模块初始化失败而内核的某些部分已经使用了模块所注册的某个设施时,此时根本不应该出现模块初始化失败,因为模块已经成功导出了可用的功能及符号。如果初始化一定要失败,则应该仔细处理内核其他部分正在进行的操作,并且要等待这些操作的完成。

更多内容请参考Linux设备驱动程序学习----目录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249