1.
在看linux核心代码的时候看到/init/main.c 里面的do_initcalls函数
static void __initdo_initcalls(void)
742 {
743 initcall_t *call;
744
745 for (call = __initcall_start; call <__initcall_end; call++)
746 do_one_initcall(*call);
747
748 /* Make sure there is no pending stuff from the initcall sequence */
749 flush_scheduled_work();
750 }
static void __initdo_one_initcall(initcall_t fn)
697 {
698 intcount =preempt_count();
699 ktime_tt0,t1,delta;
700 charmsgbuf[64];
701 intresult;
702
703 if (initcall_debug) {
704 print_fn_descriptor_symbol("calling %s/n", fn);
705 t0 =ktime_get();
706 }
707
708 result = fn();
709
710 if (initcall_debug) {
711 t1 =ktime_get();
712 delta =ktime_sub(t1,t0);
713
714 print_fn_descriptor_symbol("initcall %s", fn);
715 printk(" returned %d after %Ld msecs/n",result,
716 (unsigned long long) delta.tv64 >> 20);
717 }
718
719 msgbuf[0] = 0;
720
721 if (result && result != -ENODEV && initcall_debug)
722 sprintf(msgbuf,"error code %d ", result);
723
724 if (preempt_count() != count) {
725 strlcat(msgbuf,"preemption imbalance ", sizeof(msgbuf));
726 preempt_count() =count;
727 }
728 if (irqs_disabled()) {
729 strlcat(msgbuf,"disabled interrupts ", sizeof(msgbuf));
730 local_irq_enable();
731 }
732 if (msgbuf[0]) {
733 print_fn_descriptor_symbol(KERN_WARNING"initcall %s", fn);
734 printk(" returned with %s/n",msgbuf);
735 }
736 }
__initcall_start是在 arch目录中的相关CPU中的vmlinux.lds文件指定,如i386中位于arch/i386/vmlinux.lds中,至于在 __initcall_start和__initcall_end之间的是由函数声明__init指定
2.
察看/arch/i386/vmlinux.lds,发现一段代码
__initcall_start = .;
.initcall.init : { *(.initcall.init) }
__initcall_end = .;
跟我找的东西相关
使用info ld,察看相关资料,(最终发现太麻烦,到网上找了一个ld.pdf).发现有这么一
段介绍关于c++地联结构造器的使用,和这段用法相同
其含义时,是让__initcall_start指向代码节.initcall.init的节首,而__initcall_end
指向.initcall.init的节尾。
那么第一段代码从程序逻辑上得到了解释。
3。因为do_initcalls所作的是系统中有关于选择的驱动部分的初始化工作,那么具体是这些
函数指针数据怎样放到了.initcall.init节。
想起来了,还没有使用grep哈哈,在
grep -rn .initcall.init *
发现在include/linux/init.h:83:有这样一个定义
#define __init_call __attribute__ ((unused,__section__ (".initcall.init")))
娃哈哈哈
终于让我发现了
然后又发现了
#define __initcall(fn) /
static initcall_t __initcall_##fn __init_call = fn
4。问题是什么是__attribute__??,查找man gcc,找到关于__attribute__的定义
`section ("section-name")'
Normally, the compiler places the code it generates in the `text'
section. Sometimes, however, you need additional sections, or you
need certain particular functions to appear in special sections.
The `section' attribute specifies that a function lives in a
particular section. For example, the declaration:
extern void foobar (void) __attribute__ ((section ("bar")));
puts the function `foobar' in the `bar' section.
Some file formats do not support arbitrary sections so the
`section' attribute is not available on all platforms. If you
need to map the entire contents of a module to a particular
section, consider using the facilities of the linker instead.
他的意思就是使它建造一个在.initcall.init节的指向初始函数的指针
5。问题是##是什么意思?
查阅gcc的man page得知,她是用在可变参数使用宏定义的时候的
在这里也就是建立一个变量名称为所指向的函数的名称,并且前面加上
__initcall_.
6.然后看看成果
在/include/linux/init.c中有发现
#define module_init(x) __initcall(x);
看看很多驱动中都有类似
module_init(usb_init);
module_exit(usb_exit);
的代码,哈哈,这下明白了。
分析kernel的initcall函数
Author: Dongas
Data: 08-07-15
先来看看这些initcall函数的声明:
/* include/linux/init.h */
/* 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.
*/
#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)
#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)
#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
#define security_initcall(fn) /
static initcall_t __initcall_##fn /
__attribute_used__ __attribute__((__section__(".security_initcall.init"))) = fn
#define module_init(x) __initcall(x); ß从这里知道module_init的等级为6,相对靠后
#define module_exit(x) __exitcall(x);
可以发现这些*_initcall(fn)最终都是通过__define_initcall(level,fn)宏定义生成的。
__define_initcall宏定义如下:
#define __define_initcall(level,fn) /
static initcall_t __initcall_##fn __attribute_used__ /
__attribute__((__section__(".initcall" level ".init"))) = fn
这句话的意思为定义一个initcall_t型的初始化函数,函数存放在.initcall”level”.init section内。.initcall”level”.init section定义在vmlinux.lds内。
/* arch/arm/kernel/vmlinux.lds */
……
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
……
正好包括了上面init.h里定义的从core_initcall到late_initcall等7个level等级的.initcall”level”.init section. 因此通过不同的*_initcall声明的函数指针最终都会存放不同level等级的.initcall”level”.init section内。这些不同level的section按level等级高低依次存放。
下面我们再来看看,内核是什么时候调用存储在.initcall”level”.init section内的函数的。
内核是通过do_initcalls函数循环调用执行initcall.init section内的函数的,流程如下:
start_kernel -> rest_init -> kernel_thread -> init -> do_basic_setup -> do_initcalls
这里要分析两个函数: kernel_thread和do_initcalls,这两个函数都定义在init/main.c内
1) kernel_thread
1.static void noinline rest_init(void)
2. __releases(kernel_lock)
3.{
4. system_state = SYSTEM_BOOTING_SCHEDULER_OK;
5.
6. kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
7. numa_default_policy();
8. unlock_kernel();
9.
10. /*
11. * The boot idle thread must execute schedule()
12. * at least one to get things moving:
13. */
14. __preempt_enable_no_resched();
15. schedule();
16. preempt_disable();
17.
18. /* Call into cpu_idle with preempt disabled */
19. cpu_idle();
20.}
第6行通过kernel_thread创建一个内核线程执行init函数。(其实这里创建的即Linux的1号进程(init进程), 为linux中所有其他进程的父进程,有兴趣的可以自己查资料)
2) do_initcalls
1.static void __init do_initcalls(void)
2.{
3. initcall_t *call;
4. int count = preempt_count();
5.
6. for (call = __initcall_start; call
7. ……
8. result = (*call)();
9. ……
10. }
11.}
其中, initcall_t类型如下:
typedef int (*initcall_t)(void);
__initcall_start和__initcall_end定义在vmlinux.lds内,表示initcall section的起始和结束地址。
/* arch/arm/kernel/vmlinux.lds */
……
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
……
因此,上面6-10行代码的作用为按initcall level等级的顺序,依次循环调用预先存储在initcall section内的所有各个级别的初始化函数。这样,kernel的initcall函数的原理我们就搞清楚了。
最后要注意的是rest_init是在start_kernel函数内最后部分才被调用执行的,rest_init前包含了kernel一系列的初始化工作。另外,这些不同level等级的initcall.init section本身有一定的执行顺序,因此如果你的驱动依赖于特定的执行顺序的话需要考虑到这一点。
initcall机制原理及实践
说明:以下内容基于linux2.4.0
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
···········································································
printk(KERN_NOTICE "Kernel command line: %s/n", saved_command_line);
//打印内核命令行
parse_early_param();
parse_args("Booting kernel", command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
//解析由BOOT传递的启动参数
···········································································
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
start_kernel()中的函数rest_init()将创建第一个核心线程kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND),调用init()函数:
static int init(void * unused)-------------------
{
·······················
do_basic_setup();
······················
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) { //判断在启动时是否指定了init参数
//如果指定则执行用户init进程,成功将不会返回
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults.../n", execute_command);
}
/* 如果没有指定init启动参数,则查找下面的目录init进程,成功将不会返回,否则打印出错信息 */
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel.");
}
继而调用函数do_basic_setup()(此时与体系结构相关的部分已经初始化完了,现在开始初始化设备了):
/*
* Ok, the machine is now initialized. None of the devices
* have been touched yet, but the CPU subsystem is up and
* running, and memory and process management works.
*
* Now we can finally start doing some real work..
*/
static void __init do_basic_setup(void)-----------------
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init(); //建立设备模型子系统
#ifdef CONFIG_SYSCTL
sysctl_init();
#endif
/* Networking initialization needs a process context */
sock_init();
do_initcalls(); //系统初始化(包括设备,文件系统,内核模块等)
}
<./drivers/base/init.c>-------------------------
/**
* driver_init - initialize driver model.
*
* Call the driver model init functions to initialize their
* subsystems. Called early from init/main.c.
*/
void __init driver_init(void)
{
/* These are the core pieces */
devices_init();
<./drivers/base/core.c>-------------
int __init devices_init(void)
{
return subsystem_register(&devices_subsys);
}
-----------------------
buses_init();
classes_init();
firmware_init();
/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();
system_bus_init();
cpu_dev_init();
memory_dev_init();
attribute_container_init();
}
---------------------------
extern initcall_t __initcall_start[], __initcall_end[];
static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count();
for (call = __initcall_start; call < __initcall_end; call++) {
··················
(*call)(); //调用一系列初始化函数
···················
}
---------------------------
__initcall_start和__initcall_end界定了存放初始化函数指针区域的起始地址,即从__initcall_start开始到 __initcall_end结束的区域中存放了指向各个初始化函数的函数指针。 由 (*call)()完成各个部分的初始化工作,且便于扩充。具体实现如下:
<./arch/arm/kernel/vmlinux.lds.S>-----------------
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
<./include/linux/init.h>---------------------
#ifndef MODULE /* 如果驱动模块静态编译进内核 */
···············································
/* 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.
*/
#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)
#define arch_initcall(fn) __define_initcall("3",fn)
//此处初始化了设备
/*----eg:arch_initcall(at91sam9261_device_init)---
static int __init at91sam9261_device_init(void)
{
at91_add_device_udc();
at91_add_device_dm9000();
armebs3_add_input_buttons();
return platform_add_devices(at91sam9261_devices, ARRAY_SIZE(at91sam9261_devices));
}
------------------------*/
#define subsys_initcall(fn) __define_initcall("4",fn)
#define fs_initcall(fn) __define_initcall("5",fn)
#define device_initcall(fn) __define_initcall("6",fn)
//此处初始化了静态编译的驱动模块
#define late_initcall(fn) __define_initcall("7",fn)
#define __initcall(fn) device_initcall(fn)
/**
* 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);
//静态编译的驱动模块作为device_initcall在内核启动就被do_initcalls
/**
* 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 如果驱动模块动态加载入内核 */
···············································
/* Each module must use one module_init(), or one no_module_init */
#define module_init(initfn) /
static inline initcall_t __inittest(void) /
{ return initfn; } /
int init_module(void) __attribute__((alias(#initfn)));
//insmod 是通过系统调用sys_init_module(const char *name_user, struct module *mod_user)
//将动态驱动模块载入到内核空间
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) /
static inline exitcall_t __exittest(void) /
{ return exitfn; } /
void cleanup_module(void) __attribute__((alias(#exitfn)));