Linux系统启动之——u-boot启动

来源:互联网 发布:大数据与信息安全 编辑:程序博客网 时间:2024/06/14 18:25

     操作系统是人机交互的接口,其管理着计算机的硬件,例如内存,CUP,外设等。系统上电后,操作系统是怎么启动的呢?在嵌入式中,操作系统是由bootloader引导起来的,linux在嵌入式上应用时,其bootloaderu-boot。这篇文章讲述linux系统的启动,和大家一起学习学习。

         U-boot的启动分成两个阶段:stage1和stage2。Stage1使用汇编语言编写,通常与CPU体系结构紧密相关,如处理器初始化,内存初始化;该阶段还会建立堆栈和代码段,复制stage2阶段的代码到内存。Stage2阶段一般包括:初始化flash器件,检测系统内存影像,初始化串口等外设,初始控制台接收用户从串口发来的命令并处理等。该阶段代码使用C编写,用于加载操作系统内核。第一阶段的启动流程如图1:                   

        具体的在arm处理器上的启动流程如图2。该图以关键函数作为流程的某一阶段的名称,以便于参考具体代码,更便于了解学习u-boot的启动流程。

          

.globl _start     //u-boot的启动入口

_start: b  reset//复位向量;无条件跳转到reset标号

         ldr    pc, _undefined_instruction//未定义指令向量

         ldr    pc, _software_interrupt//软件中断向量

         ldr    pc, _prefetch_abort//预取指令异常向量

         ldr    pc, _data_abort//数据操作异常向量

         ldr    pc, _not_used//未使用

         ldr    pc, _irq//慢速中断向量

         ldr    pc, _fiq//快速中断向量

 

_undefined_instruction: .word undefined_instruction//定义中断向量表入口地址

_software_interrupt:       .word software_interrupt

_prefetch_abort:     .word prefetch_abort

_data_abort:            .word data_abort

_not_used:                .word not_used

_irq:                   .word irq

_fiq:                    .word fiq

......

//defined at /arch/arm/cpu/arm_cortexa8/start.s

上面代码即为u-boot入口_start。由code可以看到,进入_start后无条件跳转到reset标号。_start标号下面的代码主要是一些伪指令,设置全局变量,供启动程序吧U-boot影像从flash存储器复制到内存中。

reset:

         /*

          * set the cpu to SVC32 mode

          */

         mrs  r0, cpsr

         bic    r0, r0, #0x1f

         orr    r0, r0, #0xd3

         msr  cpsr,r0

......

                   /* the mask ROM code should have PLL and others stable */

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

                            bl      cpu_init_crit    //跳到开发板相关初始化代码

#endif

Reset标号处的代码是上电时最先执行的代码,其作用清除中断,设置watchdog,设置时钟等,最后部分是跳转到cpu_init_crit

cpu_init_crit标志处的代码主要是对初始化处理器的关键寄存器;例如刷新cacheTLB(Translation  Lookaside  Buffer,:旁路缓冲,完成虚拟地址和物理地址的映射关系),关闭MMC(Memory Management Unit; 内存管理单元),之后跳到lowlevel_init标志处执行代码。

lowlevel_init主要工作,计算SMRDATA(开发板上内存映射的配置)需要加载的内存地址和大小,复制SDMDATA到内存,进行内存的配置。Lowlevel_init执行完之后返回,返回后去执行relocate标志处代码。

......

#ifndef CONFIG_SKIP_RELOCATE_UBOOT

relocate:                              @ relocate U-Boot to RAM

         adr   r0, _start          @ r0 <- current position of code

..........

         ldr pc, _start_armboot    @ jump to C code

        

         _start_armboot: .word start_armboot//C code的入口,之后开始执行C code

.......

上面是部分relocate标志处的关键代码,该部分代码首先检查当前是否是在内存中执行,如果不是则复制代码到内存。复制代码到内存需要完成获取当前代码地址,对应的内存地址,stage2代码的地址和内存地址,stage2的长度,然后完成复制。然后建立内存堆栈,初始化bss段等,最后跳到start_armboot()

start_armboot()之前是u-boot的stage1,全是汇编语言编写。从此开始是stage2,从此开始使用C语言编写。

       

void start_armboot (void)

{....

         for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

                   if ((*init_fnc_ptr)() != 0) {

                            hang ();

                   }

         }//该循环进行一些必要的初始化例如CPU,中断, evn

.....

         //进一步进行初始化,例如控制台,串口等

         /* main_loop() can return to retry autoboot, if so just run it again. */

         for (;;) {

                   main_loop ();//进入主循环

         }

}

start_armboot()是整个Uboot最重要的部分之一,该函数(1)完成系统大部分的初始化,例如flash存储器配置,显示flash存储器配置、计算FrameBuffer、设置FrameBuffer内存大小和内存起始地址、设置环境变量、初始化控制台,cupramboard初始化,初始化跳转表,打开中断,配置网卡,外设初始化等。(2)初始化全局变量gb,设置gb成员的初始值。在上面示例代码的for循环中,init_sequence[]是函数数组,数组元素是一些重要的函数名,这些函数完成一些初始化,其中,board_init()中有语句gd->bd->bi_arch_number = MACH_TYPE_DM385EVM;        /* address of boot parameters */   gd->bd->bi_boot_params = PHYS_DRAM_1 + 0x100; 红色字体语句是对u-boot的参数地址赋值,该地址传递给kernelkernel就可以读取u-boot传递过来的参数。(3)跳掉main_loop()主循环。通过for死循环调用main_loop()函数,作用是防止main_loop()函数开始的初始化代码调用失败后重新执行初始操作,保证程序能进入到U-boot的命令。

Main_loop()u-boot启动过程中的最后一部分,该函数做的都是与具体平台无关的工作,主要包括初始化启动次数限制机制,设置软件版本,打印启动信息,初始化hash,检验启动延迟,运行boot命令等。到这里u-boot的启动已经完成,接下来就是要引导kernel启动。在main_loop()的最后有一个死循环读取命令,解析命令。读取到有用命令时,运行rum_command()解析命令,来引导kernel的启动。

 

接下来是引导kernel启动。引导kernel的动作主要由do_bootm()完成。

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{......

         for (i = 0; i < ARRAY_SIZE(boot_os); i++)

         if (boot_os[i] != NULL)

                   boot_os[i] += gd->reloc_off;

.....

         if (bootm_start(cmdtp, flag, argc, argv))

                   return 1;

.....

}//defined at  /common/cmd_bootm.c

For循环中数组boot_os[]是函数数组,其成员全部是函数名。根据不同的系统,注册不同的函数。Linux系统注册函数为do_bootm_linux。其后调用bootm_start(),该函数最主要的工作就是初始化images这个全局变量;其中语句

/* find kernel entry point */

         if (images.legacy_hdr_valid) {

                   images.ep = image_get_ep (&images.legacy_hdr_os_copy);

#if defined(CONFIG_FIT)

         } else if (images.fit_uname_os) {

                   ret = fit_image_get_entry (images.fit_hdr_os,

                                     images.fit_noffset_os, &images.ep);

                   if (ret) {

                            puts ("Can't get entry point property!\n");

                            return 1;

                   }

#endif

           images.ep即为操作系统的入口地址。上面的语句作用即为获取操作系统的入口地址。在do_bootm_linux()中,{……void         (*theKernel)(int zero, int arch, uint params); …….   theKernel = (void (*)(int, int, uint))images->ep;   theKernel (0, machid, bd->bi_boot_params); ……..},这样程序跳到images.ep所指的地址执行,也就是开始kernel的启动,到这里,uboot启动全部完成。

 

由于对linux的理解不够深入,甚至可以说十分浅显,恳请各位大侠帮小弟解答以下疑惑:

疑问:1 u-boot的入口_start处,程序进入_start就无条件跳转到reset,并且该跳转是不返回的跳转,从reset代码可以一步一步的进入stage2,然后启动kernel_startreset标号下面的代码什么时候执行?

         2 u-bootstage2阶段start_armboot()for循环,函数数组init_sequence[]中的函数什么时候执行的?相同的疑问,do_bootm()中的for循环boot_os[]中的函数什么时候执行的?

         3 从main_loop()怎么跳到do_bootm()的?rum_command()与U_BOOT_CMD(        bootm, CONFIG_SYS_MAXARGS,    1,         do_bootm,  ………}有什么关系?

 

原创粉丝点击