UBOOT 简介 启动流程

本文详细介绍了U-Boot作为Bootloader的工作原理,包括其目录结构、启动过程及命令实现等内容。U-Boot主要用于嵌入式系统中,负责初始化硬件设备、建立内存映射,为加载操作系统内核做准备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

u-boot介绍:

u-boot是一种普遍用于嵌入式系统中的Bootloader,Bootloader是在操作系统运行之前执行的一小段程序,通过它,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的软硬件环境,为最终调用操作系统内核做好准备。Boot Loader的主要运行任务就是将内核映象从硬盘上读到RAM中,然后跳转到内核的入口点去运行,即开始启动操作系统。系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的Boot Loader程序。

u-boot目录结构:

1、board中存放于开发板相关的配置文件,每一个开发板都以子文件夹的形式出现。
2、Commom文件夹实现u-boot行下支持的命令,每一个命令对应一个文件。
3、cpu中存放特定cpu架构相关的目录,每一款cpu架构都对应了一个子目录。
4、Doc是文档目录,有u-boot非常完善的文档。
5、Drivers中是u-boot支持的各种设备的驱动程序。
6、Fs是支持的文件系统,其中最常用的是JFFS2文件系统。
7、Include文件夹是u-boot使用的头文件,还有各种硬件平台支持的汇编文件,系统配置文件和文件系统支持的文件。
8、Net是与网络协议相关的代码,bootp协议、TFTP协议、NFS文件系统得实现。
9、Tooles是生成U-boot的工具。

其中比较重要的目录就是/board、/cpu、/drivers和 /include目录,如果想实现u-boot在一个平台上的移植,就要对这些目录进行深入的分析。

u-boot的启动过程:

系统启动的入口点。既然我们现在要分析u-boot的启动过程,就必须先找到u-boot最先实现的是哪些代码,最先完成的是哪些任务。另一方面一个可执行的image必须有一个入口点,并且只能有一个全局入口点,所以要通知编译器这个入口在哪里。由此我们可以找到程序的入口点是在/board /lpc2210/u-boot.lds中指定的,其中ENTRY(_start)说明程序从_start开始运行,而他指向的是cpu /arm7tdmi/start.o文件。因为我们用的是ARM7TDMI的cpu架构,在复位后从地址0x00000000取它的第一条指令,所以我们将Flash映射到这个地址上,这样在系统加电后,cpu将首先执行u-boot程序。

u-boot的启动过程是多阶段实现的,分了两个阶段:

第一阶段是用汇编写的,主要任务是:

1、CPU 自身初始化:包括MMU,Cache,时钟系统,SDRAM 控制器等的初始化
2、重定位:把自己从非易失性存储器搬移到 RAM 中
3、分配堆栈空间,设置堆栈指针
4、清零 BSS 数据段
5、跳转到第二阶段入口函数 start_armboot()

第二阶段是用C写的,主要任务是:

1、为 U-boot 内部私有数据分配存储空间,并清零
2、依次调用函数指针数组 init_sequence 中定义的函数进行一系列的初始化
3、如果系统支持 NOR Flash,调用flash_init ()和display_flash_config ()初始化并显示检测到的器件信息
4、如果系统支持 LCD 或VFD,调用lcd_setmem()或vfd_setmem()计算帧缓(Framebuffer)大小,然后在BSS 数据段之后为Framebuffer 分配空间,初始化gd->fb_base 为Framebuffer 的起始地址
5、调用 mem_malloc_init()进行存储分配系统(类似于C 语言中的堆)的初始化和空间分配
6、如果系统支持 NAND Flash,调用nand_init ()进行初始化
7、如果系统支持 DataFlash,调用AT91F_DataflashInit()和dataflash_print_info()进行初始化并显示检测到的器件信息
8、调用 env_relocate ()进行环境变量的重定位,即从Flash 中搬移到RAM 中
9、如果系统支持 VFD,调用drv_vfd_init()进行VFD 设备初始化
10、从 环 境 变 量 中 读 取 IP 地址和MAC 地址, 初始化gd->bd-> bi_ip_addr 和gd->bd->bi_enetaddr
11、调用 jumptable_init ()进行跳转表初始化,跳转表在global_data 中,具体用途尚不清楚
12、调用 console_init_r()进行控制台初始化
13、如果需要,调用 misc_init_r ()进行杂项初始化
14、调用 enable_interrupts ()打开中断
15、如果需要,调用board_late_init()进行单板后期初始化,对于AT91SAM9260EK,主要是以太网初始化
16、进入主循环:根据用户的选择启动 linux,或者进入命令循环执行用户输入的命令

这部分是一些相对变化不大的部分,我们针对不同的板子改变它调用的一些初始化函数,并且通过设置一些宏定义来改变初始化的流程,所以这些代码在移植的过程中并不需要修改,也是错误相对较少出现的文件。在文件的开始先是定义了一个函数指针数组,通过这个数组,程序通过一个循环来按顺序进行常规的初始化,并在其后通过一些宏定义来初始化一些特定的设备。在最后程序进入一个循环,main_loop。这个循环接收用户输入的命令,以设置参数或者进行启动引导。

 

 

U-Boot启动过程

(国嵌)

 

开发板上电后,执行U-Boot的第一条指令,然后顺序执行U-Boot启动函数。看一下board/smdk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。第一个要链接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。下面分两阶段介绍启动流程:

                           

第一阶段

1cpu/arm920t/start.S

这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。

_start: b       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      //中断向量

 /* the actual reset code  */

reset:          //复位启动子程序

       /* 设置CPUSVC32模式 */

       mrs   r0,cpsr

       bic   r0,r0,#0x1f

       orr   r0,r0,#0xd3

       msr   cpsr,r0

 

/* 关闭看门狗 */

……      ……

relocate:                  /* U-Boot重新定位到RAM */

adr   r0, _start          /* r0是代码的当前位置 */

ldr   r1, _TEXT_BASE      /*_TEXT_BASERAM中的地址 */

cmp     r0, r1          /* 比较r0r1,判断当前是从Flash启动,还是RAM */

beq     stack_setup  /* 如果r0等于r1,跳过重定位代码 */

 

/* 准备重新定位代码 */

ldr   r2, _armboot_start

ldr   r3, _bss_start

sub   r2, r3, r2       /* r2 得到armboot的大小   */

add   r2, r0, r2      /* r2 得到要复制代码的末尾地址 */

copy_loop: /* 重新定位代码 */

ldmia r0!, {r3-r10}   /*从源地址[r0]复制 */

stmia r1!, {r3-r10}   /* 复制到目的地址[r1] */

cmp   r0, r2    /* 复制数据块直到源数据末尾地址[r2] */

ble   copy_loop

 

/* 初始化堆栈等    */

stack_setup:

ldr   r0, _TEXT_BASE  /* 上面是128 KiB重定位的u-boot */

sub   r0, r0, #CFG_MALLOC_LEN  /* 向下是内存分配空间 */

sub   r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo结构体地址空间  */

#ifdef CONFIG_USE_IRQ

sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

sub   sp, r0, #12     /* abort-stack预留3个字 */

clear_bss:

ldr   r0, _bss_start      /* 找到bss段起始地址 */

ldr   r1, _bss_end        /*  bss段末尾地址   */

mov   r2, #0x00000000     /* 清零 */

clbss_l:str r2, [r0]  

/* bss段地址空间清零循环...  */

       add   r0, r0, #4

       cmp   r0, r1

bne   clbss_l

 

/* 跳转到start_armboot函数入口,_start_armboot字保存函数入口指针 */

ldr   pc, _start_armboot

_start_armboot: .word start_armboot     //start_armboot函数在lib_arm/board.c中实现

 

第二阶段

2lib_arm/board.c

start_armbootU-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。

 

3init_sequence[]

init_sequence[]数组保存着基本的初始化函数指针。

 

init_fnc_t *init_sequence[] = {

cpu_init,   /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */

board_init, /* 基本的板级相关配置 -- board/smdk2410/smdk2410.c */

interrupt_init,  /* 初始化中断处理 -- cpu/arm920t/s3c24x0/interrupt.c */

env_init,      /* 初始化环境变量 -- common/cmd_flash.c */

init_baudrate,  /* 初始化波特率设置 -- lib_arm/board.c */

serial_init,  /* 串口通讯设置 -- cpu/arm920t/s3c24x0/serial.c */

console_init_f,       /* 控制台初始化阶段1 -- common/console.c */

display_banner,       /* 打印u-boot信息 -- lib_arm/board.c */

dram_init,     /* 配置可用的RAM -- board/smdk2410/smdk2410.c */

display_dram_config,  /* 显示RAM的配置大小 -- lib_arm/board.c */

NULL,

};

void start_armboot (void)

{

/* 顺序执行init_sequence数组中的初始化函数 */

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

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

                      hang ();

              }

       }

/*配置可用的Flash */

       size = flash_init ();

       display_flash_config (size);

       /* _armboot_start u-boot.lds链接脚本中定义 */

       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

 

/* 配置环境变量*/

env_relocate ();

 

/* 从环境变量中获取IP地址 */

gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

/* 以太网接口MAC 地址 */

       ……

       devices_init ();      /* 获取列表中的设备 */

       jumptable_init ();

       console_init_r ();    /* 完整地初始化控制台设备 */

       enable_interrupts (); /* 使能中断处理 */

/* 通过环境变量初始化 */

       if ((s = getenv ("loadaddr")) != NULL) {

               load_addr = simple_strtoul (s, NULL, 16);

       }

/* main_loop()循环不断执行 */

for (;;)

{

      main_loop ();      /* 主循环函数处理执行用户命令 -- common/main.c */

}

 

命令实现

U-Boot作为Bootloader,具备多种引导内核启动的方式。常用的gobootm命令可以直接引导内核映像启动。U-Boot与内核的关系主要是内核启动过程中参数的传递。

 

1go命令的实现

/* common/cmd_boot.c  */

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

{

       ulong addr, rc;

       int     rcode = 0;

       if (argc < 2) {

              printf ("Usage:\n%s\n", cmdtp->usage);

              return 1;

       }

       addr = simple_strtoul(argv[1], NULL, 16);

       printf ("## Starting application at 0x%08lX ...\n", addr);

       rc = ((ulong (*)(int, char []))addr) (--argc, &argv[1]);    /* 运行程序 */

    

if (rc != 0) rcode = 1;

       printf ("## Application terminated, rc = 0x%lX\n", rc);  /*如果是运行linux,这条指令是否能运行?*/

       return rcode;

}

 

go命令调用do_go()函数,跳转到某个地址执行的。如果在这个地址准备好了自引导的内核映像,就可以启动了。尽管go命令可以带变参,实际使用时不用来传递参数。

 

2bootm命令的实现

/* common/cmd_bootm.c */

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

{

……        ……

/* 检查头部 */

if (crc32 (0, (uchar *)data, len) != checksum) {

          puts ("Bad Header Checksum\n");

          SHOW_BOOT_PROGRESS (-2);

          return 1;

   }

……          ……

/*解压缩*/

   switch (hdr->ih_comp) {

   case IH_COMP_NONE:

          if(ntohl(hdr->ih_load) == addr) {

                 printf ("   XIP %s ... ", name);

          } else {

#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)

                 size_t l = len;

                 void *to = (void *)ntohl(hdr->ih_load);

                 void *from = (void *)data;

 

                 printf ("   Loading %s ... ", name);

 

                 while (l > 0) {

                        size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;

                        WATCHDOG_RESET();

                        memmove (to, from, tail);

                        to += tail;

                        from += tail;

                        l -= tail;

                 }

#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */

                 memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);

#endif      /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */

          }

          break;

   case IH_COMP_GZIP:

          printf ("   Uncompressing %s ... ", name);

          if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,

                     (uchar *)data, &len) != 0) {

                 puts ("GUNZIP ERROR - must RESET board to recover\n");

                 SHOW_BOOT_PROGRESS (-6);

                 do_reset (cmdtp, flag, argc, argv);

          }

          break;

#ifdef CONFIG_BZIP2

   case IH_COMP_BZIP2:

          printf ("   Uncompressing %s ... ", name);

          /*

           * If we've got less than 4 MB of malloc() space,

           * use slower decompression algorithm which requires

           * at most 2300 KB of memory.

           */

          i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),

                                      &unc_len, (char *)data, len,

                                      CFG_MALLOC_LEN < (4096 * 1024), 0);

          if (i != BZ_OK) {

                 printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);

                 SHOW_BOOT_PROGRESS (-6);

                 udelay(100000);

                 do_reset (cmdtp, flag, argc, argv);

          }

          break;

#endif /* CONFIG_BZIP2 */

   default:

          if (iflag)

                 enable_interrupts();

          printf ("Unimplemented compression type %d\n", hdr->ih_comp);

          SHOW_BOOT_PROGRESS (-7);

          return 1;

   }

}

……           ……             ……

 

switch (hdr->ih_os) {

       default:                     /* handled by (original) Linux case */

       case IH_OS_LINUX:

           do_bootm_linux  (cmdtp, flag, argc, argv,

                          addr, len_ptr, verify);

           break;

       case IH_OS_NETBSD:

           do_bootm_netbsd (cmdtp, flag, argc, argv,

                          addr, len_ptr, verify);

           break;

       case IH_OS_RTEMS:

           do_bootm_rtems (cmdtp, flag, argc, argv,

                          addr, len_ptr, verify);

           break;

       case IH_OS_VXWORKS:

           do_bootm_vxworks (cmdtp, flag, argc, argv,

                           addr, len_ptr, verify);

           break;

       case IH_OS_QNX:

           do_bootm_qnxelf (cmdtp, flag, argc, argv,

                           addr, len_ptr, verify);

           break;

       }

bootm命令调用do_bootm函数。这个函数专门用来引导各种操作系统映像,可以支持引导LinuxvxWorksQNX等操作系统。引导Linux的时候,调用do_bootm_linux()函数。

 

3do_bootm_linux函数的实现

/* lib_arm/armlinux.c */

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

                   ulong addr, ulong *len_ptr, int verify)

{

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

… …                 … …

      /* we assume that the kernel is in place */

      printf ("\nStarting kernel ...\n\n");

… …                 … …

      theKernel (0, bd->bi_arch_number, bd->bi_boot_params);  /*启动内核,传递启动参数*/

}

 

do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。这里引导的内核映像和ramdisk映像,必须是U-Boot格式的。U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值