__init, __initdata等属性标志,是要把这种属性的代码放入目标文件的.init.text节,数据放入.init.data节──这一过程是通过编译内核时为相关目标平台提供了xxx.lds链接脚本来指导ld完成的。
对编译成module的代码和数据来说,当模块加载时,__init属性的函数就被执行;
对 静态编入内核的代码和数据来说,当内核引导时, do_basic_setup()函数调用do_initcalls()函数,后者负责所有.init节函数的执行 。
在初始化完成后,用这些关键字标识的函数或数据所占的内存会被释放掉。
1)
所有标识为__init的函数在链接的时候都放在.init.text这个区段内,
在这个区段中,函数的摆放顺序是和链接的顺序有关的,是不确定的。
2)
所有的 __init函数在区段.initcall.init中还保存了一份函数指针,
在初始化时内核会通过这些函数指针调用这些__init函数指针,
并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等),
注意,这些函数在内核初始化过程中的调用顺序只和这里的函数指针的顺序有关,
和1)中所述的这些函数本身在.init.text区段中的顺序无关。
在2.4内核中,这些函数指针的顺序也是和链接的顺序有关的,是不确定的。
__init 宏最常用的地方是驱动模块初始化函数的定义处,其目的是将驱动模块的初始化函数放入名叫.init.text的输入段。当内核启动完毕后,这个段中的内存会被释放掉供其他使用。
__initdata宏用于数据定义,目的是将数据放入名叫.init.data的输入段。其它几个宏也类似。
另外需要注意的是,在以上定意中,用__section__代替了section。还有其它一些类似的宏定义,这里不一一列出,其作用都是类似的。
模块加载分为动态加载和静态加载。
所谓静态加载就是,开机加载系统时将模块加载上去,这就是编译进内核。
而动态加载就是在开机以后将模块加载上去,这就是编译成模块!
init_module是默认的模块的入口,如果你想指定其他的函数作为模块的入口就需要
module_init函数来指定,比如
module_init (your_func);
其中your_func是你编写的一个函数的名称.
init_module()是真正的入口,module_init是宏,如果在模块中使用,最终还是要转换到init_module()上。
如果不是在模块中使用,module_init可以说没有什么作用。总之,使用module_init方便代码在模块和非模块间移植。
由于4年前对于C语言和Linux的知识贫乏,所以当时对于模块中的函数定义没有细看。这次在温习《LDD3》的时候,重新看了一下关于__init、__initdata和__exit、__exitdata的知识,记录如下:
对于__init、__initdata和__exit、__exitdata的定义位于 <linux/init.h>,这些宏定义的作用是告诉编译器将这些函数或者数据放入相应的section中,而在模块加载的阶段,.ko文件中的代码和数据的加载区域是根据section来加载的。
比如:如果函数的定义中带有__init,那么这个函数的所有代码会被放入.init.text的section中。
如果函数的定义中带有__initdata,那么这个函数的所有代码会被放入.init.data的section中。
之所以要使用这个宏定义,其中一个原因是标记为初始化的函数和数据,表明该函数和数据仅在初始化期间使用。在模块装载之后,模块装载就会将初始化函数扔掉。这样可以将该函数占用的内存释放出来。
这种释放根据是否编译进内核是有区别的:
(1)模块编译进内核:所有的初始化数据和函数都是在系统启动的最后阶段,在所有模块都初始化完成以后被内核统一释放的。所有你一般可以在内核启动信息的后面看到:
- PHY: 0:01 - Link is Up - 100/Full
- VFS: Mounted root (nfs filesystem) on device 0:14.
- devtmpfs: mounted
- Freeing init memory: 196K
- INIT: version 2.86 booting
(2)独立的模块:模块 是通过module-init-tool中的insmod的程序利用系统调用来挂载的,而所有的初始化数据和函数都是被这个系统调用所使用的,在模块挂载完成并初始化过后,由系统调用来完成对初始化数据和函数所占空间的释放。
所以对于将内核驱动代码中的函数和数据定义为“初始化”时需要注意:不要将驱动定义的文件方法(如 open、read、write、close)或者驱动在实际工作中需要使用的函数和数据定义为“初始化”属性,因为在驱动初始化后这些东东就已经被释放了,如果使用了就会Oops。
有些网上的文章中写到:
__init宏使内建模块中的init函数在执行完成后释放掉,不过可装载的模块不受影响。
这个是 错误的,这些“初始化”宏同样影响可装载模块。从模块装载的系统调用源码中你可以找到释放的地方,他释放的是整个“初始化”section。具体的情况请参考 《深入Linux内核构架》的《第七章 模块》
从代码上可以证明“初始化”宏同样影响可装载模块,从实验中同样可以,实验步骤:
(1)在一个简单的字符驱动中定义一个“初始化”字符串,并在模块初始化时打印出来。在驱动的其他方法中也试图打印这个字符串,如果这个方法被系统调用执行了,那么你就得到了一个Oops。
(2)去掉这个字符串定义时的“初始化”宏,再做一边实验,字符串依然可以被打印出来。