__init标记的理解

来源:互联网 发布:打的费用计算软件 编辑:程序博客网 时间:2024/05/09 07:10

看了一段时间的内核代码,特别是针对drivers目录下的各目录。其实linux的实现也是含有面向对象的思想的,drivers目录下的设备(精确讲是每类设备,类的概念)都是按类分好的,每个目录都是一个子系统,显然每个子系统中管理着这一类设备,例如/drivers/usb目录:


仔细观察便不难发现,并不是只有我们所理解的单纯的usb设备。扯远了,感兴趣的可以去仔细研究一下设备,驱动,总线的概念。


回到主题__init标记,其实在每个子系统的初始化函数中我们都可以找到它。


linux通用采用initcall()机制或是module_init 宏指定初始化函数。本文以usb为例:

在 drivers/usb/core/usb.c 文件中,我们可以发现下面的代码:



显然此处采用initcall()机制初始化,module_exit()退出初始化。

我们看到一个subsys_initcall,它也是一个宏,我们可以把它理解为module_init,只不过因为这部分代码比较核心,开发者们把它看作一个子系统,而不仅仅是一个模块。这也很好理解,usbcore这个模块它代表的不是某一个设备,而是所有USB设备赖以生存的模块,Linux中,像这样一个类别的设备驱动被归结为一个子系统。比如PCI子系统,比如SCSI子系统,基本上,drivers/目录下面第一层的每个目录都算一个子系统,因为它们代表了一类设备。
    subsys_initcall(usb_init)的意思就是告诉我们 usb_init 是 USB 子系统真正的初始化函数,而 usb_exit()将是整个 USB 子系统的结束时的清理函数。于是为了研究 USB 子系统在内核中的实现,我们需要从 usb_init 函数开始看起。代码如上,不详细分析。

写过驱动的应该不会陌生,它对内核来说就是一种暗示,表明这个函数仅在初始化期间使用,在模块被装载之后,它占用的资源就会释放掉用作它处。

__init 的 定 义 在include/linux/init.h 文件里
#define__init          __attribute__((__section__(".init.text")))

好像这里引出了更多的疑问,__attribute__是什么?Linux内核代码使用了大量的GNUC扩展,以至于GNU C成为能够编译内核的唯一编译器,GNU C的这些扩展对代码优化、目标代码布局、安全检查等方面也提供了很强的支持。而__attribute__就是这些扩展中的一个,它主要被用来声明一些特殊的属性,这些属性主要被用来指示编译器进行特定方面的优化和更仔细的代码检查。

GNUC支持十几个属性,section是其中的一个,我们查看GCC的手册可以看到下面的描述
‘section("section-name")'
   Normally,thecompilerplacesthecodeitgeneratesinthe`text'

section. Sometimes,however,youneedadditionalsections,oryou
needcertainparticularfunctionstoappearinspecialsections.
   The`section'attributespecifiesthatafunctionlivesina
   particularsection. Forexample,thedeclaration:

     externvoidfoobar(void)__attribute__((section("bar")));

44   putsthefunction‘foobar'inthe‘bar'section.

   Somefileformatsdonotsupportarbitrarysectionssothe
   ‘section'attributeisnotavailableonallplatforms. Ifyou
   needtomaptheentirecontentsofamoduletoaparticular
   section,considerusingthefacilitiesofthelinkerinstead.
通常编译器将函数放在.text 节,变量放在.data 或.bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的节中。那么前面对__init 的定义便表示将它修饰的代码放在.init.text 节。连接器可以把相同节的代码或数据安排在一起,比如__init 修饰的所有代码都会被放在.init.text 节里,初始化结束后就可以释放这部分内存。问题可以到此为止,也可以更深入,即内核又是如何调用到这些__init修饰的初始化函数?要回答这个问题,还需要回顾一下subsys_initcall宏,它也在include/linux/init.h里定

125#definesubsys_initcall(fn)             __define_initcall("4",fn,4)
这里又出现了一个宏__define_initcall,它用于将指定的函数指针fn放到initcall.init节里 而对于具体的subsys_initcall宏,则是把fn放到.initcall.init的子节.initcall4.init里。要弄清楚.initcall.init、.init.text和.initcall4.init这样的东东,我们还需要了解一点内核可执行文件相关的概念。内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init 数据、bass 等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。

vmlinux.lds 是存在于 arch/<target>/ 目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏移量处。45我可以负责任的告诉你,要看懂 vmlinux.lds 这个文件是需要一番功夫的,不过大家都是聪明人,聪明人做聪明事,所以你需要做的只是搜索 initcall.init,然后便会看到似曾相识的内容
__inicall_start=.;
.initcall.init:AT(ADDR(.initcall.init)–0xC0000000){
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end=.;
这里的__initcall_start 指向.initcall.init 节的开始,__initcall_end 指向它的结尾。而.initcall.init 节又被分为了 7 个子节,分别是
.initcall1.init 
.initcall2.init 
.initcall3.init 
.initcall4.init 
.initcall5.init 
.initcall6.init 
.initcall7.init
我们的subsys_initcall宏便是将指定的函数指针放在了.initcall4.init子节。其它的比如core_initcall 将 函 数 指 针 放 在 .initcall1.init 子 节 , device_initcall 将 函 数 指 针 放 在
了.initcall6.init子节等等,都可以从include/linux/init.h文件找到它们的定义。各个字节的顺序是确定的,即先调用.initcall1.init中的函数指针再调用.initcall2.init中的函数指针,等
等。__init修饰的初始化函数在内核初始化过程中调用的顺序和.initcall.init节里函数指针的顺序有关,不同的初始化函数被放在不同的子节中,因此也就决定了它们的调用顺序。
至于实际执行函数调用的地方,就在/init/main.c 文件里,内核的初始化么,不在那里46还能在哪里,里面的 do_initcalls 函数会直接用到这里的__initcall_start、__initcall_end 来
进行判断。

原创粉丝点击