模块初始化代码

在Linux旧模型中,每个模块必须按照其是否静态编译进内核映像中使用条件式代码,引入大量的冗余和不清晰代码。为了消除条件式代码引起的混乱,是程序具有更高的可读性,内核开发人员引入了一组宏,现在模块开发人员可以使用这组宏编写更为清晰的初始化代码。这些宏允许内核在后台决定每个模块要把哪些代码引入内核映像中,由于不需要哪些代码排除在外,以及哪些代码只在初始化期间执行等等。这使得每个程序员的负担减轻,不用在每个模块内重复相同的逻辑。这些宏必须提供至少下列两项服务:

  • 定义新内核组件开启时必须执行的函数,无论这些组件是因为与内核静态连接,或者是因为于执行期间以模块形式而加载
  • 定义一些初始化函数之间的次序,强制内核组件之间遵循相互依赖性

优化宏卷标

Linux内核使用各种不同的宏为函数和数据结构标记特殊属性,这些宏大多都定义在include/linux/init.h中,而有些宏会通知连接器把带有共同属性的代码或数据结构放到特定专用的内存区域(内存节区)。如此一来,内核就会用简单的方式轻易管好全部具有共同属性的对象类(函数或数据结构)。

image

函数所用的宏

__init, 引导期间初始化函数:针对那些到了引导阶段结束时已不需要使用的函数。

__exit,__init的配对宏,当相关的内核组件关闭时,就会被调用。通常用于标记module_exit函数。

core_initcall,postcore_initcall,arch_initcall,subsys_initcall,fs_initcall,device_initcall,late_initcall这组宏(按优先级高低排列)可标记那些必须在引导期间执行的初始化函数。

数据结构用的宏

__initdata,只用于引导期间的已初始化的数据结构

__exitdata,只用于标记__exitcall的函数所用的数据结构。

大多数宏都是成对出现的:一个(或一组)负责初始化,而另一个宏就负责删除。宏用于两种情况,一种是当函数要被执行时,另一种是函数或数据结构要放入内存节区。同一个函数可以标记一个以上的宏。

XXX_initcall宏

内核引导的早期阶段有下列两项主要的初始化工作:

  • 各种必须以特定次序完成初始化的关键子系统和强制子系统,例如,PCI层初始化之前,内核无法对PCI设备驱动程序做初始化。
  • 其他不需要以严格次序完成初始化的内核组件

第二阶段的初始化,有do_initcalls完成,

   1:  extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
   2:   
   3:  static void __init do_initcalls(void)
   4:  {
   5:      initcall_t *fn;
   6:   
   7:      for (fn = __early_initcall_end; fn < __initcall_end; fn++)
   8:          do_one_initcall(*fn);
   9:  }

由do_initcalls所调用的函数不应该改变抢占状态或关闭IRQ。因此,每个函数执行之后,do_initcalls就会检查该函数是否做了任何修改,然后必要时调整抢占和IRQ状态。

最优化

把模块编译成内核的一部分时,就可以做如下的优化

1,module_exit函数绝对不会被用到,所以将其标记为_exit之后,程序员就可以确保在链接期间这些函数将不会被引入映像中。

2,module_init函数只会在引导期间执行一次,所以将其标记为__init之后,程序员就可以确保它们在执行后就会被丢弃

3,无论MODULE是否定义,当内核不支持热插拔时,设备就无法从正在运行中的系统中删除掉。因为,remove函数绝对不会被PCI层调用,从而得以初始化为NULL指针。这是由__devexit_p宏指出的。

4,当内核不支持热插拔或模块时,该模块就不再需要驱动程序用于初始化pci_driver->remove的函数。

你可能感兴趣的:(模块初始化代码)