模块的初始化和关闭

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内核中错误编码是定义在头文件中的负整数,如果不想使用其他函数返回的错误码,应该包含头文件,以使用如:-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 禁用 内核 驱动程序,Linux设备驱动程序学习----5.模块的初始化和关闭相关推荐

  1. Unix/Linux操作系统分析实验四 设备驱动: Linux系统下的字符设备驱动程序编程

    Unix/Linux操作系统分析实验一 进程控制与进程互斥 Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配 Unix/Linux操作系统分析实验三 文 ...

  2. linux kernel 2.6 i2c设备驱动程序框架介绍,linux设备驱动程序-i2c(2)-adapter和设备树的解析...

    linux设备驱动程序-i2c(2)-adapter和设备树的解析 (注: 基于beagle bone green开发板,linux4.14内核版本) 而在linux设备驱动程序--串行通信驱动框架分 ...

  3. linux 驱动 内核模式,Linux内核模块和驱动的编写

    Linux内核是一个整体是结构,因此向内核添加任何东西,或者删除某些功能,都十分困难.为了解决这个问题引入了内核机制.从而可以动态的想内核中添加或者删除模块. 模块不被编译在内核中,因而控制了内核的大 ...

  4. linux img 内核启动,linux的启动流程(initrd.img)

    http://www.ibm.com/developerworks/cn/linux/l-initrd.html 一.从哪里到哪里 本文旨在描述linux中内核如何调用启动,然后如何从img的文件系统 ...

  5. linux pae内核安装,Linux 安装PAE内核

    客户软件是部署在32位的CentOS5服务器当中,CentOS5目前只能识别4G内存,需要安装PAE内核,让系统支持PAE物理地址扩展. 1.安装PAE内核 yum -y install kernel ...

  6. linux ipv6内核编译,linux ipv6内核设置

    linux ipv6内核设置,进入/proc/sys/net/ipv6: conf/all/forwarding Type: BOOLEAN 在两个接口之间进行global IPv6 forwardi ...

  7. linux追加内核参数,Linux设置内核参数的方法

    1 内核参数的查看方法 使用"sysctl -a"命令可以查看所有正在使用的内核参数.内核参数比较多(一般多达500项),按照前缀主要分为以下几大类:net.ipv4.net.ip ...

  8. linux禁止内核抢占,Linux内核态抢占机制分析

    [51CTO晃荡]8.26 带你深度懂得清华大年夜学.搜狗基于算法的IT运维实践与摸索 本文起首介绍非抢占式内核(Non-Preemptive Kernel)和可抢占式内核(Preemptive Ke ...

  9. linux升级内核ivh,Linux内核升级

    当前的系统版本为rhel7.2,内核版本为"Linux 3.10.0-327.el7.x86_64" [root@rhel7_2 ~]# hostnamectl Static ho ...

最新文章

  1. 论文笔记:Missing Value Imputation for Multi-view UrbanStatistical Data via Spatial Correlation Learning
  2. 路由选择协议笔记ripv1、ripv2、ripng
  3. IT外包 OpenEIM 强调CMMI等级
  4. jtoken判断是否包含键_Redis源码解析十三--有序集合类型键实现(t_zset)
  5. 有什么手机python编辑器_好用的Python编辑器有哪些?
  6. python图像处理应用的前景_传统图像处理还有前景吗?
  7. Echarts数据可视化polar极坐标系,开发全解+完美注释
  8. OpenCV-图像处理(22、像素重映射(cv::remap))
  9. 安徽掀起新一轮大规模清房行动 官员急抛房产
  10. 项目管理-项目启动会
  11. LM2903器件使用说明
  12. 区块链学习笔记20——权益证明
  13. 应届毕业生怎么找java工作,应届毕业生怎么能找到高薪工作?
  14. 《高效阅读——20分钟读懂一本书》读书总结
  15. ASPP - 空洞空间金字塔池化
  16. 偷得假期半日闲,只羡鸳鸯不羡仙
  17. 二战时图灵机破译的Enigma密码,现在AI仅需13分钟便可破译
  18. 林业调查规划设计单位资质办理认定
  19. 一键设置电脑锁屏后程序仍然运行
  20. 黑马程序员————IO流4(day21)

热门文章

  1. python调用metasploit自动攻击_Python实现远程调用MetaSploit的方法
  2. 【Python基础知识-pycharm版】第十节_异常
  3. sqlserver2008驱动_Python连接数据库两种方法,QSqlDatabase,pymmsql,驱动名
  4. 天天说常识推理,究竟常识是什么?
  5. 美团Android自动化之旅—生成渠道包
  6. 史上最全memcached面试26题和答案
  7. CV模型,全目标检测等
  8. DTW动态时间规整算法
  9. DeepMind最新研究:如何将「大语言模型」 训练到最优?
  10. 10 计算机组成原理第六章 总线 总线的概念与分类 总线性能指标 总线仲裁 总线操作和定时 总线标准