一、Linux内核初始化段介绍
1、Linux内核初始化段一览表
段名 | 标记方法 | 功能描述 |
---|---|---|
.init.text | __init | 初始化代码 |
.init.data | __initdata | 初始化数据 |
.init.setup | early_param/__setup/__setup_param | 启动参数 |
.con_initcall | console_initcall | 控制台初始化 |
.initcallearly.init | early_initcall | 早期initcall函数 |
.initcall0.init | pure_initcall | 第0级 |
.initcall1.init/.initcall1s.init | core_initcall/core_initcall_sync | 第1级 |
.initcall2.init/.initcall2s.init | postcore_initcall/postcore_initcall_sync | 第2级 |
.initcall3.init/.initcall3s.init | arch_initcall/arch_initcall_sync | 第3级 |
.initcall4.init/.initcall4s.init | subsys_initcall/subsys_initcall_sync | 第4级 |
.initcall5.init/.initcall5s.init | fs_initcall/fs_initcall_sync | 第5级 |
.initcallrootfs.init | rootfs_initcall | Rootfs级 |
.initcall6.init/.initcall6s.init | device_initcall/device_initcall_sync | 第6级 |
.initcall7.init/.initcall7s.init | late_initcall/late_initcall_sync | 第7级 |
2、初始化段大致有四种:
第一类是初始化代码,也就是用__init标记的函数代码,放置在.init.text段;
第二类是初始化数据,也就是用__initdata标记的全局变量,放置在.init.data段;
第三类是启动参数,也就是用early_param()、__setup()、__setup_param()定义的参数变量,放置在.init.setup段;
第四类是用各种*_initcall()定义的函数,称为initcall函数,共有10种,有一种是console_initcall(),其余9种分为9个级别放置在各种.initcall*.init段中。
3、重点介绍第四类初始化段
3.1、console_initcall
console_initcall修饰的函数直接由console_init函数调用;console_init()由内核启动C语言入口start_kernel()直接调用
3.2、early_initcall和剩余9种initcall
第1~7级的initcall函数分为两个子级别:一个不带s,用*_initcall()来定义函数;另一个带s,用*_initcall_sync()来定义函数。在同一级别里,不带s的比带s的子级别先执行。
在9个级别里,最早被执行的是早期initcall函数,然后是第0级initcall,第1级initcall函数,以此类推。在Linux-2.6.20版本,引入了一个特殊的Rootfs级别的initcall,介于fs_initcall和device_initcall之间,其主要用于建立初始化内存盘(initrd/initramfs)中的根文件系统。
除了这9个级别,还有一种用module_init()定义的initcall函数,它跟这9级函数有一个对应关系:如果module_init()定义的函数被直接编译在内核里面,那么它等价于device_initcall();如果编译成模块,则跟其他initcall定义的函数编译成模块的情况相同(如下说明)。
如果*_initcall()修饰的函数被编译成模块,在内核启动完成后,由1号进程间接调用。
二、initcall函数的执行时机
1、early_initcall和其余9种initcall函数在1号进程的启动代码中执行,其执行关系如下:
kernel_init()
kernel_init_freeable();
do_pre_smp_initcalls(); //处理early_initcall()定义的initcall函数
do_basic_setup();
do_initcalls(); //执行其他的8个级别的initcall函数
如上所示:
early_initcall : do_pre_smp_initcalls();
其他initcall : do_basic_setup—>do_initcalls();
2、内核中对initcall的宏定义
内核源码根目录 include/linux/init.h
第195行
196 #define pure_initcall(fn) __define_initcall(fn, 0)
197
198 #define core_initcall(fn) __define_initcall(fn, 1)
199 #define core_initcall_sync(fn) __define_initcall(fn, 1s)
200 #define postcore_initcall(fn) __define_initcall(fn, 2)
201 #define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
202 #define arch_initcall(fn) __define_initcall(fn, 3)
203 #define arch_initcall_sync(fn) __define_initcall(fn, 3s)
204 #define subsys_initcall(fn) __define_initcall(fn, 4)
205 #define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
206 #define fs_initcall(fn) __define_initcall(fn, 5)
207 #define fs_initcall_sync(fn) __define_initcall(fn, 5s)
208 #define rootfs_initcall(fn) __define_initcall(fn, rootfs)
209 #define device_initcall(fn) __define_initcall(fn, 6)
210 #define device_initcall_sync(fn) __define_initcall(fn, 6s)
211 #define late_initcall(fn) __define_initcall(fn, 7)
212 #define late_initcall_sync(fn) __define_initcall(fn, 7s)
213
214 #define __initcall(fn) device_initcall(fn)
215
216 #define __exitcall(fn)
217 static exitcall_t _exitcall##fn __exit_call = fn
218
* 驱动模块的module_init实际是调用的device_initcall去修饰这个函数
3、内核链接脚本记录了各种init代码的存放位置
arch/mips/kernel/vmlinux.lds
60行
. = ALIGN(16384); /* Init code and data */
__init_begin = .;
. = ALIGN(16384); .init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text) _einittext = .; }
.init.data : AT(ADDR(.init.data) - 0) { *(.init.data) *(.init.rodata) . = ALIGN(8); __start_ftrace_events = .; *(_ftrace_events) __stop_ftrace_events = .; . = ALIGN(8); __start_syscalls_metadata = .; *(__syscalls_metadata) __stop_syscalls_metadata = .; . = ALIGN(8); __iommu_of_table = .; *(__iommu_of_table) *(__iommu_of_table_end) . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = .; . = ALIGN(8); __irqchip_begin = .; *(__irqchip_of_table) *(__irqchip_of_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) }
. = ALIGN(4);
.mips.machines.init : AT(ADDR(.mips.machines.init) - 0) {
__mips_machines_start = .;
(.mips.machines.init)
__mips_machines_end = .;
}
/ .exit.text is discarded at runtime, not link time, to deal with
- references from .rodata
*/
.exit.text : {
*(.exit.text)
}
.exit.data : {
*(.exit.data)
}
. = ALIGN(16384); .data…percpu : AT(ADDR(.data…percpu) - 0) { __per_cpu_load = .; __per_cpu_start = .; *(.data…percpu…first) . = ALIGN(16384); __per_cpu_user_mapped_start = .; *(.data…percpu…user_mapped…page_aligned) . = ALIGN(1 << 6); *(.data…percpu…user_mapped) *(.data…percpu…user_mapped…shared_aligned) __per_cpu_user_mapped_end = .; . = ALIGN(16384); *(.data…percpu…page_aligned) . = ALIGN(1 << 6); *(.data…percpu…readmostly) . = ALIGN(1 << 6); *(.data…percpu) (.data…percpu…shared_aligned) __per_cpu_end = .; }
/ - Align to 64K in attempt to eliminate holes before the
- .bss…swapper_pg_dir section at the start of .bss. This
- also satisfies PAGE_SIZE alignment as the largest page size
- allowed is 64K.
*/
. = ALIGN(0x10000);
__init_end = .;