第二章 深入理解helloworld

上一篇主要介绍了一个最最简单的设备启动模块Hello world的编译,并将其加载到内核的一些步骤。现在对hello world的代码做一个详细的解析,并提出几个编写设备驱动程序代码时要注意的问题。

        如上一篇文章所看到的源码一样,设备驱动的初始化函数一般定义如下:

1 static int __init initialization_function(void)
2 {
3 /* Initialization code here */
4 }
5 module_init(initialization_function);

  每一个设备驱动的初始化函数都应该像上面那样子定义。首先,这个初始化函数应该定义为静态的,只有这样,他们才不会在特定文件之外可见(暂时不是很理解这句话)。函数名前面的__init 显得很是惹眼。事实上,这是一个给内核的暗示标记罢了,告诉内核,这个函数只有在模块初始化的时候才会被调用,调用完了之后,这个函数所占用的内存空间就可以直接被清空了。不过这个__init 标记对于初始化函数来说并不一定是必须的(上一篇中所举的例子就没有使用这个 __init 标记)。最底下的module_init是一定不可缺少的,这个宏的作用相当于把你写的初始化函数注册到内核,这样内核才会知道你的模块的初始化函数在哪。没有调用这个宏的话,你的初始化函数永远都不会被调用。

       在上一篇文章的例子中,我们的初始化函数的函数体完成的功能相当的简单,单纯的输出了一个字符串。然而,实际的设备驱动模块的初始化函数要完成的工作要复杂的多。由于驱动是直接跟硬件相关的,必须在驱动模块的初始化函数中完成这个驱动要使用的所有设施的注册工作。如果看过一些驱动的源码的话,你会发现初始化函数里面会出调用很多的以register_为前缀的函数。linux中设施的注册函数大都以register_开头。

       这里要特别提出的就是,调用初始化函数当中的错误处理。由于初始化函数会调用很多的注册设施函数,每一个函数都有可能失败,所以必须检查每一个函数的返回值。假如在调用一个函数时发生了错误,而且这个错误大到模块已经不可能在继续加载了,那么你要做的事情可不是简单的输出模块加载失败并且退出就行了。你必须把之前所有成功的动作取消掉,重点就是退回已经注册成功的设施。这个地方的代码编写必须严谨。举个简单的例子:

 1 int __init my_init_function(void)
2 {
3 int err;
4 /* registration takes a pointer and a name */
5 err = register_this(ptr1, "skull");
6 if (err) goto fail_this;
7 err = register_that(ptr2, "skull");
8 if (err) goto fail_that;
9 err = register_those(ptr3, "skull");
10 if (err) goto fail_those;
11 return 0; /* success */
12 fail_those: unregister_that(ptr2, "skull");
13 fail_that: unregister_this(ptr1, "skull");
14 fail_this: return err; /* propagate the error */
15 }

  虽然一边情况下都是不推荐使用goto语句的,但是这个地方使用goto的话能够达到一个很好的效果。当然,上面的例子只是一个参考,你完全可以根据自己的风格来写代码,只要能够达到目的就行了。

        清理函数定义如下:

  

1 static void __exit cleanup_function(void)
2 {
3 /* Cleanup code here */
4 }
5 module_exit(cleanup_function);

  这个结构基本和初始化函数一致,相应的内容自行脑补一下就行了。记住一点就好了,这个函数只会在设备被卸载的时候调用,任何其他地方的调用都是错误的。至于它的函数体应该做的事情,当然也就是把之前初始化的时候得到的东西还给系统了。

posted @ 2011-07-28 14:16  自由泳的青蛙  阅读(378)  评论(0编辑  收藏  举报