bootloader 要想启动内核,可以直接跳到内核的第一个指令处,即内核的起始地址,这样便可以完成内核的启动工作了。但是要想启动内核还需要满足一些条件,如下所示:

1、cpu 寄存器设置

  * R0 = 0
    * R1 = 机器类型 id
    * R2 = 启动参数在内存中的起始地址

2、cpu 模式

* 禁止所有中断
    * 必须为SVC(超级用户)模式

3、Cache、MMU

* 关闭 MMU
    * 指令Cache可以开启或者关闭
    * 数据Cache必须关闭

4、设备

* DMA 设备应当停止工作

5、PC为内核的起始地址

这些需求都由 boot loader 实现,在常用的 uboot 中完成一系列的初始化后最后通过 bootm 命令加载 linux 内核。bootm 向将内核映像从各种媒介中读出,存放在指定的位置;然后设置标记列表给内核传递参数;最后跳到内核的入口点去执行。

Uboot版本:u-boot-2013.01

一、bootm命令用法介绍如下:

在 common/cmd_bootm.c 中可以看到bootm 的定义:

可以看到 bootm 命令使调用了do_bootm 函数

do_bootm 函数

在cmd_bootm.c 第586行可以看到do_bootm函数的定义(为方便阅读,对其中一些代码进行了删减,完整代码请阅读uboot源码):

[cpp] view plaincopy
  1. /*******************************************************************/
  2. /* bootm - boot application image from image in memory */
  3. /*******************************************************************/
  4. int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
  5. {
  6. ulong       iflag;
  7. ulong       load_end = 0;
  8. int     ret;
  9. boot_os_fn  *boot_fn;
  10. if (bootm_start(cmdtp, flag, argc, argv))// 获取镜像信息
  11. return 1;
  12. iflag = disable_interrupts(); // 关闭中断
  13. usb_stop();// 关闭usb设备
  14. ret = bootm_load_os(images.os, &load_end, 1);//加载内核
  15. lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));
  16. if (images.os.type == IH_TYPE_STANDALONE) {//如有需要,关闭内核的串口
  17. if (iflag)
  18. enable_interrupts();
  19. /* This may return when 'autostart' is 'no' */
  20. bootm_start_standalone(iflag, argc, argv);
  21. return 0;
  22. }
  23. boot_fn = boot_os[images.os.os];//获取启动参数
  24. arch_preboot_os();//启动前准备
  25. boot_fn(0, argc, argv, &images);//启动,不再返回
  26. #ifdef DEBUG
  27. puts("\n## Control returned to monitor - resetting...\n");
  28. #endif
  29. do_reset(cmdtp, flag, argc, argv);
  30. return 1;
  31. }

该函数的实现分为 3 个部分:

a -- 首先通过 bootm_start 函数分析镜像的信息;

b -- 如果满足判定条件则进入 bootm_load_os 函数进行加载;

c -- 加载完成后就可以调用 boot_fn 开始启动。

1、bootm_start

在cmd_bootm.c 第193行可以看到bootm_start函数的定义, 主要作用是填充内核相关信息

[cpp] view plaincopy
  1. static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
  2. {
  3. void        *os_hdr;
  4. int     ret;
  5. memset((void *)&images, 0, sizeof(images));
  6. images.verify = getenv_yesno("verify");//获取环境变量
  7. boot_start_lmb(&images);
  8. bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
  9. /*获取镜像头,加载地址,长度 */
  10. os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
  11. &images, &images.os.image_start, &images.os.image_len);
  12. if (images.os.image_len == 0) {
  13. puts("ERROR: can't get kernel image!\n");
  14. return 1;
  15. }
  16. /*获取镜像参数*/
  17. switch (genimg_get_format(os_hdr)) {
  18. case IMAGE_FORMAT_LEGACY:
  19. images.os.type = image_get_type(os_hdr);//镜像类型
  20. images.os.comp = image_get_comp(os_hdr);//压缩类型
  21. images.os.os = image_get_os(os_hdr);//系统类型
  22. images.os.end = image_get_image_end(os_hdr);//镜像结束地址
  23. images.os.load = image_get_load(os_hdr);/加载地址
  24. break;
  25. /* 查询内核入口地址*/
  26. if (images.legacy_hdr_valid) {
  27. images.ep = image_get_ep(&images.legacy_hdr_os_copy);
  28. } else {
  29. puts("Could not find kernel entry point!\n");
  30. return 1;
  31. }
  32. if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
  33. images.os.load = images.os.image_start;
  34. images.ep += images.os.load;
  35. }
  36. if (((images.os.type == IH_TYPE_KERNEL) ||
  37. (images.os.type == IH_TYPE_KERNEL_NOLOAD) ||
  38. (images.os.type == IH_TYPE_MULTI)) &&
  39. (images.os.os == IH_OS_LINUX)) {
  40. /* 查询是否存在虚拟磁盘 */
  41. ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
  42. &images.rd_start, &images.rd_end);
  43. if (ret) {
  44. puts("Ramdisk image is corrupt or invalid\n");
  45. return 1;
  46. }
  47. #if defined(CONFIG_OF_LIBFDT)
  48. /* 找到设备树,设备树是linux 3.XX版本特有的 */
  49. ret = boot_get_fdt(flag, argc, argv, &images,
  50. &images.ft_addr, &images.ft_len);
  51. if (ret) {
  52. puts("Could not find a valid device tree\n");
  53. return 1;
  54. }
  55. set_working_fdt_addr(images.ft_addr);
  56. #endif
  57. }
  58. images.os.start = (ulong)os_hdr;//赋值加载地址
  59. images.state = BOOTM_STATE_START;//更新状态
  60. return 0;
  61. }

该函数主要进行 镜像的有效性判定、校验、计算入口地址 等操作,大部分工作通过  boot_get_kernel -> image_get_kernel  完成。


2、bootm_load_os

在cmd_bootm.c 第317行可以看到bootm_load_os函数的定义, 这个函数主要判断镜像是否需要解压,并且将镜像移动到加载地址:

[cpp] view plaincopy
  1. static int bootm_load_os(image_info_t os, ulong *load_end, int boot_progress)
  2. {
  3. uint8_t comp = os.comp;         /* 压缩格式 */
  4. ulong load = os.load;           /* 加载地址 */
  5. ulong blob_start = os.start;    /* 镜像起始地址 */
  6. ulong blob_end = os.end;        /* 镜像结束地址 */
  7. ulong image_start = os.image_start;    /* 镜像起始地址 */
  8. ulong image_len = os.image_len;        /* 镜像长度 */
  9. uint unc_len = CONFIG_SYS_BOOTM_LEN;   /* 镜像最大长度 */
  10. const char *type_name = genimg_get_type_name (os.type);  /* 镜像类型 */
  11. switch (comp) {  /* 选择解压格式 */
  12. case IH_COMP_NONE:  /* 镜像没有压缩过 */
  13. if (load == blob_start) {   /* 判断是否需要移动镜像 */
  14. printf ("   XIP %s ... ", type_name);
  15. } else {
  16. printf ("   Loading %s ... ", type_name);
  17. if (load != image_start) {
  18. memmove_wd ((void *)load, (void *)image_start, image_len, CHUNKSZ);
  19. }
  20. }
  21. *load_end = load + image_len;
  22. puts("OK\n");
  23. break;
  24. case IH_COMP_GZIP:  /* 镜像采用 gzip 解压 */
  25. printf ("   Uncompressing %s ... ", type_name);
  26. if (gunzip ((void *)load, unc_len, (uchar *)image_start, &image_len) != 0) {  /* 解压 */
  27. puts ("GUNZIP: uncompress, out-of-mem or overwrite error "
  28. "- must RESET board to recover\n");
  29. return BOOTM_ERR_RESET;
  30. }
  31. *load_end = load + image_len;
  32. break;
  33. ...
  34. default:
  35. printf ("Unimplemented compression type %d\n", comp);
  36. return BOOTM_ERR_UNIMPLEMENTED;
  37. }
  38. puts ("OK\n");
  39. debug ("   kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end);
  40. if ((load < blob_end) && (*load_end > blob_start)) {
  41. debug ("images.os.start = 0x%lX, images.os.end = 0x%lx\n", blob_start, blob_end);
  42. debug ("images.os.load = 0x%lx, load_end = 0x%lx\n", load, *load_end);
  43. return BOOTM_ERR_OVERLAP;
  44. }
  45. return 0;
  46. }

3、do_bootm_linux

在bootm_load_os 执行结束后,回到do_bootm 函数,调用boot_fn 运行linux 内核;

boot_os 为函数指针数组,在cmd_bootm.c 136行有定义


可以看出 boot_fn 函数指针指向的函数是位于 arch/arm/lib/bootm.c的 do_bootm_linux,这是内核启动前最后的一个函数,该函数主要完成启动参数的初始化,并将板子设定为满足内核启动的环境,代码如下:

可以看到 do_bootm_linux 实际调用的是 boot_jump_linux 函数

4、boot_jump_linux 

在arch/arm/lib/bootm.c 下第326行有定义

[cpp] view plaincopy
  1. /* Subcommand: GO */
  2. static void boot_jump_linux(bootm_headers_t *images)
  3. {
  4. unsigned long machid = gd->bd->bi_arch_number;//获取机器码
  5. char *s;
  6. void (*kernel_entry)(int zero, int arch, uint params);//内核入口函数
  7. unsigned long r2;
  8. kernel_entry = (void (*)(int, int, uint))images->ep;
  9. s = getenv("machid");//从环境变量中获取机器码
  10. if (s) {
  11. strict_strtoul(s, 16, &machid);
  12. printf("Using machid 0x%lx from environment\n", machid);
  13. }
  14. debug("## Transferring control to Linux (at address %08lx)" \
  15. "...\n", (ulong) kernel_entry);
  16. bootstage_mark(BOOTSTAGE_ID_RUN_OS);
  17. announce_and_cleanup();
  18. #ifdef CONFIG_OF_LIBFDT
  19. if (images->ft_len)
  20. r2 = (unsigned long)images->ft_addr;
  21. else
  22. #endif
  23. r2 = gd->bd->bi_boot_params;//将启动参数地址赋给 r2
  24. kernel_entry(0, machid, r2);
  25. }

kernel_entry(0, machid, r2)

真正将控制权交给内核, 启动内核;

满足arm架构linux内核启动时的寄存器设置条件:第一个参数为0 ;第二个参数为板子id需与内核中的id匹配,第三个参数为启动参数地址 。


二、为内核设置启动参数

Uboot 也是通过标记列表向内核传递参数,标记在源代码中定义为tag,是一个结构体,在arch/arm/include/asm/setup.h 中定义。

tag_header 结构体定义如下:

在一些内存标记、命令行标记的示例代码就是取自Uboot 中的 setup_memory_tags、setup_commandline_tag函数,他们都是在 arch/arm/lib/bootm.c中定义。

[cpp] view plaincopy
  1. #if defined(CONFIG_SETUP_MEMORY_TAGS) || \
  2. defined(CONFIG_CMDLINE_TAG) || \
  3. defined(CONFIG_INITRD_TAG) || \
  4. defined(CONFIG_SERIAL_TAG) || \
  5. defined(CONFIG_REVISION_TAG)
  6. static void setup_start_tag (bd_t *bd)
  7. {
  8. params = (struct tag *)bd->bi_boot_params;
  9. params->hdr.tag = ATAG_CORE;
  10. params->hdr.size = tag_size (tag_core);
  11. params->u.core.flags = 0;
  12. params->u.core.pagesize = 0;
  13. params->u.core.rootdev = 0;
  14. params = tag_next (params);
  15. }
  16. #endif
  17. #ifdef CONFIG_SETUP_MEMORY_TAGS
  18. static void setup_memory_tags(bd_t *bd)
  19. {
  20. int i;
  21. for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
  22. params->hdr.tag = ATAG_MEM;
  23. params->hdr.size = tag_size (tag_mem32);
  24. params->u.mem.start = bd->bi_dram[i].start;//物理内存起始地址
  25. params->u.mem.size = bd->bi_dram[i].size;//物理内存结束地址
  26. params = tag_next (params);
  27. }
  28. }
  29. #endif
  30. #ifdef CONFIG_CMDLINE_TAG
  31. static void setup_commandline_tag(bd_t *bd, char *commandline)
  32. {
  33. char *p;
  34. if (!commandline)
  35. return;
  36. /* eat leading white space */
  37. for (p = commandline; *p == ' '; p++);
  38. /* skip non-existent command lines so the kernel will still
  39. * use its default command line.
  40. */
  41. if (*p == '\0')
  42. return;
  43. params->hdr.tag = ATAG_CMDLINE;
  44. params->hdr.size =
  45. (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
  46. strcpy (params->u.cmdline.cmdline, p);
  47. params = tag_next (params);
  48. }
  49. #endif

一般有 setup_memory_tags、setup_commandline_tag 这两个标记就可以了,在配置文件Include/configs/fs4412.h中定义:

Exynos4412 Uboot 移植(四)—— Uboot引导内核过程分析相关推荐

  1. mini2440 uboot使用nfs方式引导内核,文件系统

    mini2440 uboot使用nfs方式引导内核,文件系统 成于坚持,败于止步 看了一段时间的u-boot了,到今天才真正完全实现u-boot引导内核和文件系统,顺利开机,在此记录完整过程 1.首先 ...

  2. U-BOOT下使用bootm引导内核方法

    U-BOOT下使用bootm引导内核方法 注:   u-boot 使用的是打上: http://www.hhcn.com/cgi-bin/topic.cgi?forum=3&topic=651 ...

  3. U-Boot移植(8)u-boot的流程

    u-boot的启动流程: 从文件层面上看主要流程是在两个文件中:cpu/arm920t/start.s,lib_arm/board.c, 1)start.s 在flash中执行的引导代码,也就是boo ...

  4. 创客exynos-fs4412系统移植-(uboot,内核,文件系统)

    fs4412系统移植学习笔记 环境: ubuntu 14.04 发行版 FS4412 实验平台 交叉编译工具:arm-none-linux-gnueabi- gcc: 4.8.5 目录 fs4412系 ...

  5. u-boot移植篇——了解u-boot

    文章目录 一.U-Boot 1.1 如何下载 二.u-boot是如何引导内核的 2.1 u-boot连接脚本 2.1.1 扫盲 2.2 u-boot.lds 一.U-Boot 对于嵌入式玩家来说,ub ...

  6. uboot的移植——移植uboot官方的uboot到x210开发板

    以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除. 参考内容 uboot--官网下载直接移植(一) - biaohc - 博客园 uboot--官网下载直接移植(二) - biaohc - ...

  7. 主线剧情03-NXP-i.MX系列的u-boot移植基础详解

    u-boot 移植基础详解 本文系广泛撷取.借鉴和整理(相关的内容在网络上有很多,但很多相互抄,或者是版本太老,或者就是不通用的非常有平台针对性的步骤,碎片化泛滥,甚至就是有待分拣的垃圾厂,当然也有一 ...

  8. linux用户环境下进入uboot,嵌入式linux开发uboot移植(一)——uboot项目简介

    嵌入式linux开发uboot移植(一)--uboot项目简介 一.uboot简介 U-Boot,全称 Universal Boot Loader,是遵循GPL条款的从FADSROM.8xxROM.P ...

  9. tiny4412 uboot 2020.10版本移植(四)——uboot修改支持sd卡、eMMC引导内核及其他一些杂项设置

    本文在<tiny4412 uboot 2020.10版本移植(三)--uboot初步启动> 的基础上继续向tiny4412 uboot 2020.10版添加功能. 主要有三块内容:1. D ...

最新文章

  1. matlab画图函数plot
  2. 数据结构:单向链表的反转
  3. C++ kadane算法的实现之二(附完整源码)
  4. 广度优先遍历算法-01寻找制高点问题
  5. “不翻身,就要翻船”!帆软独家:制造业数字化转型解决方案
  6. 2020-11-08
  7. springmvc的作用:
  8. DID会固定年份吗_倍分法DID详解 (二):多时点 DID (渐进DID)
  9. AppWeb或云打分时,附加分相关问题
  10. R数据分析:用lme4包拟合线性和非线性混合效应模型
  11. 掩码位数原理、掩码位数计算方法、VLSM详解(IP地址划分方法)、CIDR详解
  12. ipone=遥控器?
  13. 电磁学基本知识与基本定律复习
  14. beta阶段贡献分配实施
  15. PaddleClas-图像分类中的8种数据增广方法(cutmix, autoaugment,..)
  16. AI:2020北京智源大会与五位图灵奖得主和100多位专家《共同探讨人工智能的下一个十年》——6月21日~6月24日的日程安排(实时更新,建议收藏)
  17. 记一次服务器被当肉鸡挖矿的经历
  18. Bloom filters in Python
  19. 查询字五笔打法的window自带方法
  20. 合作升级!阿里宣布入股哔哩哔哩2400万股,占比8%

热门文章

  1. python 装饰器 三 (带有参数的装饰器)
  2. 在webservice中传递Hashtable
  3. ms sql 触发器( 转)
  4. ZT Web Control 开发系列(一) 页面的生命周期
  5. 在.NET3.5平台上使用LinQ to SQL + NBear 创建三层WEB应用
  6. 最小二乘法以及RANSAC(随机采样一致性)思想及实现
  7. 使用Hadoop所需要的一些Linux基础
  8. leetcode 1232. 缀点成线
  9. leetcode 922. 按奇偶排序数组 II(双指针)
  10. wordpress 插件_如何为您的Web应用程序创建WordPress插件