当前位置:首页 > > 充电吧
[导读]一、我们从上一节命令解析可以知道,u-boot启动启动Linux内核有两种方法: 第一种u-boot等待无空格按下自启内核: s = getenv ("bootcmd");     if (boot

一、我们从上一节命令解析可以知道,u-boot启动启动Linux内核有两种方法: 第一种u-boot等待无空格按下自启内核:

 s = getenv ("bootcmd");
    if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
        ......
        run_command (s, 0);
        ......
        }

第二种在u-boot控制台输入boot命令启动:

int do_bootd (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    int rcode = 0;
#ifndef CFG_HUSH_PARSER
    if (run_command (getenv ("bootcmd"), flag) < 0) rcode = 1;
#else
    if (parse_string_outer(getenv("bootcmd"),
        FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP) != 0 ) rcode = 1;
#endif
    return rcode;
}

U_BOOT_CMD(
    boot,   1,  1,  do_bootd,
    "boot    - boot default, i.e., run 'bootcmd'n",
    NULL
);

这两种方式本质上都是一样的,通过最终调用run_command(bootcmd)来启动

比如我这里bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0

二、nand read.jffs2解析

在run_command中会将命令分离并找对应的处理函数。

第一条命令是从调用(do_nand)把内核把读到到一个地址上去;第二条命令是从内核里面调用相应的函数(do_bootm)来启动内核; ① 在哪里来读 从哪里读?从kernel分区读; 读到哪里去?放到指定地址(0x30007fc0)去; 在PC机上,每一个硬盘前面都有一个分区表。对于嵌入式Linux来说,flash上面没有分区表,显然这个分区就和PC机上不一样;既然没有分区表,这些分区怎么体现?只能在源码里面写死的; 定义分区的源码如下:(includeconfigs100ask24x0.h)

#define MTDIDS_DEFAULT "nand0=nandflash0"
#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader)," 
                            "128k(params)," 
                            "2m(kernel)," 
                            "-(root)"

上面定义了各个分区的起始地址; ② 通过什么读

在/common/cmd_nand.c中do_nand函数中

/* read write */
 if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0)

三、bootm 0x30007FC0解析 ① 在/common/cmd_bootm.c中do_bootm函数中启动内核。启动内核的时候,首先对内核的头部要进行操作,原因是在Flash中保存的内核是由两部分构成的,第一部分是头部,第二部分是真正的内核。而头部的结构如下:

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;

ih_load是加载地址 ih_ep是入口地址

下面要开始分析如何启动内核,主要是do_bootm函数:

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])  
{  
    ......  
    image_header_t *hdr = &header;  //uimage 是内核加了一个4K的头部,这个头部的内容是按照结构体image_header_t来放在,是image传递给Uboot的信息。  
    ......  
    if (argc < 2) {  
        addr = load_addr;   //如果bootm的参数小于2 则使用默认的加载地址  
    } else {  
        addr = simple_strtoul(argv[1], NULL, 16);  
    }  
    ......  
    switch (hdr->ih_comp) {  
    case IH_COMP_NONE:  
        if(ntohl(hdr->ih_load) == addr) {      
/* 
    这里判断“uimage头部里指定的加载地址”与bootm指定的加载地址是否相等,不相等则需要移动 
    判断的方式有两种 
    1、判断 uimage头部里指定的加载地址 == bootm指定的加载地址 (hdr->ih_load == addr) 
    此时: 
        实际存放的地址  == uimage加载地址 == uimage连接地址-64字节 
        bootm == 实际存放的地址 
    例子: 
        实际存放在  0x30007fc0 
        bootm       0x30007fc0 
        加载地址    0x30007fc0 
        连接地址    0x30008000 
        1、uboot根据Bootm指定的0x30007fc0去找image,实际地址为0x30007fc0,找到头部 
        2、读出头部里的加载地址,判断是否和Bootm相等,相等则不移动,启动 
    2、判断 uimage头部里指定的加载地址 == bootm指定的加载地址 + 64字节 (hdr->ih_load == addr+64字节/data) 
    此时:  
        实际存放地址+64字节 == uimage加载地址 == uimage连接地址 
        bootm == 实际存放的地址 
    例子:  
        实际存放在  0x30007fc0 
        bootm       0x30007fc0 
        加载地址    0x30008000 
        连接地址    0x30008000 
        1、uboot根据Bootm指定的0x30007fc0去找image,实际地址为0x30007fc0,找到头部 
        2、读出头部里的加载地址,判断是否和Bootm + 字节相等,相等则不移动,启动 

    首先bootm的地址要和我们 实际存放(不管它的加载地址是多少) 整个uimage的首地址吻合,这样就可以找到头部。 
    这里存在两种情况,我们可以看到 Uboot源码里 
    1、 hdr->ih_load == addr  
    也就是说判断的是bootm_addr 与uimage里的ih_load加载地址是否相等,这样的话,我们在制作uimage的时候就应该让ih_load=bootmaddr 
    那么uimage里的另一个参数,连接地址就应该等于bootm+64字节 
    2、hdr->ih_load == addr+64字节  友善以及韦东山老师的uboot里判断条件都被改成了这样 
    那么,uimage里的Load地址和连接地址应该相等,等于bootm+64字节 
*/  
            printf ("   XIP %s ... ", name);  
        } else {  
    ......do_bootm_linux  (cmdtp, flag, argc, argv,addr, len_ptr, verify);  
}

总结:do_bootm有两个作用: 作用1:读取内核头部将内核移动到合适地方,还有一些校验 作用2:启动内核,用的是do_bootm_linux函数。在跳到ih_ep入口之前还要uboot设置内核启动参数,然后才是跳到ih_ep启动内核。 ② do_bootm_linux函数有用的代码如下:

//uboot参数设置
#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)
 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
 setup_end_tag (bd);
#endif


 printf ("nStarting kernel ...nn");


//启动内核
 theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

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

总结:do_bootm_linux有两个作用: 作用1:设置内核启动参数,参数的格式是tag(标记列表),对于韦东山的开发板标记列表开始地址是0x30000100,一般来说有四个标记传参函数。setup_start_tag (bd);setup_memory_tags (bd);setup_commandline_tag (bd, commandline);setup_end_tag (bd);作用2:跳到入口地址去是

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); 
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

这样就启动内核了!!! 我们再来分析一下theKernel的三个参数 (0, bd->bi_arch_number, bd->bi_boot_params); 第一个参数是固定的

通过看来自于linux-2.6.30.4DocumentationarmBooting:
 

第二个参数是机器类型ID

看board100ask24x0100ask24x0.c

第三个参数是标记列表的开始地址

看board100ask24x0100ask24x0.c
 

1.启动标记:
setup_start_tag:

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);
}

由代码可以得到tag是一个结构体,bi_boot_params为0x300000100,ATAG_CORE为54410001

2.内存标记:
setup_memory_tag:

#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)
{
 int i;

 for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
  params->hdr.tag = ATAG_MEM;
  params->hdr.size = tag_size (tag_mem32);

  params->u.mem.start = bd->bi_dram[i].start;
  params->u.mem.size = bd->bi_dram[i].size;

  params = tag_next (params);
 }
}
#endif

bi_dram[i].start;内存的初始地址,bi_dram[i].start;内存的大小
 这两个参数的初始值在start_armboot()函数中dram_init可以设置。

3.命令行标记:

static void setup_commandline_tag (bd_t *bd, char *commandline)
{
 char *p;

 if (!commandline)
  return;

 把命令前面的空格给干掉
 for (p = commandline; *p == ' '; p++);


 if (*p == '')
  return;

 params->hdr.tag = ATAG_CMDLINE;
 params->hdr.size =
  (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;

 strcpy (params->u.cmdline.cmdline, p);

 params = tag_next (params);
}

*commandlinechar的来源为*commandline = getenv ("bootargs");
   那么在终端所获得的信息是bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
   root=/dev/mtdblock3     根文件系统在Flash中的第三分区
   init=/linuxrc         第一个进程为linuxrc
   console=ttySAC0      内核打印信息从串口输出

4.结束标记:
setup_end_tag:

static void setup_end_tag (bd_t *bd)
{
 params->hdr.tag = ATAG_NONE;
 params->hdr.size = 0;
}

参数设置结束的时候将tag设置为空,size设置为0.

三、总结:

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭