module_init解析

module_init这个函数的具体功能和执行过程

在kernel源码目录中找到include\linux\init.h文件 

  1. #define module_init(x)  __initcall(x);  
有对module_init 的定义,我们发现

module_init(x)是一个宏定义,那么_initcall(x)又是什么呢?

[cpp]  view plain  copy
 print ?
  1. #define __initcall(fn) device_initcall(fn)  

完整的宏定义如下:

__define_initcall:

  1. #define __define_initcall(fn, id) \  
  2.     static initcall_t __initcall_##fn##id __used \  
  3.     __attribute__((__section__(".initcall" #id ".init"))) = fn  

initcalls:


  1. #define pure_initcall(fn)       __define_initcall(fn, 0)  
  2.   
  3. #define core_initcall(fn)       __define_initcall(fn, 1)  
  4. #define core_initcall_sync(fn)      __define_initcall(fn, 1s)  
  5. #define postcore_initcall(fn)       __define_initcall(fn, 2)  
  6. #define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)  
  7. #define arch_initcall(fn)       __define_initcall(fn, 3)  
  8. #define arch_initcall_sync(fn)      __define_initcall(fn, 3s)  
  9. #define subsys_initcall(fn)     __define_initcall(fn, 4)  
  10. #define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)  
  11. #define fs_initcall(fn)         __define_initcall(fn, 5)  
  12. #define fs_initcall_sync(fn)        __define_initcall(fn, 5s)  
  13. #define rootfs_initcall(fn)     __define_initcall(fn, rootfs)  
  14. #define device_initcall(fn)     __define_initcall(fn, 6)  
  15. #define device_initcall_sync(fn)    __define_initcall(fn, 6s)  
  16. #define late_initcall(fn)       __define_initcall(fn, 7)  
  17. #define late_initcall_sync(fn)      __define_initcall(fn, 7s)  
  18.   
  19. #define __initcall(fn) device_initcall(fn)  
Note:下面用 xxx_initcall来代表pure_initcall,core_initcall、core_initcall_sync … …

我们可以看到非常多的xxx_initcall宏函数定义,他们都是通过__define_initcall 实现的。在__define_initcall里面包含了两个参数,一个是fn,另一个则是id。那么,这么多的宏又有何用??

我们来到init\main.c文件中可以找到函数do_initcalls

  1. static void __init do_initcalls(void)  
  2. {  
  3.     int level;  
  4.   
  5.     for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)  
  6.         do_initcall_level(level);  
  7. }  
很明显do_initcalls中有一个for循环, 那么此循环就是按照优先级顺序执行一些函数的 。那么问题又来了, 执行哪些函数?? 我们看看do_initcalls这个名字。是不是initcall非常的眼熟?没错就是上面我们提到过的宏定义 xxx_initcall里面就有initcall。

所以,我们先来解释一下这些宏有什么用
还是从我们最熟悉的地方module_init(fn)开始说起,其中fn是module_init的参数,fn是一个函数指针(函数名)。

module_init(fn)---> __initcall(fn) ---> device_initcall(fn) ---> __define_initcall(fn, 6)

所以当我们写module_init(fn)最终我们可以简化成以下内容(假设module_init的参数为test_init)

module_init(test_init) ---> __define_initcall(test_init, 6)

[cpp]  view plain  copy
 print ?
  1. #define __define_initcall(fn, id) \  
  2.     static initcall_t __initcall_##fn##id __used \  
  3.     __attribute__((__section__(".initcall" #id ".init"))) = fn  

简单补充:

符号作用举例
##
“##”符号 可以
是连接的意思
例如  __initcall_##fn##id 为__initcall_fnid
那么,fn = test_init,id = 6时, __initcall_##fn##id 为  __initcall_test_init6
#
“#”符号 可以
是字符串化的意思
例如 #id 为 “id”,id=6 时,#id 为“6”


通过上面的定义,我们把module_init(test_init)给替换如下内容

static initcall_t __initcall_ test6 __used __attribute__((__section__(".initcall" "6" ".init"))) = test_init

通过__attribute__(__section__)设置函数属性,也就是将test_init放在.initcall6.init段中。这个段在哪用?这就要涉及到链接脚本了。

大家可以到kernel目录arch中,根据自己的处理器平台找到对应的链接脚本。例如我现在的平台是君正m200(mips架构),可能大部分是arm架构。

在arch/mips/kernel/vmlinux.lds这个链接脚本里面有如下一段代码

  1. __init_begin = .;  
  2.  . = ALIGN(4096); .init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text) *(.cpuinit.text) *(.meminit.text) _einittext = .; }  
  3.  .init.data : AT(ADDR(.init.data) - 0) { *(.init.data) *(.cpuinit.data) *(.meminit.data) *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = .; . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .; __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .; __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .; __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .; . = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info) }  
  4.  . = ALIGN(4);  
当然,关于链接脚本又有很多多动要讲。所以现在我们不关心里面的具体含义,我们可以观察到上面有这些字符串使我们比较熟悉的:__initcall6_start = .; *( .initcall6.init ) *(.initcall6s.init)。链接脚本里的东西看似很乱很难,其实是非常有逻辑有规律可循的,我们来简单解释下面一行的代码作用

[cpp]  view plain  copy
 print ?
  1. __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init)   
其中__initcall6_start是一个符号,链接器用到的。 __initcall6_start = .; ,其中的 '.'符号是对当前地址的一个引用,也就说把当前的地址给了符号__initcall6_start, *(.initcall6.init) *(.initcall6s.init) 的意思是所有的.initcall6.init段和.initcall6s.init段的内容从__initcall6_start为起始地址开始链接。

.initcall0.init .initcall0s.init .initcall1.init .initcall1s.init …… .initcall7.init .initcall7s.init

上面的内容都出现在了链接脚本中,而0,0s,1,1s,2,2s …… 6,6s,7,7s 有没有觉得在哪里见过? 我们回顾一下initcalls里面的定义

[cpp]  view plain  copy
 print ?
  1. #define pure_initcall(fn)       __define_initcall(fn, 0)  
  2.   
  3. #define core_initcall(fn)       __define_initcall(fn, 1)  
  4. #define core_initcall_sync(fn)      __define_initcall(fn, 1s)  
  5. #define postcore_initcall(fn)       __define_initcall(fn, 2)  
  6. #define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)  
  7. #define arch_initcall(fn)       __define_initcall(fn, 3)  
  8. #define arch_initcall_sync(fn)      __define_initcall(fn, 3s)  
  9. #define subsys_initcall(fn)     __define_initcall(fn, 4)  
  10. #define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)  
  11. #define fs_initcall(fn)         __define_initcall(fn, 5)  
  12. #define fs_initcall_sync(fn)        __define_initcall(fn, 5s)  
  13. #define rootfs_initcall(fn)     __define_initcall(fn, rootfs)  
  14. #define device_initcall(fn)     __define_initcall(fn, 6)  
  15. #define device_initcall_sync(fn)    __define_initcall(fn, 6s)  
  16. #define late_initcall(fn)       __define_initcall(fn, 7)  
  17. #define late_initcall_sync(fn)      __define_initcall(fn, 7s)  
这里面就有 0,0s,1,1s,2,2s …… 6,6s,7,7s,也就是__define_initcall(fn, id)中的第二个参数 id。很显然这个id的值不是我们在调用module_init的时候传过去的。数字id 0~7代表的是不同的优先级(0最高,module_init对应的优先级为6,所以一般我们注册的驱动程序优先级为6),链接脚本里面根据我们注册不同的id,将我们的函数fn放入对应的地址里面。根据上面的分析,test_init放在.initcall6.init段中。

在kernel启动过程中,会调用do_initcalls函数一次调用我们通过xxx_initcall注册的各种函数,优先级高的先执行。所以我们通过module_init注册的函数在kernel启动的时候会被顺序执行。


如果了解过Linux操作系统启动流程,那么当bootloader加载完kernel并解压并放置与内存中准备开始运行,首先被调用的函数是start_kernel。start_kernel函数顾名思义,内核从此准备开启了,但是start_kernel做的事情非常多,简单来说为内核启动做准备工作,复杂来说也是非常之多(包含了自旋锁检查、初始化栈、CPU中断、立即数、初始化页地址、内存管理等等等...)。所以这篇博文我们还是主要分析和module_init注册函数的执行过程

start_kernel函数在 init/main.c文件中,由于start_kernel本身功能也比较多,所以为了简介分析过程我把函数从start_kernel到do_initcalls的调用过程按照如下方式展现出来

[cpp]  view plain  copy
 print ?
  1. start_kernel -> reset_init -> kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);  
  2.                                                     |  
  3.                                                     |->static int __ref kernel_init(void *unused)  
  4.                                                         |  
  5.                                                         |-> kernel_init_freeable( )  
  6.                                                                 |  
  7.                                                                 |-> do_basic_setup();  
  8.                                                                         |  
  9.                                                                         |——> do_initcalls();  
在上面的调用过程中,通过kernel_thread注册了一个任务kernel_init,kernel_thread的函数原型如下。

[cpp]  view plain  copy
 print ?
  1. /* 
  2.  * Create a kernel thread. 
  3.  */  
  4. pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)  
  5. {  
  6.     return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,  
  7.         (unsigned long)arg, NULL, NULL);  
  8. }  
kernel_thread创建了一个内核线程,也就是创建一个线程完成kernel_init的任务。通过kernel_init的逐层调用,最后调用到我们目前最应该关心的函数 do_initcalls

do_initcalls函数如下

[cpp]  view plain  copy
 print ?
  1. static void __init do_initcalls(void)  
  2. {  
  3.     int level;  
  4.   
  5.     for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)  
  6.         do_initcall_level(level);  
  7. }  

这个函数看起来就非常简单了,里面有for循环,每循环一次就调用一次do_initcall_level(level);其实可以发现在我们分析kernel源码时,大部分函数都能从函数名猜到函数的功能,这也是一名优秀程序猿的体现,大道至简,悟在天成。

接下来我们就开始具体分析do_initcalls函数啦~~

[cpp]  view plain  copy
 print ?
  1. for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)  
这句for循环很简单,循环执行条件是 level < ARRAY_SIZE(initcall_levels)

ARRAY_SIZE是一个宏,用于求数组元素个数,在文件include\linux\kernel.h文件中

[cpp]  view plain  copy
 print ?
  1. #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))  

当然ARRAY_SIZE宏里面还多了一个__must_be_array(),这个主要是确保我们传过来的arr是一个数组,防止ARRAY_SIZE的误用。所以在我们写kernel驱动程序时,遇到需要求一个数组的大小请记得使用ARRAY_SIZE。有安全感又高大上...哈哈

那么,initcall_levels是不是数组呢?如果是,里面有什么内容?

还是在文件main.c中有数组initcall_levels的定义

[cpp]  view plain  copy
 print ?
  1. static initcall_t *initcall_levels[] __initdata = {  
  2.     __initcall0_start,  
  3.     __initcall1_start,  
  4.     __initcall2_start,  
  5.     __initcall3_start,  
  6.     __initcall4_start,  
  7.     __initcall5_start,  
  8.     __initcall6_start,  
  9.     __initcall7_start,  
  10.     __initcall_end,  
  11. };  
这个数组可不能小看他,如果看过 module_init解析(上) 的朋友,对数组里面的名字“__initcall0 __initcall1 ... __initcall7”有一点点印象吧。

谈到数组,我们知道是元素的集合,那么initcall_levels数组中得元素是什么???

[cpp]  view plain  copy
 print ?
  1. static initcall_t *initcall_levels[] __initdata = {  
很显然,这个数组定义非常高大上。不管如何高大上,总离不开最基本的知识吧。所以我先从两点去探索:

1. 数组的名字,根据数组标志性的‘[ ]’,我们应该很容易知道数组名字是initcall_levels

2.数组的元素类型,由于定义中出现了指针的符号‘ * ’,也很容知道initcall_levels原来是一个指针数组啦。

所以现在我们知道了initcall_levels数组里面保存的是指针啦,也就是指针的一个集合而已。掰掰脚趾数一下也能知道initcall_levels数组里面有9个元素,他们都是指针。哈哈

对于这个数组,我们先暂且到这儿,因为我们已经知道了数组的个数了,也就知道for循环的循环次数。(后面还会继续分析这个数组,所以要由印象)

我们再回来看看do_initcalls:

  1. static void __init do_initcalls(void)  
  2. {  
  3.     int level;  
  4.   
  5.     for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)  
  6.         do_initcall_level(level);  
  7. }  

ARRAY_SIZE求出了数组initcall_levels的元素个数为9,所以 level变量从 0 ~ 7都是满足 level < ARRAY_SIZE(initcall_levels) - 1level < 9 - 1。一共循环了8次。

循环8此就调用了do_initcall_level(level) 8次。
do_initcall_level函数原型如下:

[cpp]  view plain  copy
 print ?
  1. static void __init do_initcall_level(int level)  
  2. {  
  3.     extern const struct kernel_param __start___param[], __stop___param[];  
  4.     initcall_t *fn;  
  5.   
  6.     strcpy(static_command_line, saved_command_line);  
  7.     parse_args(initcall_level_names[level],  
  8.            static_command_line, __start___param,  
  9.            __stop___param - __start___param,  
  10.            level, level,  
  11.            &repair_env_string);  
  12.   
  13.     for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)  
  14.         do_one_initcall(*fn);  
  15. }  

在do_initcall_level函数中,有如下部分是和内核初始化过程调用parse_args对选项进行解析并调用相关函数去处理的。其中的__start___param和__stop___param也是可以在内核链接脚本vmlinux.lds中找到的。

[cpp]  view plain  copy
 print ?
  1. extern const struct kernel_param __start___param[], __stop___param[];  
  2.   
  3. strcpy(static_command_line, saved_command_line);  
  4. parse_args(initcall_level_names[level],  
  5.        static_command_line, __start___param,  
  6.        __stop___param - __start___param,  
  7.        level, level,  
  8.        &repair_env_string);  
如果将上面初始化过程中命令行参数解析过程忽略,那么就剩下的内容也就是我们最想看到的内容了
[cpp]  view plain  copy
 print ?
  1. for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)  
  2.     do_one_initcall(*fn);  

这个也很简单,不就是一个for循环嘛,so easy~!!骂人

那么接下来我们就开始分析这个for循环:

1. for循环开始,fn = initcall_levels[level],initcall_levels是上面分析过的数组,数组里面存放着指针,所以fn也应该是指针咯。那么看看fn的定义

[cpp]  view plain  copy
 print ?
  1. initcall_t *fn;  
fn确实是一个initcall_t类型的指针,那initcall_t是什么?

在文件include\linux\init.h文件中找到其定义

[cpp]  view plain  copy
 print ?
  1. /* 
  2.  * Used for initialization calls.. 
  3.  */  
  4. typedef int (*initcall_t)(void);  
  5. typedef void (*exitcall_t)(void);  
从上面的定义可以知道,initcall_t原来是一个 函数指针 的类型定义。函数的返回值是int类型,参数是空 void。从注释也可以看出,initcall_t是初始化调用的。
简单来说,fn是一个函数指针。

2. 每循环一次,fn++。循环执行的条件是fn < initcall_levels[level+1];

这里fn++就不是很容易理解了,毕竟不是一个普通的变量而是一个函数指针,那么fn++有何作用呢??

首先,fn = initcall_levels[level],所以我们还是有必要去再看看initcall_levels数组了(之前暂时没有分析的,现在开始分析了)

[cpp]  view plain  copy
 print ?
  1. static initcall_t *initcall_levels[] __initdata = {  
  2.     __initcall0_start,  
  3.     __initcall1_start,  
  4.     __initcall2_start,  
  5.     __initcall3_start,  
  6.     __initcall4_start,  
  7.     __initcall5_start,  
  8.     __initcall6_start,  
  9.     __initcall7_start,  
  10.     __initcall_end,  
  11. };  
已经知道了initcall_levels是一个指针数组,也就是说数组的元素都是指针,指针是指向什么类型的数据呢? 是initcall_t类型的,上面刚刚分析过initcall_t是函数指针的类型定义。

这样一来,initcall_levels数组里面保存的元素都是数组指针啦。

很显然这是通过枚举的方式定义了数组initcall_levels,那么元素值是多少??(数组中元素是分别是 __initcall0_start __initcall1_start __initcall2_start ... __initcall7_start __initcall_end)

通过寻找会发现在main.c文件中有如下的声明

[cpp]  view plain  copy
 print ?
  1. extern initcall_t __initcall_start[];  
  2. extern initcall_t __initcall0_start[];  
  3. extern initcall_t __initcall1_start[];  
  4. extern initcall_t __initcall2_start[];  
  5. extern initcall_t __initcall3_start[];  
  6. extern initcall_t __initcall4_start[];  
  7. extern initcall_t __initcall5_start[];  
  8. extern initcall_t __initcall6_start[];  
  9. extern initcall_t __initcall7_start[];  
  10. extern initcall_t __initcall_end[];  
所以__initcall0_start __initcall1_start __initcall2_start ... __initcall7_start __initcall_end都是initcall_t类型的数组名,数组名也就是指针。只是这些都是extern声明的,所以在本文件里面找不到他们的定义出。那么他们在哪一个文件??答案还是 链接脚本 vmlinux.lds,而且我们已经看过这些名字很多次了...

下面再次把链接脚本中相关的内容拿出来:(相关的解释请参考 module_init 解析--上

[cpp]  view plain  copy
 print ?
  1. __init_begin = .;  
  2.  . = ALIGN(4096); .init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text) *(.cpuinit.text) *(.meminit.text) _einittext = .; }  
  3.  .init.data : AT(ADDR(.init.data) - 0) { *(.init.data) *(.cpuinit.data) *(.meminit.data) *(.init.rodata) *(.cpuinit.rodata) *(.meminit.rodata) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = .; . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .; __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .; __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .; __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .; . = ALIGN(4); __initramfs_start = .; *(.init.ramfs) . = ALIGN(8); *(.init.ramfs.info) }  
  4.  . = ALIGN(4);  
所以在main.c文件中extern声明的那些数组__initcall0_start  ... __initcall7_start __initcall_end其实就是上面链接脚本vmlinux.lds中定义的标号(也可以暂且简单粗暴认为是地址)。
为了好理解,把其中的__initcall0_start单独拿出来

[cpp]  view plain  copy
 print ?
  1. __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init)  
这里的意思是,__initcall0_start 是一段地址的开始,从这个地址开始链接所有 .initcall0.init .initcall0s.init 段的内容。那 .initcall0.init .initcall0s.init 段有什么东东??这就是 上篇博文 中解释的。简单来说,就是我们通过module_init(xxx)添加的内容,只是module_init对应的level值默认为6而已。

总而言之,__initcallN_start(其中N = 0,1,2...7)地址开始存放了一系列优先级为N的函数。我们通过module_init注册的函数优先级为6

现在我们回过头再去看看上面的for循环

[cpp]  view plain  copy
 print ?
  1. for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)  
  2. <span style="white-space: pre;">      </span>do_one_initcall(*fn);  

一开始fn = initcall_levels[level],假设level = 0。也就是fn = initcall_levels[0] = __initcall0_start。所以fn指向了链接脚本中的__initcall0_start地址,每当fn++也就是fn逐次指向注册到.initcall0.init和.initcall0s.init段中的函数地址了。for循环的条件是fn <initcall_levels[level + 1] = initcall_levels[0 + 1] = initcall_level[1] = __initcall1_start

为了能直观看出fn增加的范围,用如下的简易方式表达一下。

__initcall0_start  __initcall1_start  __initcall2_start  __initcall3_start ... ... __initcall7_start  __initcall_end

| <----- fn++ ---->|| <----- fn++ --->| | <----- fn++ --->| | <----- fn++ --->|... ... | <----- fn++ --->| END

了解这一点,我们已经接近胜利的彼岸~~

[cpp]  view plain  copy
 print ?
  1. for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)  
  2.     do_one_initcall(*fn);  
最后我们要了解的就是for循环每次执行的内容 do_one_initcall(*fn) ,其函数原型如下

[cpp]  view plain  copy
 print ?
  1. int __init_or_module do_one_initcall(initcall_t fn)  
  2. {  
  3.     int count = preempt_count();  
  4.     int ret;  
  5.   
  6.     if (initcall_debug)  
  7.         ret = do_one_initcall_debug(fn);  
  8.     else  
  9.         ret = fn();  
  10.   
  11.     msgbuf[0] = 0;  
  12.   
  13.     if (preempt_count() != count) {  
  14.         sprintf(msgbuf, "preemption imbalance ");  
  15.         preempt_count() = count;  
  16.     }  
  17.     if (irqs_disabled()) {  
  18.         strlcat(msgbuf, "disabled interrupts "sizeof(msgbuf));  
  19.         local_irq_enable();  
  20.     }  
  21.     WARN(msgbuf[0], "initcall %pF returned with %s\n", fn, msgbuf);  
  22.   
  23.     return ret;  
  24. }  
do_one_initcall函数就非常简单了,让我们看看最重要的内容如下

[cpp]  view plain  copy
 print ?
  1. if (initcall_debug)  
  2.     ret = do_one_initcall_debug(fn);  
  3. else  
  4.     ret = fn();  
这里就是判断是不是debug模式,无非debug会多一些调试的操作。但是不管是哪一种,他们都执行  ret = fn( );
因为fn就是函数指针,fn指向的是我们注册到__initcall0_start  ... __initcall7_start的一系列函数。所以  fn( );  就是 调用 这些函数。当然也包括了驱动中module_init注册的函数啦,只是通过module_init注册的level等级是6,for循环是从level = 0开始的,这也能看出0是优先级最高,7是优先级最低的。

到现在,module_init的作用已经全部分析完毕~




  • 32
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值