U-boot 启动内核

1:什么是UBOOT,为什么要有UBOOT?

  UBOOT的主要作用是用来启动linux内核,因为CPU不能直接从块设备中执行代码,需要把块设备中的程序复制到内存中,而复制之前还需要进行很多初始化工作,如时钟、串口、dram等;

  如要想让CPU启动linux内核,只能通过另外的程序,进行必要的初始化工作,在把linux内核中代码复制到内存中,并执行这块内存中的代码,即可启动linux内核;一般情况下,我们把linux

  镜像储存在块设备中如SD卡、iNand、Nandflash等块设备中,首先执行UBOOT带码,在UBOOT中把块设备中的内核代码复制到内存地址0x30008000地址处,然后在执行bootm 0x30008000

  命令来执行内核代码;

整个过程大致如上述所讲,下面我们详细分析一下UBOOT启动内核的代码:

2:在启动UBOOT时候会出现看机倒计时,如果没有按键按下,会自动启动内核,我们来看一下这个是如何实现的:

下面这段代码是在main_loop函数中:作用是执行完倒数计时函数以后启动linux内核,启动方式是 s = getenv ("bootcmd");我们假定不使用HUAH_PARSER的情况下 run_command (s, 0);

实际上就是读取环境变量bootcmd,然后执行这个命令;

s = getenv ("bootcmd");

    debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

    if (bootdelay >= 0 && s && !abortboot (bootdelay)) {

#ifndef CFG_HUSH_PARSER
        run_command (s, 0);
#else
        parse_string_outer(s, FLAG_PARSE_SEMICOLON |
                    FLAG_EXIT_FROM_LOOP);

看一下bootcmd命令:bootcmd=movi read kernel 30008000; movi read rootfs 30B00000 300000; bootm 30008000 30B00000

movi read kernel 30008000 以及 bootm 30008000

这两个命令来完成linux内核启动的:

movi read kernel 30008000是把sd卡中kernel分区复制到30008000内存地址处,bootm 30008000即到内存地址处执行代码;

下面详细分一下bootm这个命令对应的函数

代码一步步分析:

下面这段代码的作用是判断内核镜像是zImage、uImage、设备树

在这里要解释一下zImage、uImage的区别:

linux内核代码经过编译链接以后生成一个elf文件名叫vmlinuz文件,这个文件在经过arm-linux-objcopy编译以后会生成一个Image镜像文件,vmlinuz elf文件大小为70M以上

而Image镜像文件为7M左右,然后Image文件在进一步经过压缩生成zImage文件,当zImage文件作为启动镜像来启动时,首先要解压这个文件,这个解压过程可以由uboot解压

或者zImage文件本身可以自解压,zImage中除了linux内核的镜像以外,还有一些头文件以及这部分解压代码,所以内核实际上在addr地址中在加一个偏移量的位置;

uImage是uboot自己专用的启动内核镜像,相对于zImage他们之间头文件有一定区别可以详细看代码是如何判断的;uImage现在基本上要属于过时的技术了,新一点的技术为

设备树的启动方式;

我们时这么使用bootm命令的:bootm 0x30008000

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

addr中的值为0x30008000

接下来判断0x30008000右偏移36字节以后,这个地址中的值如果为 0x016f2818这个魔数的话,说明启动镜像为zImage则 输出boot with zImage,

  hdr->ih_os = IH_OS_LINUX;      zImage header中 IH_os 赋值为 IH_OS_LINUX;

hdr->ih_ep = ntohl(addr);      ih_ep 中存放的是point address 这个值实际上就是真正内核代码的地址;

在看下面这句代码

memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));

把hdr中的值复制一份到 image.legacy_hdr_os_copy中,即把内存地址0x30008000处设置好的zImage头复制一份到uboot的data段,

因为static bootm_headers_t images; images为uboot内定义的一个bootm_header_t格式的全局变量;

看一下bootm_header_t类型为一个结构体,包含一个image_header_t类型的指针,这个指针最后指向了0x30008000处的zImage header

还包含一个image_header_t类型的结构体,就是用上面那句代码把0x30008000处的zImage header在酯类复制了一份;

还包含一个标志位 legacy_hdr_valid如果上面两个赋值以后,把legacy_hdr_valid赋值为1;

typedef struct bootm_headers {

    image_header_t    *legacy_hdr_os;        /* image header pointer */
    image_header_t    legacy_hdr_os_copy;    /* header copy */
    ulong        legacy_hdr_valid;
}

uint8_t ih_os; /* Operating System */

typedef struct image_header {
    uint32_t    ih_magic;    /* Image Header Magic Number    */
    uint32_t    ih_hcrc;    /* Image Header CRC Checksum    */
    uint32_t    ih_time;    /* Image Creation Timestamp    */
    uint32_t    ih_size;    /* Image Data Size        */
    uint32_t    ih_load;    /* Data     Load  Address        */
    uint32_t    ih_ep;        /* Entry Point Address        */
    uint32_t    ih_dcrc;    /* Image Data CRC Checksum    */
    uint8_t        ih_os;        /* Operating System        */
    uint8_t        ih_arch;    /* CPU architecture        */
    uint8_t        ih_type;    /* Image Type            */
    uint8_t        ih_comp;    /* Compression Type        */
    uint8_t        ih_name[IH_NMLEN];    /* Image Name        */
} image_header_t;

#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC    0x016f2818
    /* find out kernel image address */
    if (argc < 2) {
        addr = load_addr;
        debug ("*  kernel: default image load address = 0x%08lx\n",
                load_addr);
    } else {
        addr = simple_strtoul(argv[1], NULL, 16);
        debug ("*  kernel: cmdline image address = 0x%08lx\n", img_addr);
    }

    if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
        printf("Boot with zImage\n");
        addr = virt_to_phys(addr);
        hdr = (image_header_t *)addr;
        hdr->ih_os = IH_OS_LINUX;
        hdr->ih_ep = ntohl(addr);

        memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));

        /* save pointer to image header */
        images.legacy_hdr_os = hdr;

        images.legacy_hdr_valid = 1;

        goto after_header_check;
    }
#endif

直接跳转到after_header_check处,os为IH_OS_LINUX

下面判断操作系统,然后调用do_bootm_linux函数;

do_bootm_linux (cmdtp, flag, argc, argv, &images);

 1 #if defined(CONFIG_ZIMAGE_BOOT)
 2 after_header_check:
 3     os = hdr->ih_os;
 4 #endif
 5
 6     switch (os) {
 7     default:            /* handled by (original) Linux case */
 8     case IH_OS_LINUX:
 9 #ifdef CONFIG_SILENT_CONSOLE
10         fixup_silent_linux();
11 #endif
12         do_bootm_linux (cmdtp, flag, argc, argv, &images);
13         break;
14
15     case IH_OS_NETBSD:
16         do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
17         break;
18
19 #ifdef CONFIG_LYNXKDI
20     case IH_OS_LYNXOS:
21         do_bootm_lynxkdi (cmdtp, flag, argc, argv, &images);
22         break;
23 #endif
24
25     case IH_OS_RTEMS:
26         do_bootm_rtems (cmdtp, flag, argc, argv, &images);
27         break;
28
29 #if defined(CONFIG_CMD_ELF)
30     case IH_OS_VXWORKS:
31         do_bootm_vxworks (cmdtp, flag, argc, argv, &images);
32         break;
33
34     case IH_OS_QNX:
35         do_bootm_qnxelf (cmdtp, flag, argc, argv, &images);
36         break;
37 #endif
38
39 #ifdef CONFIG_ARTOS
40     case IH_OS_ARTOS:
41         do_bootm_artos (cmdtp, flag, argc, argv, &images);
42         break;
43 #endif
44     }
45
46     show_boot_progress (-9);
47 #ifdef DEBUG
48     puts ("\n## Control returned to monitor - resetting...\n");
49     do_reset (cmdtp, flag, argc, argv);
50 #endif
51     if (iflag)
52         enable_interrupts();
53
54     return 1;
55 }

下面看一下do_bootm_linux都做了哪些事情

#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
#endif

首先获取环境变量bootargs:

if (images->legacy_hdr_valid) {
        ep = image_get_ep (&images->legacy_hdr_os_copy)

else {
puts ("Could not find kernel entry point!\n");
goto error;
}

 

在判断全局变量images中的legacy_hdr_valid是否为1,如果为1 获取ep 值;如果为1读出ep的值,如果不为1则erro

theKernel = (void (*)(int, int, uint))ep;

    s = getenv ("machid");
    if (s) {
        machid = simple_strtoul (s, NULL, 16);
        printf ("Using machid 0x%x from environment\n", machid);
    }

把ep强制类型换换为函数指针类型复制给thekernel;

从环境变量中读取machid的值,赋值给s,如果s不空 则machid = 环境变量中machid的值,并打印machid;

在看一下uboot如何给内核传参:

传参主要是uboot把与硬件有关的信息传给linux内核,如memory信息几bank size 起始地址、命令行信息、lcd 串口、initrd、MTD等信息

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) ||     defined (CONFIG_INITRD_TAG) ||     defined (CONFIG_SERIAL_TAG) ||     defined (CONFIG_REVISION_TAG) ||     defined (CONFIG_LCD) ||     defined (CONFIG_VFD) ||     defined (CONFIG_MTDPARTITION)
    setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
    setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
    setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
    setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
    setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
    if (initrd_start && initrd_end)
        setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
    setup_videolfb_tag ((gd_t *) gd);
#endif

#ifdef CONFIG_MTDPARTITION
    setup_mtdpartition_tag();
#endif

    setup_end_tag (bd);
#endif

    /* we assume that the kernel is in place */
    printf ("\nStarting kernel ...\n\n");

#ifdef CONFIG_USB_DEVICE
    {
        extern void udc_disconnect (void);
        udc_disconnect ();
    }
#endif

    cleanup_before_linux ();

    theKernel (0, machid, bd->bi_boot_params);
    /* does not return */

首先:如要定义了任意一个CONFIG_XXXXX的话

struct tag {
        struct tag_header hdr;
        union {
                struct tag_core         core;
                struct tag_mem32        mem;
                struct tag_videotext    videotext;
                struct tag_ramdisk      ramdisk;
                struct tag_initrd       initrd;
                struct tag_serialnr     serialnr;
                struct tag_revision     revision;
                struct tag_videolfb     videolfb;
                struct tag_cmdline      cmdline;

                /*
                * Acorn specific
                */
                struct tag_acorn        acorn;

                /*
                 * DC21285 specific
                 */
                struct tag_memclk       memclk;

                struct tag_mtdpart      mtdpart_info;
        } u;
};

static void setup_start_tag (bd_t *bd)
{
    params = (struct tag *) bd->bi_boot_params;

    params->hdr.tag = ATAG_CORE;
    params->hdr.size = tag_size (tag_core);

    params->u.core.flags = 0;
    params->u.core.pagesize = 0;
    params->u.core.rootdev = 0;

    params = tag_next (params);
}

 

struct tag_header {
    u32 size;
    u32 tag;
};

首先要setup_start_tag(bd); 这个函数的作用

  params = (struct tag *) bd->bi_boot_params; 给params赋值,gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);

  这句代码的作用就是把uboot全局变量中设定好的bi_boot_params内存地址处强制转换为stuct tag* 类型赋值给params

  分析一下struct tag结构体:它是由一个stuct tag_header类型的结构体加上一个由一系列结构体组成的union联合体组成;

  这一系列结构体中存放的就是与board有关的参数;

  把PHYS_SDRAM_1+0x100这个地址设置为传参的起始地址;

  params->hdr.tag = ATAG_CORE;

  params->hdr.size = tag_size (tag_core);

  hdr.tag 与hdr.size赋值;

  params->u.core.flags = 0;

  params->u.core.pagesize = 0;

  params->u.core.rootdev = 0;

  然后对联合体中的结构体参数赋值;

   #define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))

  params = tag_next (params);

  在把params向右移动sizeof(tag_core)大小

  继续赋值:

  #ifdef CONFIG_SETUP_MEMORY_TAGS

   setup_memory_tags (bd); 

  #endif

  这段代码是传递内存参数:

  把内存每个bank的信息放到这里:第一个扇区的起始地址和大小,第二个扇区的起始地址和大小

 1 #ifdef CONFIG_SETUP_MEMORY_TAGS
 2 static void setup_memory_tags (bd_t *bd)
 3 {
 4     int i;
 5
 6     for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
 7         params->hdr.tag = ATAG_MEM;
 8         params->hdr.size = tag_size (tag_mem32);
 9
10         params->u.mem.start = bd->bi_dram[i].start;
11         params->u.mem.size = bd->bi_dram[i].size;
12
13         params = tag_next (params);
14     }
15 }
16 #endif /* CONFIG_SETUP_MEMORY_TAGS */

  接着是传递命令行参数

 1 static void setup_commandline_tag (bd_t *bd, char *commandline)
 2 {
 3     char *p;
 4
 5     if (!commandline)
 6         return;
 7
 8     /* eat leading white space */
 9     for (p = commandline; *p == ‘ ‘; p++);
10
11     /* skip non-existent command lines so the kernel will still
12      * use its default command line.
13      */
14     if (*p == ‘\0‘)
15         return;
16
17     params->hdr.tag = ATAG_CMDLINE;
18     params->hdr.size =
19         (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
20
21     strcpy (params->u.cmdline.cmdline, p);
22
23     params = tag_next (params);  }

前面我们分析了commandline是一个char *类型,指向环境变量中的bootargs的值;

#define CONFIG_BOOTARGS     "root=/dev/mtdblock4 rootfstype=yaffs2 init=/init console=ttySAC0,115200"

最后setup_end_tag (bd);结束传参

再看最后uboot中最后一句代码

theKernel (0, machid, bd->bi_boot_params);
    /* does not return */
    return;

通过执行thekernel函数直接启动linux内核,传递三个参数0、machid、传参的首地址;

这三个参数是通过r0、r1、r2三个寄存器来传递了,r0传递0、r1传递machid、r2传递传参的首地址;

这样就启动起来linux内核了;

-----------------------------------------------------------------------------------------------------------------------------------

下面我们再来总结一下uboot启动linux内核的整个流程:

开机时会出现倒计时,当没有按键按下的时候,uboot会读取出bootcmd这个环境变量,并使用rum_command函数来执行这个命令;

实质是执行了movi read kernel 30008000;以后在执行bootm 30008000

moviread kernel的作用是把sd卡中的kernel分区赋值到30008000内存处

bootm 30008000就是真正的传参以及跳转到linux内核中执行;

bootm 首先要做的事情是判断这个内核镜像为zImage、uImage、设备树

通过对镜像文件的头文件的验证,确定是哪种内核镜像,然后再把必须的信息储存起来如是linux操作系统,ep的值等;

确定好以后调用do_bootm_linux函数来对内核传参并且启动内核;

来源:https://www.cnblogs.com/biaohc/p/6403863.html

原文地址:https://www.cnblogs.com/zzdbullet/p/9486398.html

时间: 2024-10-09 17:04:24

U-boot 启动内核的相关文章

S5P210-uboot源码分析-uboot如何启动内核

uboot如何启动内核 7.1.uboot和内核到底是什么? 1.uboot是一个裸机程序 (1)uboot的本质就是一个复杂点的裸机程序,和我们arm裸机中写的程序没有什么本质上的区别. (2)uboot最像我们在arm裸机中的最后写的那个shell,它其实就是一个迷你型的uboot. 2.linux内核本身也是一个"裸机程序" (1)操作系统内核本身就是一个裸机程序,和uboot并没有本质区别. (2)区别在于,操作系统运行起来后在软件层次上可以分为内核层和应用层,分层后两层的权限

Redhat/Centos 设置默认启动内核

一般编译新内核完成后 ,系统默认启动的还是之前的旧内核,如果不手动选择是不会自己启动新内核的,所以我们可以更改新内核为默认启动! #此案例是编译的4.4.0的新内核! 查看默认启动的内核 # grub2-editenv list 可以看出默认启动的内核是3.10.0! 2.查看所有内核 # cat /boot/grub2/grub.cfg | grep menuentry 可以看出有两个内核版本! 3.接着修改修改最新内核为默认启动(这儿只能使用上面命令输出中双引号 " " 或者单引号

ubuntu13.04修改默认启动内核

ubuntu下面的启动内核选项跟其他操作系统不一样,有个子菜单,比如我在默认的ubuntu13.04上安装了一个新的内核3.14.5,那么默认的第一项是3.14.5内核,第二项是一个子菜单,第二项里面的第一项是3.14.5,第二项是3.14.5 recovery 模式 第三项是3.8.0,第四项是3.8.0(recover) 那么应该修改 /boot/grub/grub.cfg 中的 GRUB_DEFAULT=0为  GRUB_DEFAULT="1>2" 然后update-gru

AM335x(TQ335x)学习笔记——启动内核

老式的u-boot使用ATAGS的方式启动linux内核,本文使用新式的dtb方式启动内核. 我使用的内核是linux-3.17.2版本,下面开始编译内核. (1) 解压内核 tar jxf linux-3.17.2.tar.bz2 (2)配置linux内核,由于am335x在内核中都归为omap2系列,故可以使用如下命令: make ARCH=arm omap2plus_defconfig (3)编译内核: make ARCH=arm CROSS_COMPILE=arm-linux-gnuea

linux的几个内核镜像格式Image 和 u-boot启动内核和文件系统时的一些环境变量的设置

关于编译powerpc linux的几个Image参考原文 http://blog.sina.com.cn/s/blog_86a30b0c0100wfzt.html 转载▼ PowerPC架构 Linux和ARM,X86等平台有些差异,PowerPC平台HW参数不是通过命令行方式传递到Linux,而是通过传递Device tree文件的方式传递参数,所以PowerPC平台Linux需要编译dtb和uImage才能正常加载,另外PowerPC架构linux还提供simpleImage的方式加载,也

tiny4412学习(一)之从零搭建linux系统(烧写uboot、内核进emmc+uboot启动内核)【转】

本文转载自:http://blog.csdn.net/fengyuwuzu0519/article/details/74080109 版权声明:本文为博主原创文章,转载请注明http://blog.csdn.net/fengyuwuzu0519. 目录(?)[+] 硬件平台:tiny4412 系统:linux-3.5-20151029 文件系统:busybox-1.22.1.tar.bz2 编译器: arm-linux-gcc-4.5.1 目的: 使用uboot引导Linux系统,并挂接根文件系

第3阶段——内核启动分析之创建si工程和启动内核分析(3)

目标: (1)创建Source Insight 工程,方便后面分析如何启动内核的 (2)分析uboot传递参数,链接脚本如何进入stext的 (3) 分析stext函数如何启动内核 1 创建内核source sight 工程 1.1 点击 "add all" 添加所有文件,后面再慢慢删去Arch目录和Include目录中与2440芯片没用的文件. 1.2 点击Remove Tree 删除Arch文件夹,再添加与2440相关的硬件核心代码以及其它公用的代码 Arch:包含了平台,处理器相

嵌入式linux开发uboot移植(四)——uboot启动内核的机制

嵌入式linux开发uboot移植(四)--uboot启动内核的机制 一.嵌入式系统的分区 嵌入式系统部署在Flash设备上时,对于不同SoC和Flash设备,bootloader.kernel.rootfs的分区是不同的.三星S5PV210规定启动设备的分区方案如下: SD/MMC设备的分区方案: NandFlash设备的分区方案: 嵌入式系统在启动时,uboot.kernel.rootfs不能随意存放,必须存放在规划好的相应分区,在启动过程中uboot.kernel会到相应分区加载相应内容,

centos7 选定默认启动内核,及删除无用内核

转自 https://www.cnblogs.com/niyeshiyoumo/p/6762193.html centos7 选定默认启动内核,及删除无用内核 #使用cat /boot/grub2/grub.cfg |grep menuentry 查看系统可用内核 [[email protected]-slave27 ~]# cat /boot/grub2/grub.cfg |grep menuentry if [ x"${feature_menuentry_id}" = xy ];