内核启动时,设备及驱动初始化的实现( 版本2.6.35.4)

内核启动时,设备及驱动初始化的实现( 版本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实现动态加载。






你可能感兴趣的:(内核启动时,设备及驱动初始化的实现( 版本2.6.35.4))