千家信息网

Linux内核是怎么初始化各个模块的

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,小编给大家分享一下Linux内核是怎么初始化各个模块的,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!相信很多在研究linu
千家信息网最后更新 2026年10月31日Linux内核是怎么初始化各个模块的

小编给大家分享一下Linux内核是怎么初始化各个模块的,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

相信很多在研究linux内核源码的同学,经常会发现一些模块的初始化函数找不到调用者,比如下面的网络模块的初始化函数:

// net/ipv4/af_inet.c  static int __init inet_init(void)  {          ...          /*           *      Set the IP module up           */          ip_init();          /* Setup TCP slab cache for open requests. */          tcp_init();          /* Setup UDP memory threshold */          udp_init();          ...  }  fs_initcall(inet_init);

即使你在整个内核代码中搜索,也找不到任何地方调用这个函数,那这个函数到底是怎么调用的呢?

秘密就在这个函数之后的一行代码里:

fs_initcall( inet_init);

在该行代码中,fs_initcall是一个宏,具体定义如下:

// include/linux/init.h  #define ___define_initcall(fn, id, __sec) \          static initcall_t __initcall_##fn##id __used \                  __attribute__((__section__(#__sec ".init"))) = fn;  ...  #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)  ...  #define fs_initcall(fn)                 __define_initcall(fn, 5)

在该宏展开后,上面宏调用的结果,大致像下面这个样子:

static initcall_t __initcall_inet_init5 __attribute__((__section__(".initcall5.init"))) = inet_init;

由上可见,fs_initcall宏最终是定义了一个静态变量,该变量的类型是initcall_t,值是宏参数表示的函数地址。

initcall_t类型的定义如下:

typedef int (*initcall_t)(void);

由上可见,initcall_t是一个函数指针类型,它定义的变量会指向一个函数,该函数的参数要为空,返回类型要为int。

我们可以再看下上面的 inet_init 方法,该方法确实符合这些要求。

综上可知,fs_initcall宏定义了一个变量 __initcall_inet_init5,它的类型为initcall_t,它的值为inet_init函数的地址。

到这里我相信很多同学会想,linux内核一定是通过这个变量来调用inet_init函数的,对吗?

对,也不对。

对是因为内核确实是通过该变量指向的内存来获取inet_init方法的地址并调用该方法的。

不对是因为内核并不是通过上面的__initcall_inet_init5变量来访问这个内存的。

那不用这个变量,还能通过其他方式访问这个内存吗?

当然可以,这正是linux内核设计的巧妙之处。

我们再来看下上面的宏展开之后,静态变量__initcall_inet_init5的定义,在该定义中有如下的一些代码:

__attribute__((__section__(".initcall5.init")))

该部分代码并不属于c语言标准,而是gcc对c语言的扩展,它的作用是声明该变量属于 .initcall5.init这个section。

所谓section,我们可以简单的理解为对程序所占内存区域的一种布局和规划,比如我们常见的 section有 .text用来存放我们的代码,.data或.bss用来存放我们的变量。

通过这些section的定义,我们可以把程序中的相关功能放到同一块内存区域中,这样来方便内存管理。

除了这些默认的section之外,我们还可以通过gcc的attribute来自定义section,这样我们就可以把相关的函数或变量放到相同的section中了。

比如上面的__initcall_inet_init5变量就属于.initcall5.init这个自定义section。

在定义了这些section之后,我们可以在链接脚本中告诉linker,这些section在内存中的位置及布局是什么样子的。

对于x86平台来说,内核的链接脚本是:

arch/x86/kernel/vmlinux.lds.S

在该脚本中,对.initcall5.init等这些section做了相关定义,具体逻辑如下:

// include/asm-generic/vmlinux.lds.h  #define INIT_CALLS_LEVEL(level)                                         \                  __initcall##level##_start = .;                          \                  KEEP(*(.initcall##level##.init))                        \                  KEEP(*(.initcall##level##s.init))                       \  #define INIT_CALLS                                                      \                  __initcall_start = .;                                   \                  KEEP(*(.initcallearly.init))                            \                  INIT_CALLS_LEVEL(0)                                     \                  INIT_CALLS_LEVEL(1)                                     \                  INIT_CALLS_LEVEL(2)                                     \                  INIT_CALLS_LEVEL(3)                                     \                  INIT_CALLS_LEVEL(4)                                     \                  INIT_CALLS_LEVEL(5)                                     \                  INIT_CALLS_LEVEL(rootfs)                                \                  INIT_CALLS_LEVEL(6)                                     \                  INIT_CALLS_LEVEL(7)                                     \                  __initcall_end = .;

由上可见,initcall相关的section有很多,我们上面例子中的.initcall5.init只是其中一个,除此之外还有 .initcall0.init,.initcall1.init等等这些section。

这些section都是通过宏INIT_CALLS_LEVEL来定义其处理规则的,相同level的section被放到同一块内存区域,不同level的section的内存区域按level大小依次连接在一起。

对于上面的__initcall_inet_init5变量来说,它的section是.initcall5.init,它的level是5。

假设我们还有其他方法调用了宏fs_initcall,那该宏为该方法定义的静态变量所属的section也是.initcall5.init,level也是5。

由于该变量和__initcall_inet_init5变量所属的initcall的level都相同,所以它们被连续放在同一块内存区域里。

也就是说,这些level为5的静态变量所占的内存区域是连续的,又因为这些变量的类型都为initcall_t,所以它们正好构成了一个类型为initcall_t的数组,而数组的起始地址也在INIT_CALLS_LEVEL宏中定义了,就是__initcall5_start。

如果我们想要调用这些level为5的initcall,只要先拿到__initcall5_start地址,把其当成元素类型为initcall_t的数组的起始地址,然后遍历数组中的元素,获取该元素对应的函数指针,就可以通过该指针调用对应的函数了。

来看下具体代码:

// init/main.c  extern initcall_entry_t __initcall_start[];  extern initcall_entry_t __initcall0_start[];  extern initcall_entry_t __initcall1_start[];  extern initcall_entry_t __initcall2_start[];  extern initcall_entry_t __initcall3_start[];  extern initcall_entry_t __initcall4_start[];  extern initcall_entry_t __initcall5_start[];  extern initcall_entry_t __initcall6_start[];  extern initcall_entry_t __initcall7_start[];  extern initcall_entry_t __initcall_end[];  static initcall_entry_t *initcall_levels[] __initdata = {          __initcall0_start,          __initcall1_start,          __initcall2_start,          __initcall3_start,          __initcall4_start,          __initcall5_start,          __initcall6_start,          __initcall7_start,          __initcall_end,  };  static void __init do_initcall_level(int level)  {          initcall_entry_t *fn;          ...          for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)                  do_one_initcall(initcall_from_entry(fn));  }  static void __init do_initcalls(void)  {          int level;          for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)                  do_initcall_level(level);  }

在上面的代码中,do_initcalls方法遍历了所有的合法level,对于每个level,do_initcall_level方法又调用了该level里所有函数指针指向的函数。

我们上面示例中的inet_init方法就属于level 5,也是在这里被调用到的。

linux内核就是通过这种方式来调用各个模块的初始化方法的,很巧妙吧。

最后我们再来总结下:

1. 在各模块的初始化方法之后,一般都会调用一个类似于fs_initcall(inet_init)的宏,该宏的参数是该模块的初始化方法的方法名。

2. 该宏展开后的结果是定义一个静态变量,该变量通过gcc的attribute来声明其所属的initcall level的section,比如inet_init方法对应的静态变量就属于.initcall5.init这个section。

3. 在linux的链接脚本里,通过INIT_CALLS_LEVEL宏告知linker,将属于同一level的所有静态变量放到连续的一块内存中,组成一个元素类型为initcall_t的数组,该数组的起始地址放在类似__initcall5_start的变量中。

4. 在内核的初始化过程中,会通过调用 do_initcalls方法,遍历各个level里的各个函数指针,然后调用该指针指向的方法,即各模块的初始化方法。

以上是"Linux内核是怎么初始化各个模块的"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

变量 函数 方法 内存 内核 类型 模块 代码 地址 静态 面的 区域 指针 数组 元素 指向 脚本 相同 参数 所属 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技垄断 互联网金融 金融科技 互联网科技股票 致骋互联网科技 深圳期待互联网科技 科技伦理 互联网公司 深圳前海聚择互联网科技有限公司 顶级互联网科技成果有哪些 互联网科技研发水平 健康食品互联网创新科技园项目 如何创新互联网科技 互联网科技如何创业 中和天地互联网科技有限公司 山东子淼互联网科技有限公司 中国新注册互联网科技 陕西九四连强互联网科技有限公司 兰石爱特工业互联网科技分公司 广州三三互联网科技有限公司 科技互联网专业工作会议发言稿 互联网科技快速发展优美句子 美国互联网科技巨头监管 广州凡科互联网科技 亿人帮互联网科技 河南猎农互联网科技有限公司 宜昌元轩互联网科技有限公司 中国科技创新与世界互联网大会 银行科技岗属于互联网吗 半导体行业属于互联网高科技类吗 抖集互联网科技技术咨询 株洲百竞科技的互联网营销顾问
0