fs_initcall、early_initcall、__init宏
来源:互联网 发布:mac机械键盘推荐 编辑:程序博客网 时间:2024/06/04 00:30
fs_initcall、early_initcall...分析
在内核代码中经常可以看到类似fs_initcall\early_initcall\late_initcall这样的宏,这些宏有什么作用?如何实现的?下面来具体分析
具体定义
在include/linux/init.h
中可以找到这些宏的定义
/* * Early initcalls run before initializing SMP. * * Only for built-in code, not modules. */#define early_initcall(fn) __define_initcall(fn, early)/* * A "pure" initcall has no dependencies on anything else, and purely * initializes variables that couldn't be statically initialized. * * This only exists for built-in code, not for modules. * Keep main.c:initcall_level_names[] in sync. */#define pure_initcall(fn) __define_initcall(fn, 0)#define core_initcall(fn) __define_initcall(fn, 1)#define core_initcall_sync(fn) __define_initcall(fn, 1s)#define postcore_initcall(fn) __define_initcall(fn, 2)#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)#define arch_initcall(fn) __define_initcall(fn, 3)#define arch_initcall_sync(fn) __define_initcall(fn, 3s)#define subsys_initcall(fn) __define_initcall(fn, 4)#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)#define fs_initcall(fn) __define_initcall(fn, 5)#define fs_initcall_sync(fn) __define_initcall(fn, 5s)#define rootfs_initcall(fn) __define_initcall(fn, rootfs)#define device_initcall(fn) __define_initcall(fn, 6)#define device_initcall_sync(fn) __define_initcall(fn, 6s)#define late_initcall(fn) __define_initcall(fn, 7)#define late_initcall_sync(fn) __define_initcall(fn, 7s)
可以看到它们利用了__define_initcall宏:
/* initcalls are now grouped by functionality into separate * subsections. Ordering inside the subsections is determined * by link order. * For backwards compatibility, initcall() puts the call in * the device init subsection. * * The `id' arg to __define_initcall() is needed so that multiple initcalls * can point at the same handler without causing duplicate-symbol build errors. */#define __define_initcall(fn, id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" #id ".init"))) = fn; \ LTO_REFERENCE_INITCALL(__initcall_##fn##id)
从上面的注释信息可以看出:initcalls这类函数会被分别放置到不同的subsections中,subsections中的先后顺序由linker来决定(但是subsections之间的顺序是能够确定的,由vmlinux.lds.S指定);此外id的作用是防止duplicate-symbol错误,如果没有这个参数,那么两类不同的initcall使用同一个fn的话会发生symbol重复定义的错误。__define_initcall宏
的作用是定义了一个initcall_t类型(函数指针)的变量,并将它放在名为.initcall#id.init
的subsection中。该变量指向函数fn。
这样一来的话,这些subsection中存储的都是些函数指针,可以通过这些值来调用对应的函数。那么何时调用这些函数?
调用时机
首先,我们需要理解几个变量:
static initcall_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end,};
这几个__initcall开头的变量值实际上是在link阶段就被确定了。在内核的链接脚本中会记录这几个subsection的起始、结束地址,分别存在上述变量中。这样的话就可以在程序中通过这些变量来访问这些subsections。
在内核启动初始化过程中会有下列流程:
start_kernel --> rest_init
在rest_init函数中会创建一个内核线程,执行kernel_init
,
kernel_init --> kernel_init_freeable --> do_pre_smp_initcalls |---> do_basic_setup --> do_initcalls
early_initcall
需要在SMP初始化之前被调用,其在do_pre_smp_initcalls
函数中被调用:
static void __init do_pre_smp_initcalls(void){ initcall_t *fn; for (fn = __initcall_start; fn < __initcall0_start; fn++) do_one_initcall(*fn);}
这个for循环只对early_initcall定义的函数进行调用,do_one_initcall
函数中会调用fn指向的函数。
同理,在do_initcalls
函数中会对其他initcall函数进行调用。
module_init
我们现在知道了上面的initcall是在内核初始化过程中被调用的,因此使用initcall定义的函数一定是built-in的。
在模块编程的时候会使用到module_init
,它也有两种定义,在include/linux/module.h
中:
#ifndef MODULE......#define module_init(x) __initcall(x);......#else /* MODULE */....../* Each module must use one module_init(). */#define module_init(initfn) \ static inline initcall_t __inittest(void) \ { return initfn; } \ int init_module(void) __attribute__((alias(#initfn)));......#endif
对于built-in的模块,其module_init
对应的是__initcall
,最终对应的是device_initcall
(在include/linux/init.h
中):
#define __initcall(fn) device_initcall(fn)
这样,在内核初始化过程中就会调用到模块的初始化函数。
对于load的模块,我们暂时先不关心,以后分析模块子系统时再看。
链接脚本上相关内容
上面提到过各个subsections之间的顺序在链接脚本中指定的,我们现在就来看看链接脚本中有关的内容。在arch/x86/kernel/vmlinux.lds.S
中有以下内容:
......#include <asm-generic/vmlinux.lds.h>......SECTIONS{ ...... INIT_DATA_SECTION(16) ......}
进入include/asm-generic/vmlinux.lds.h
文件,我们可以看到INIT_DATA_SECTION
的定义:
#define INIT_DATA_SECTION(initsetup_align) \ .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { \ ...... INIT_CALLS \ ...... }
而INIT_CALLS
的定义如下:
#define INIT_CALLS_LEVEL(level) \ VMLINUX_SYMBOL(__initcall##level##_start) = .; \ *(.initcall##level##.init) \ *(.initcall##level##s.init) \#define INIT_CALLS \ VMLINUX_SYMBOL(__initcall_start) = .; \ *(.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) \ VMLINUX_SYMBOL(__initcall_end) = .;
这个宏中的顺序就是在最终内核镜像中subsections之间的顺序,并且可以看到对于每个subsection都导出了xxx_start值,而整个initcall的范围是_initcall_start、_initcall_end,这些变量在上面处理initcall的时候会被使用到。
__init分析
__init
也是在include/linux/init.h
中定义的:
#define __init __section(.init.text) __cold notrace
通过__init
定义的函数表示是在内核初始化期间被调用的,当初始化完成之后会将他们释放掉。它指明将该函数放到.init.text
section中。那么这个section放置在什么地方?
在arch/x86/kernel/vmlinux.lds.S
中有以下内容:
......#include <asm-generic/vmlinux.lds.h>......SECTIONS{ ...... INIT_TEXT_SECTION(PAGE_SIZE) ...... INIT_DATA_SECTION(16) ......}
INIT_DATA_SECTION
我们在上面已经分析过了,这里还有INIT_TEXT_SECTION
,这个宏在vmlinux.lds.h
中定义:
......#define INIT_TEXT \ *(.init.text) \ MEM_DISCARD(init.text)......#define INIT_TEXT_SECTION(inittext_align) \ . = ALIGN(inittext_align); \ .init.text : AT(ADDR(.init.text) - LOAD_OFFSET) { \ VMLINUX_SYMBOL(_sinittext) = .; \ INIT_TEXT \ VMLINUX_SYMBOL(_einittext) = .; \ }......
可以看到上述是对.init.text section如何放置的描述,并且导出了_sinittext、_einittext分别表示此section的起始、结束地址。
链接脚本展开后最终的效果如下:
* SECTIONS* {* . = START;* __init_begin = .;* HEAD_TEXT_SECTION* INIT_TEXT_SECTION(PAGE_SIZE)* INIT_DATA_SECTION(...)* PERCPU_SECTION(CACHELINE_SIZE)* __init_end = .;** _stext = .;* TEXT_SECTION = 0* _etext = .;** _sdata = .;* RO_DATA_SECTION(PAGE_SIZE)* RW_DATA_SECTION(...)* _edata = .;** EXCEPTION_TABLE(...)* NOTES** BSS_SECTION(0, 0, 0)* _end = .;** STABS_DEBUG* DWARF_DEBUG** DISCARDS // must be the last* }
我们现在只关注和init section相关的,即被_init_begin和_init_end所包围的内容,这篇我们关注的initcalls和_init可以看到都包括在其中。
在上面我们提到了内核初始化尾声创建了一个kernel_init
内核线程,其中会执行free_initmem
函数:
void free_initmem(void){ free_init_pages("unused kernel", (unsigned long)(&__init_begin), (unsigned long)(&__init_end));}
可以看到它会将_init_begin和_init_end之间的内存释放掉。
- fs_initcall、early_initcall、__init宏
- __init和__exit宏
- __init和__exit宏
- linux中的__init 宏
- __init
- __init
- __init
- module_init, fs_initcall
- Linux中__init、__devinit等初始化宏
- Linux中__init、__devinit等初始化宏
- linux内存中的__init和__exit宏
- Linux中__init、__devinit等初始化宏
- Linux中__init、__devinit等初始化宏
- 内核初始化优化宏(__init, __devinit)
- Linux内核中的宏:__init and __exit
- 内核初始化优化宏(__init , __devinit ,etc.)
- Linux中__init、__devinit等初始化宏
- __init和__exit宏的作用
- Qt动态链接库的生成和应用
- ArrayAdapter的简单使用
- Launcher3源码分析 — 数据加载过程
- greendao->报错:java.lang.NoClassDefFoundError: database.dao.DaoMaster
- 关于“静态变量不能跨函数使用”的一个疑问
- fs_initcall、early_initcall、__init宏
- Ducci 队列
- Windows环境下java编译出错的解决方法
- OpenSSH 仅用于端口转发(仅使用SSH隧道,禁止Shell)
- 孤华暗香的Python Spider,Updating
- ECMAScript 引用类型
- 把图片变为圆形的方法(QQ头像)
- 北大OJ1000
- QQl聊天消息