【1】OMAP335X-内核BSP之C代码启动那些事.

来源:互联网 发布:流畅的python 知乎 编辑:程序博客网 时间:2024/05/29 19:34

PC 系统 :ubuntu10.04

MPU平台:OAMP3352

内核版本:3.2.0

     声明:我讲解的范畴是从内核解压以后经过汇编代码执行最后跳到第一个C代码这个点开始讲解,一直讲到文件系统被正确的挂载起来,用户可以登入!至于之前的汇编代码我会以后另开文章讲解。

    目标:本文想阐述清楚OAMP335X这个平台的BSP部分代码的启动过程。

第一步:  

  init/main.c 文件是汇编过来的第一个被调用的C代码文件;

  asmlinkage void __init start_kernel(void)是汇编过来的第一个被执行的C函数;

  asmlinkage void __init start_kernel(void)
  {

   ...........

   setup_arch(&command_line);

   ...........

   rest_init();

 }

这个函数中有很多的调用函数,每一个都具有举足轻重的地位,够我们研究一段时间的,它们中的许多都肩负着初始化内核中的某个子系统的重要使命,而Linux内核中每一个子系统都错综复杂,牵涉到各种软件、硬件的复杂算法,所以我们要慢慢的理解他。这里我们讲解其中和本文章相关的两个函数,其他的以后用到再讲。

void __init setup_arch(char **cmdline_p)

{

struct machine_desc *mdesc;

setup_processor();
mdesc = setup_machine_fdt(__atags_pointer);
if (!mdesc)
mdesc = setup_machine_tags(machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;
if (mdesc->restart_mode)

reboot_setup(&mdesc->restart_mode);

......................

}

这里的setup_machine()函数的作用就是找到我们想要的struct machine_desc类型的变量,也就是在BSP里定义那个变量。

MACHINE_START(AM335XEVM, "am335xevm")
 /* Maintainer: Texas Instruments */
    .atag_offset = 0x100,
    .map_io  = am335x_evm_map_io,
    .init_early = am33xx_init_early,
    .init_irq = ti81xx_init_irq,
    .handle_irq     = omap3_intc_handle_irq,
    .timer  = &omap3_am33xx_timer,
    .init_machine = am335x_evm_init,
MACHINE_END

这样我们板级代码的初始化就完成了,但这只是仅仅数据结构得到初始化,真正的资源都在这个结构体里面,那在哪里通过这个变量调用这个数据结构里面的成员或成员函数的呢?

  在arch/arm/kernel/setup.c里我们看到这样一个函数

static int __init customize_machine(void)

{

        /* customizes platform devices, or adds new ones */

        if (machine_desc->init_machine)

                machine_desc->init_machine();

        return 0;

}

arch_initcall(customize_machine);

终于看到了,成员函数init_machine就是在这里被调用的。但是它没有被显示调用,而是放在了arch_initcall这个宏里,很显然这个宏肯定在什么地方被调用了!

第二步:

我们先看一些宏的定义(定义在文件include/linux/init.h中)

#define pure_initcall(fn)                   __define_initcall("0",fn,0)   

#define core_initcall(fn)                    __define_initcall("1",fn,1)   

#define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)   

#define postcore_initcall(fn)             __define_initcall("2",fn,2)   

#define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)   

#define arch_initcall(fn)                      __define_initcall("3",fn,3)   

#define arch_initcall_sync(fn)             __define_initcall("3s",fn,3s)   

#define subsys_initcall(fn)                  __define_initcall("4",fn,4)   

#define subsys_initcall_sync(fn)       __define_initcall("4s",fn,4s)   

#define fs_initcall(fn)                            __define_initcall("5",fn,5)   

#define fs_initcall_sync(fn)                 __define_initcall("5s",fn,5s)   

#define rootfs_initcall(fn)                     __define_initcall("rootfs",fn,rootfs)   

#define device_initcall(fn)                    __define_initcall("6",fn,6)   

#define device_initcall_sync(fn)         __define_initcall("6s",fn,6s)   

#define late_initcall(fn)                         __define_initcall("7",fn,7)   

#define late_initcall_sync(fn)              __define_initcall("7s",fn,7s)  

#define __define_initcall(level,fn,id) static initcall_t __initcall_##fn##id  __used  __attribute__((__section__(".initcall" level ".init"))) = fn

这其中initcall_t是函数指针,原型:typedef int (*initcall_t)(void);  

而编译器宏 __attribute__((__section__())) 则表示把对象放在一个这个由括号中的名称所指代的section中,__define_initcall("6",fn,6) 则是把fn的地址放到.initcall6.init这个selection中

所以__define_initcall的含义是:

1) 声明一个名称为__initcall_##fn##id 的函数指针;

2) 将这个函数指针初始化赋值为fn;

3) 编译的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init"的section中

明确了__define_initcall的含义,就知道了它其实是分别将这些初始化函数地址以指针方式放到各自的section中的。

SECTION“.initcall”level”.init”被放入INITCALLS(include/asm-generic/vmlinux.lds.h)

#define INITCALLS                                                   \   

            *(.initcallearly.init)                                  \  

            VMLINUX_SYMBOL(__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)  

VMLINUX_SYMBOL(__early_initcall_end) = .;  

这里的 . 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用。

这句命令的意思,是对__early_initcall_end变量赋值为当前section内的偏移

*(.initcall7s.init) 表示的是所有输入文件中的名为.initcall7s.init的SECTIONS

__initcall_start和__initcall_end以及INITCALLS中定义的SECTION都是在arch/xxx/kernel/vmlinux.lds.S中放在.init段的。

SECTIONS  

{  

        .init : {  

                __initcall_start = .;  

                        INITCALLS  

                __initcall_end = .;  

        }  

.init只是用于对SECTIONS进行分类  

 __initcall_start = .;  和__initcall_end = .;  表示分别对变量 __initcall_start和__initcall_end 赋值为当前section内的偏移。

而这些SECTION里的函数在初始化时被顺序执行(init内核线程->do_basic_setup()[main.c#778]->do_initcalls())。

程序(init/main.c文件do_initcalls()函数)如下,do_initcalls()把XX.initcallXX.init中的函数按顺序都执行一遍。

for (call = __early_initcall_end; call < __initcall_end; call++)  

        do_one_initcall(*call);  

看到这里我们知道内核对于函数的执行顺序是通过一个链接脚本来进行排号。

      也就是说这个链接脚本在内核编译链接完成后初始化函数的执行先后顺序就确定了。这是通过链接器在链接时调用链接脚本/arch/arm/kernel/vmlinux.lds和include/asm-generic/vmlinux.lds.h来完成的。脚本规定了不同代码段,例如_init、text、data等不同属性的代码段存放的位置。

     假如同属_init函数A()和函数B()就全部放在脚本中定义的_init地址上,但是具体是A()函数在前还是B()函数在前呢?这是由目录下的Makefile文件来决定了。Makefile文件中函数存放的先后顺序来决定了,假如obj+y   = A()在obj+y    = B()之前,则最后连接的时候A函数就在B()函数之前执行了。所以最后结论是决定函数执行的先后顺序是由:1.vmlinux.lds链接脚本

2.驱动目录下Makefile文件

共同确定的;

 

      但是我还是只看到customize_machine()被放到了.initcall3.init里。这只是说顺序是安排好了,那究竟它在哪里被调用呢?

   在/init/main.c里一个叫do_initcalls()的函数里被调用,我们老看看这个函数的实现:

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);

}

看到第1行,很熟悉吧。在for循环里依次调用了从__early_initcall_start开始到__initcall_end结束的所有函数。customize_machine()也是在其间被调用了,也就是说

我们的BSP的初始化也被执行了。

 

好了,到这里差不多该结束了,最后总结一下这些函数调用顺序

【第一步】start_kernel()                                 【/init/main.c】

【第二步】setup_arch(&command_line);   【arch/arm/kernel/setup.c】

该函数找到匹配的machine_desc 但没有调用里面的函数

 【第三步】 rest_init()->kernel_thread(kernel_init,NULL,CLONE_FSCLONE_SIGHAND)->

                      regs.ARM_r5 = (unsigned long)kernel_init;

                      do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, &regs, 0, NULL, NULL)->

                      kernel_init()->do_basic_setup()->do_initcalls() 【/init/main.c】

【第四步】customize_machine()                  【/init/main.c】

【第五步】am335x_evm_init                         【arch/arm/mach-oamp2/board-am335xevm.c】

                    struct machine_desc 结构体的其他各个成员函数在不同时期被调用!这个以后再分析

 

 总结:明确了 汇编过来的内核第一个C函数.start_kernel()

       明确了 start_kernel()函数中对于函数的启动顺序是依据什么来安排的?

       明确了 嵌入式的BSP代码是什么时候初始化的?成员变量又是什么时候被调用执行的?

 明确了这些对我们的开发工作有什么帮助呢? 这个在下一章节中进行讲解。       

 

 

 

原创粉丝点击