module_init()这个接口应该是驱动开发人员常用的一个接口了,在内核源码里也可以看到无数。
下午详细跟了一下内核代码,顺便分享一下,也算是自己做个记录。
源代码:MTK ALPS 4.4
首先是宏module_init的原始定义:
#define module_init(x) __initcall(x);
定义成了__initcall(),再看__initcall():
#define __initcall(fn) device_initcall(fn)
定义成了device__initcall(),再看device__initcall():
#define device_initcall(fn) __define_initcall("6",fn)
定义成了__define_initcall(“6”,fn),再看__define_initcall(“6”,fn):
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
可以看到module_init()宏最终把传进来的函数fn组装成了__initcall_fn,并且把函数fn定义到代码段.initcall6.init里面。
找到含有INIT_CALLS字符的文件vmlinux.lds.h:
#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) = .;
可以看到.initcall6.init是在这里定义的。打开vmlinux.lds.S,可以看到INITCALLS:
.init.data : {
#ifndef CONFIG_XIP_KERNEL
INIT_DATA
#endif
INIT_SETUP(16)
INIT_CALLS
CON_INITCALL
SECURITY_INITCALL
INIT_RAM_FS
}
vmlinux.lds.S是编译内核时使用的链接脚本,从这里可以看出,INIT_CALLS所定义的所有代码均被放到了.init.data这个节里面。即,我们使用module_init()来初始化模块注册函数fn,这个fn将会被放到.init.data节里面,这里就是代码在存储空间的组织位置。再回过头来看看其他init的定义:
#define __define_initcall(level,fn) static initcall_t __initcall_##fn __attribute_used__ __attribute__((__section__(".initcall" level ".init"))) = fn
#define core_initcall(fn) __define_initcall("1",fn)
#define postcore_initcall(fn) __define_initcall("2",fn)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define arch_initcall(fn) __define_initcall("3",fn)
#define subsys_initcall(fn) __define_initcall("4",fn)
#define fs_initcall(fn) __define_initcall("5",fn)
#define device_initcall(fn) __define_initcall("6",fn)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define late_initcall(fn) __define_initcall("7",fn)
#define __initcall(fn) device_initcall(fn)
#define __exitcall(fn) static exitcall_t __exitcall_##fn __exit_call = fn
#define console_initcall(fn) static initcall_t __initcall_##fn __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn
可以看到,其他的init的也是通过这样的方法存放到存储空间的。内核在起动的时候,从存储空间里面按顺序获取代码段,并且执行。接下来我们来详细看看这块代码是怎么样跑起来的。
该部分的工作是在内核的init进程当中完成的,看函数kern_init():
static int __init kernel_init(void * unused)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/* Now the scheduler is fully set up and can do blocking allocations */
gfp_allowed_mask = __GFP_BITS_MASK;
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_HIGH_MEMORY]);
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask);
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
**do_basic_setup();**
/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
#ifdef CONFIG_MTK_HIBERNATION
// IPO-H, move here for console ok after hibernaton resume
software_resume();
#endif
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*/
init_post();
return 0;
}
该部分工作在函数do_basic_setup()里面完成,该函数如下:
static void __init do_basic_setup(void)
{
cpuset_init_smp();
usermodehelper_init();
shmem_init();
driver_init();
init_irq_proc();
do_ctors();
usermodehelper_enable();
**do_initcalls();**
random_int_secret_init();
}
再看:
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
可以看出,这里是对每个level进行启动,再详细看do_initcalls的实现。
static void __init do_initcall_level(int level)
{
extern const struct kernel_param __start___param[], __stop___param[];
initcall_t *fn;
strcpy(static_command_line, saved_command_line);
parse_args(initcall_level_names[level],
static_command_line, __start___param,
__stop___param - __start___param,
level, level,
repair_env_string);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
**do_one_initcall(*fn);**
}
do_one_initcall(*fn)这个函数会继续往下,直接调用函数。