一 Driver初始化
每个驱动程序都有自己的init函数,但是这些init函数是如何初始化的?
两种假设:
1、放到一个总的init函数,我们把init函数填入这个函数?
2、放到一个专用的init文件,我们把init函数填入这个头文件?
Linux处理方式:
—在编译的镜像文件中定义一个INIT_CALL段(section),存放init函数的指针
—用module_init等宏定义将init函数填入对应INIT_CALL段中;
—用不同的init level实现初始化先后顺序的分级;
1 INIT_CALL段的定义
源码位置:
Kernel/include/asm-generic/vmlinux.lds.h
1.1 添加INIT_CALLS到initdata段
#define INIT_DATA_SECTION(initsetup_align) \
.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { \
INIT_DATA \
INIT_SETUP(initsetup_align) \
INIT_CALLS \
CON_INITCALL \
SECURITY_INITCALL \
INIT_RAM_FS \
}
1.2 定义INIT_CALLS
#define INIT_CALLS \
VMLINUX_SYMBOL(__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) \
VMLINUX_SYMBOL(__initcall_end) = .;
1.3 定义INIT_CALLS_LEVEL
#define INIT_CALLS_LEVEL(level) \
VMLINUX_SYMBOL(__initcall##level##_start) = .; \
KEEP(*(.initcall##level##.init)) \
KEEP(*(.initcall##level##s.init))
举例展开后level=6
#define INIT_CALLS_LEVEL(6) \
VMLINUX_SYMBOL(__initcall6_start) = .; \
KEEP(*(.initcall6.init)) \
KEEP(*(.initcall6s.init))
2 init宏的展开
2.1 源码解析
源码位置:
include/linux/module.h
#ifndef MODULE
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
#else /* MODULE */
/*
* In most cases loadable modules do not need custom
* initcall levels. There are still some valid cases where
* a driver may be needed early if built in, and does not
* matter when built as a loadable module. Like bus
* snooping debug drivers.
*/
#define early_initcall(fn) module_init(fn)
#define core_initcall(fn) module_init(fn)
#define core_initcall_sync(fn) module_init(fn)
#define postcore_initcall(fn) module_init(fn)
#define postcore_initcall_sync(fn) module_init(fn)
#define arch_initcall(fn) module_init(fn)
#define subsys_initcall(fn) module_init(fn)
#define subsys_initcall_sync(fn) module_init(fn)
#define fs_initcall(fn) module_init(fn)
#define fs_initcall_sync(fn) module_init(fn)
#define rootfs_initcall(fn) module_init(fn)
#define device_initcall(fn) module_init(fn)
#define device_initcall_sync(fn) module_init(fn)
#define late_initcall(fn) module_init(fn)
#define late_initcall_sync(fn) module_init(fn)
#define console_initcall(fn) module_init(fn)
#define security_initcall(fn) module_init(fn)
/* Each module must use one module_init(). */
#define module_init(initfn) \
static inline initcall_t __maybe_unused __inittest(void) \
{ return initfn; } \
int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __maybe_unused __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __copy(exitfn) __attribute__((alias(#exitfn)));
#endif
源码位置:
源码位置:
/*
* 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(fn) device_initcall(fn)
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn
#define console_initcall(fn) ___define_initcall(fn, con, .con_initcall)
#define security_initcall(fn) ___define_initcall(fn, security, .security_initcall)
#ifdef CONFIG_LTO_CLANG
/*
* With LTO, the compiler doesn't necessarily obey link order for
* initcalls, and the initcall variable needs to be globally unique
* to avoid naming collisions. In order to preserve the correct
* order, we add each variable into its own section and generate a
* linker script (in scripts/link-vmlinux.sh) to ensure the order
* remains correct. We also add a __COUNTER__ prefix to the name,
* so we can retain the order of initcalls within each compilation
* unit, and __LINE__ to make the names more unique.
*/
#define ___lto_initcall(c, l, fn, id, __sec) \
static initcall_t __initcall_##c##_##l##_##fn##id __used \
__attribute__((__section__( #__sec \
__stringify(.init..##c##_##l##_##fn)))) = fn;
#define __lto_initcall(c, l, fn, id, __sec) \
___lto_initcall(c, l, fn, id, __sec)
#define ___define_initcall(fn, id, __sec) \
__lto_initcall(__COUNTER__, __LINE__, fn, id, __sec)
#else
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn;
#endif
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
1.attribute 机制
GNU C 的一大特色就是__attribute__ 机制。attribute 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute )
attribute 语法格式为:attribute ((attribute-list))
attribute((section(“section_name”))),其作用是将作用的函数或数据放入指定名为"section_name"对应的段中。
2“#”符号的使用
(“initcall” #id “init”)中的‘#’的作用是将宏参数id转换为一个字符串,整条语句就是定义__section__的name是“initcallXinit”
3.attribute((used))的作用
使用前提是在编译器编译过程中,如果定义的符号没有被引用,编译器就会对其进行优化,不保留这个符号,而__attribute__((used))的作用是告诉编译器这个静态符号在编译的时候即使没有使用到也要保留这个符号。
整条宏定义的作用是
定义一个名为__initcall_##fn##id的结构体,他的值=fn这个函数指针,而段属性则规定了已定义的结构体会放在一起。
attribute((section(“.initcall” #id “.init”)))编译参数,通知编译器把__initcall_fnid写入section initcallid.init中
module_init()初始化过程:
module_init(x)
__initcall(x)
device_initcall(fn)
__define_initcall(fn, 6)
___define_initcall(fn, id, .initcall##id)
fn
2.2 module_init展开
1 定义了一个函数指针:__initcall_fnid
2 函数指针指向:fn
3 函数指针写入固定的section:.initcallid.init
展开后:
static initcall_t __initcall_fnid = fn
2.3 System.map中INIT_CALL段:
build/System.map
c11647c0 T __initcall_start
c11647c0 t __initcall_trace_init_flags_sys_exitearly
c11647c0 T __setup_end
c11647c4 t __initcall_trace_init_flags_sys_enterearly
c11647c8 t __initcall_cpu_suspend_alloc_spearly
c11647cc t __initcall_init_static_idmapearly
c11647d0 t __initcall_spawn_ksoftirqdearly
c11647d4 t __initcall_check_cpu_stall_initearly
c11647d8 t __initcall_srcu_bootup_announceearly
c11647dc t __initcall_rcu_spawn_gp_kthreadearly
c11647e0 t __initcall_init_eventsearly
c11647e4 t __initcall_init_trace_printkearly
c11647e8 t __initcall_event_trace_enable_againearly
c11647ec t __initcall_cpu_clock_initearly
c11647f0 t __initcall_init_memory_dumpearly
c11647f4 t __initcall_rand_initializeearly
c11647f8 t __initcall_dummy_timer_registerearly
c11647fc t __initcall_initialize_ptr_randomearly
c1164800 T __initcall0_start
c1164800 t __initcall_bpf_jit_charge_init0
System.map是一个与Linux内核版本相关的文件,它包含了内核符号和地址的映射关系。当内核编译完成后,会生成一个System.map文件,该文件记录了内核中每个函数和变量的地址信息。
System.map文件在调试和故障排除时非常有用。它可以帮助开发人员定位和解决内核相关问题,例如查找函数的地址、符号的地址以及其在内核中的位置等。
3 __initcall_fnid的执行
源码位置:
<