一、内核执行流程
内核初始化设备驱动的过程:
第一个C函数从main.c (kernel\init)开始,暂且不论汇编文件
start_kernel()->rest_init()->do_basic_setup()->do_initcalls()
函数do_initcalls如下:
static void __init do_initcalls(void)
{
initcall_t *fn;
/*循环调用__initcall_start与__initcall_end之间函数*/
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
二、而关于module_init(x)是如何被加载的
1.#define module_init(x) __initcall(x);
2.#define __initcall(fn) device_initcall(fn)
3.#define device_initcall(fn) __define_initcall("6",fn)
4.#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
从而可以得出:
module_init(x) = static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = x
首先分析:
1.其中 initcall_t 是个函数指针类型:typedef int (*initcall_t)(void);
2.属性 __attribute__((__section__())) 则表示把对象放在一个这个由括号中的名称所指代的section中。
所以这个宏定义的的含义是:
1) 声明一个名称为__initcall_##fn的函数指针(其中##表示替换连接,);
2) 将这个函数指针初始化为fn;
3) 编译的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init"
的section中(比如level="1",代表这个section的名称是 ".initcall 1 .init")。
4)举例说明:假如我们有这个宏定义module_init(kpd_mod_init);那么经过预编译替换之后就发生如下变化
将函数 kpd_mod_init 的首地址放到section的名称是 ".initcall 6 .init" 的段中去。
注意:为什么是6?因为我们这里使用的是device,#define device_initcall(fn) __define_initcall("6",fn)
会被替换成6。具体对应规则如下:
#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)
在连接脚本段.initcall中将依次存放着
".initcall 1 .init"
".initcall 2 .init"
".initcall 3 .init"
".initcall 4 .init"
".initcall 5 .init"
".initcall 6 .init"
".initcall 7 .init"
三、现在回到之前提到的
do_one_initcall(*fn);
注意这也是个回调函数,关于回调函数的进一步说明可以参考有关资料
不难得出,在这里将会依次执行,上述提到的那7个段中的函数,进行有关的初始化。
假如您希望某个初始化函数在内核初始化阶段就被调用,那么您就应该使用宏__define_initcall(level,fn)
或其7个衍生宏来把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的section 中。 内核初始化
时的do_initcalls()将从这个section中按顺序找到这些函数来执行。
几个注意点:
1.module_init这个是只有在该模块以module方式存在的时候才有意义,当你加载该模块的时候才会去调用它
,加载哪个模块,就调用哪个模块的module_init。如果那个模块本身直接编译进内核了,那它的代码就直接
放在内核中相应的区域了,系统启动的时候自动就调用这些函数了。在我使用的MTK平台都是将其编译进内核
了的。
2.*(.initcall6.init)在这里.initcall6.init是一个数据段的名称,这句话的意思是所有声明要放在这个段
的数据都将按顺序存放在这个段里。每个module_init都将定义一个函数指针,这些函数指针被指定要放在
.initcall6.init这个段里,它们将按顺序排列。这样在内核启动的时候,它会根据这些函数指针按顺序调用
所有的函数。
四、接下来可以开始分析我们的kpd驱动
1.我们知道加入我们把驱动编译进内核的话,那么内核启动初始化的阶段会执行module_init(kpd_mod_init);
也就是kpd_mod_init这个函数Kpd.c (mediatek\platform\mt6575\kernel\drivers\keypad)
2.在这个初始化函数中,会有如下流程
kpd_mod_init() -> platform_driver_register(&kpd_pdrv) -> kpd_pdrv_probe(struct platform_device *pdev)
注意了,上述流程将是内核启动后自动进行的初始化。当然我们假设driver和device是可以在bus上绑定的。
3.所以接下来我们可以看看这个kpd_pdrv_probe到底是做了些什么事情。