超详细分析Bootloader(Uboot)到内核的启动流程(万字长文!)

您所在的位置:网站首页 重启的代码是什么名字 超详细分析Bootloader(Uboot)到内核的启动流程(万字长文!)

超详细分析Bootloader(Uboot)到内核的启动流程(万字长文!)

2024-07-15 16:42| 来源: 网络整理| 查看: 265

Bootloader启动流程分析

  Bootloader的启动过程可以分为单阶段、多阶段两种。通常多阶段的 Bootloader能提供更为复杂的功能以及更好的可移植性。从固态存储设备上启动的 Bootloader大多都是两阶段的启动过程。第一阶段使用汇编来实现,它完成一些依赖于CPU体系结构的初始化,并调用第二阶段的代码;第二阶段则通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。

  一般而言,这两个阶段完成的功能可以如下分类:

Bootloader第一阶段的功能硬件设备初始化

  首先需要设置时钟,设置MPLL(具体参见下面的FCLK HCLK PCLK 部分)。接着设置CLKDIVN地址为0x4C000014,写入0x05,表示设置分频系数为FCLK:HCLK:PCLK=1:4:8。接着,关闭看门狗,关中断,启动ICACHE,关闭DCACHE和TLB,关闭MMU(ICACHE为指令缓存,可以不关闭,指令直接操作的硬件,实际的物理地址。但是DCACHE就必须要关闭,此时MMU没有使能,虚拟地址映射不成功,sdram无法访问,DCACHE无数据)。start.s具体代码如下:

代码语言:javascript复制 /* 设置时钟 */ ldr r0, =0x4c000014 // mov r1, #0x03; mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8 str r1, [r0] /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */ mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */ orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */ mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */ /* MPLLCON = S3C2440_MPLL_200MHZ */ ldr r0, =0x4c000004 ldr r1, =S3C2440_MPLL_400MHZ str r1, [r0] /* 启动ICACHE */ mrc p15, 0, r0, c1, c0, 0 @ read control reg orr r0, r0, #(1u.mem.start = CFG_GLOBAL_RAM_BASE; t->u.mem.size = CFG_GLOBAL_RAM_SIZE; t = tag_next(t);

  相关结构体定义如下

代码语言:javascript复制#define ATAG_MEM 0x54410002 struct tag_mem32 { __u32 size; __u32 start; /* physical start address */ };

(3)设置命令行参数标记

  命令行参数是一个字符串,一般用它来告诉内核挂载根文件系统的方式。由uboot的bootargs环境变量提供,它的内容有如下两种格式

代码语言:javascript复制root=nfs nfsroot=202.193.61.237:/work/nfs_root/first_fs ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200代码语言:javascript复制root=/dev/mtdblock2 ip=202.193.61.196 init=/linuxrc console=ttySAC0,115200

名称

含义

root

告诉Linux内核挂载根文件系统的方式,nfs表示以NFS服务的方式挂载根文件系统,/dev/mtdblock2表示根文件系统在MTD设置的第二个分区上。

nfsroot

告诉Linux内核,以NFS方式挂载根文件系统时,根文件系统所在主机的P地址和路径

ip

告诉Linux内核,启动后它的p地址

init

告诉Linux内核,启动的第一个应用程序是根目录下的linuxrc程序

console

告诉Linux区内核,控制台为ttySAC0,波特率为115200

代码语言:javascript复制tag = tag_next(tag); tag->hdr.tag = ATAG_CMDLINE; tag->hdr.size = (strlen(params->commandline) + 3 + sizeof(struct tag_header)) >> 2; strcpy(tag->u.cmdline.cmdline, params->commandline); tag = tag_next(tag);

  相关结构体定义如下

代码语言:javascript复制/* command line: \0 terminated string */ #define ATAG_CMDLINE 0x54410003 struct tag_cmdline { char cmdline[1]; /* this is the minimum size */ };

(4)设置结束标记

代码语言:javascript复制 tag->hdr.tag = ATAG_NONE; tag->hdr.size = 0;

  我们明白了运行Linux区内核的时候,uboot需要给内核的传递的参数,接下来我们就来看看如何从uboot中跳到Linux内核。

uboot跳转到Linux内核

  在uboot中可以使用go和bootm来跳转到内核,这两个命令的区别如下:

  (1) go命令仅仅修改pc的值到指定地址

  格式:go addr

  (2) bootm命令是uboot专门用来启动uImage格式的Linux内核,它在修改pc的值到指定地址之前,会设置传递给Linux内核的参数,用法如下:

  格式:bootm addr

uboot中bootm命令实现

  bootm命令在uboot源码common/cmd_bootm.c中实现,它的功能如下:

  (1)读取uImage头部,把内核拷贝到合适的地方。

  (2)把参数给内核准备好。

  (3)引导内核。

  当我们使用我们在uboot使用bootm命令后,bootm命令会从uImage头中读取信息后,发现是Linux内核,就会调用do_bootm_linux()函数,函数的具体实现bootm.c中

代码语言:javascript复制int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images) { /* No need for those on ARM */ if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE) return -1; if (flag & BOOTM_STATE_OS_PREP) { boot_prep_linux(images); return 0; } if (flag & BOOTM_STATE_OS_GO) { boot_jump_linux(images); return 0; } boot_prep_linux(images); boot_jump_linux(images); return 0; }

  do_bootm_linux 函数最终会 跳转执行 boot_prep_linux 和 boot_jump_linux 函数,首先分析 boot_prep_linux 函数(位于 bootm.c 文件中):

代码语言:javascript复制static void boot_prep_linux(bootm_headers_t *images) { char *commandline = getenv("bootargs");      //从环境变量中获取 bootargs 的值   。。。。。。。 setup_board_tags(¶ms);       setup_end_tag(gd->bd);    //将 tag 参数保存在指定位置 } else { printf("FDT and ATAGS support not compiled in - hanging\n"); hang(); } do_nonsec_virt_switch(); }

   从代码可以看出来,boot_prep_linux,主要功能是将 tag 参数保存到指定位置,比如 bootargs 环境变量 tag,串口 tag,接下来分析 boot_jump_linux 函数(位于 bootm.c 文件中):

代码语言:javascript复制static void boot_jump_linux(bootm_headers_t *images, int flag) { unsigned long machid = gd->bd->bi_arch_number;      //获取机器id (在 board/samsung/jz2440/jz2440.c 中设置,为 MACH_TYPE_SMDK2410(193)) char *s; void (*kernel_entry)(int zero, int arch, uint params); unsigned long r2; int fake = (flag & BOOTM_STATE_OS_FAKE_GO); kernel_entry = (void (*)(int, int, uint))images->ep;    //获取 kernel的入口地址,此处应为 30000000 s = getenv("machid");        //从环境变量里获取机器id (本例中还未在环境变量里设置过机器 id) if (s) {            //判断环境变量里是否设置机器id strict_strtoul(s, 16, &machid);    //如果设置则用环境变量里的机器id printf("Using machid 0x%lx from environment\n", machid); } debug("## Transferring control to Linux (at address %08lx)" \ "...\n", (ulong) kernel_entry); bootstage_mark(BOOTSTAGE_ID_RUN_OS); announce_and_cleanup(fake); if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) r2 = (unsigned long)images->ft_addr; else r2 = gd->bd->bi_boot_params;    //获取 tag参数地址,gd->bd->bi_boot_params在 setup_start_tag 函数里设置 if (!fake) kernel_entry(0, machid, r2); }  //进入内核

  通过分析可以看出,最终进入内核的函数为 :

代码语言:javascript复制kernel_entry(0, machid, r2)

  到这里bootm就成功给内核传递了参数,并跳转到了内核。关于go命令的实现可以自己参考内核,在cmd_boot.c文件中,所不同的是,go命令实现的时候没有设置参数,只是简单的跳转执行。如果想要使用go来跳转到Linux内核,我们需要做简单的修改,有兴趣的可以自己研究下,这里就不展开讲了。

  至此,uboot就启动了内核。启动内核后就是挂载根文件系统了,下篇将具体介绍是如何挂载根文件系统的。

构建根文件系统

内核镜像格式vmlinuz和zImage和uImage

  最后插讲下内核的不同映像格式的区别:

  (1)uboot经过编译直接生成的elf格式的可执行程序是u-boot,这个程序类似于windows下的exe格式,在操作系统下是可以直接执行的。但是这种格式不能用来烧录下载。我们用来烧录下载的是u-boot.bin,这个东西是由u-boot使用arm-linux-objcopy工具进行加工(主要目的是去掉一些无用的)得到的。这个u-boot.bin就叫镜像(image),镜像就是用来烧录到iNand中执行的。

  (2)linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个就是原始的未经任何处理加工的原版内核elf文件;嵌入式系统部署时烧录的一般不是这个vmlinuz/vmlinux,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),经过制作加工成烧录镜像的文件就叫Image(制作把78M大的精简成了7.5M,因此这个制作烧录镜像主要目的就是缩减大小,节省磁盘)。

  (3)原则上Image就可以直接被烧录到Flash上进行启动执行(类似于u-boot.bin),但是实际上并不是这么简单。实际上linux的作者们觉得Image还是太大了所以对Image进行了压缩,并且在image压缩后的文件的前端附加了一部分解压缩代码。构成了一个压缩格式的镜像就叫zImage。(因为当年Image大小刚好比一张软盘(软盘有2种,1.2M的和1.44MB两种)大,为了节省1张软盘的钱于是乎设计了这种压缩Image成zImage的技术)。

  (4)uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生成uImage。注意:uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可。

  (5)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了LINUX_ZIMAGE_MAGIC这个宏。所以大家可以看出:有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。

  (6)如果直接在kernel底下去make uImage会提供mkimage command not found。解决方案是去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统目录下。再去make uImage即可。

  通过上面的介绍我们了解了内核镜像的各种格式,如果通过uboot启动内核,Linux必须为uImage格式。

本文参考:

https://www.cnblogs.com/PengfeiSong/p/6407903.html



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3