内核启动时,设备及驱动初始化的实现( 版本2.6.35.4)
参考了网页:http://blog.csdn.net/lemon_fantasy/archive/2009/02/17/3900590.aspx
http://blog.chinaunix.net/u2/68846/showart_694377.html
Uboot完成系统的引导并将Linux内核拷贝到内存之后,bootm -> do_bootm_linux()跳转到kernel的起始位置;压缩过的kernel入口在arch/x86/boot/compressed/head_32.S; 在head_32.S的startup_32()调用decompress_kernel(arch/x86/boot/compressed/Misc.c);解压后的映像就被移动到从物理地址0x00100000开始的最终位置,然后跳转到物理地址0x00100000执行。在startup_32执行的最后,将跳转到start_kernel()函数。start_kernel()中完成了一系列系统初始化,设备及驱动的注册即在此时完成
///<./init/main.c>-------------------------
main.c中的start_kernel()中的函数rest_init()将创建第一个核心线程kernel_thread(kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND)),并调用kernel_init函数:
static int __init kernel_init(void * unused)
{
......
do_basic_setup();
........
}
而:
static void __init do_basic_setup(void)
{
init_workqueues();
cpuset_init_smp();
usermodehelper_init();
init_tmpfs();
driver_init(); //建立设备模型子系统
init_irq_proc();
do_ctors();
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 */
devtmpfs_init();
devices_init();
buses_init();
classes_init();
firmware_init();
hypervisor_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();
}
其中执行了do_initcalls()(init/main.c),那它的作用是什么呢?
---------------------------
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
static void __init do_initcalls(void)
{
initcall_t *fn;
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();
}
//include/linux/init.h
----------------------------------------------------------------
__initcall_start和__initcall_end界定了存放初始化函数指针区域的起始地址,即从__initcall_start开始到 __initcall_end结束的区域中存放了指向各个初始化函数的函数指针。 由 (*fn)()完成各个部分的初始化工作,且便于扩充。具体实现如下:
__initcall_start = .;
*(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
__initcall_end = .;
.......................................
/* 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(level,fn,id) /
static initcall_t __initcall_##fn##id __used /
__attribute__((__section__(".initcall" level ".init"))) = fn
参考GCC说明,这段话的意思就是说所有以__define_initcall前缀定义的函数在链接过程中都放到名字为.initcall.init的段(section)里面。OK,有点味道了,也就是说,如果我们给一个函数冠以__define_initcall,那么它在编译链接的时候就会放到.initcall.init这个段里面。而上面这段循环do_initcalls()中所做的事情就很清楚了,它从段的首地址开始,依次执行每一个函数,直到段尾为止。
例如:
.....// 初始化设备
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
........
那些要注册的外围模块的初始化程序是不是都是定义成__define_initcall类型的呢?正如我们所料,查看各个模块我们会发现其初始化函数x会被定义成为module_init(x),在/include/linux/init.h中它定义如下:
/**
* 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);
这段代码说module_init(x)等价于__initcall(x),而__initcall(x)表示函数x是静态的具有__define_initcall性质的函数(这里名字比较多,容易看乱),因此在链接时,它会被放在.initcall.init段中。只要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)));
//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)));
*********************以上就完成了设备的加载。
搞清出设备如何被加载以后,我们还需要知道另外一个问题:怎样把一个模块的驱动程序加载到内核里面呢?SO简单,make menuconfig,把对应设备打开。但是能不能再具体一点呢,我们做这么一个改动,怎么映射到编译&链接过程呢。我这个人就是喜欢找麻烦,因此又在网上搜啊搜,而且用了最笨的方法,看看make menuconfig前后那些文件的修改日期发生了变化。最终还是找到了一点,/scripts下的文件是用来支持各种config模式的(当然包括menuconfig),核心代码在Kconfig中。在每个驱动设备的文件夹下(比如net,mtd)都有一个叫config.in的文件,这些文件定义了我们在menuconfig画面中看到的目录结构&选项。
眼睛看到的画面总归都是虚的,这些改动究竟反映到了哪里去了呢?两个文件:./config和/include/linux/ autoconf.h。我们做完menuconfig以后,所有改动就反映到了这两个文件中,这两个文件的内容是一致的。在我们做编译的过程中,顶层的makefile文件从autoconf.h文件中读取各项宏定义然后传递给子一层的makefile,这些makefile根据宏定义选择那些.o文件被链接进来加到内核中。
好了,知道这些我就知道怎么给8019添加驱动了,yy一下:
1,首先要有驱动程序代码,8019.c
2,修改net目录下的config.in文件中添加一项,
dep_tristate ' RTL8019 support' CONFIG_RTL8019 $CONFIG_ISA
3,打开menuconfig,将RTL8019 support选择y,保存退出后autoconf文件中应该就有了一个宏定义:#define CONFIG_RTL8019
4,打开net目录下的makefile,添加:
obj-$( CONFIG_RTL8019) += 8019.o
5,make dep; make zImage;搞定!
注:在menuconfig中选择m和 y的区别:
y: 模块驱动编译到内核中,启动时自动加载
m:模块会被编译,但是不会被编译到内核中,只是生成.o文件,我们可以收集这些.o文件做到linux的文件系统中,然后用insmod实现动态加载。