进入uboot后,输入printenv可以看到一个环境变量,bootcmd,这个环境变量里面内容的作用就是把内核从NandFlash中读取到SDRAM中,然后从SDRAM的加载地址启动内核。
一、内核是什么
内核实际上就是一个比较复杂庞大的裸板程序,只不过是内核无法自启动,必须借助boot来启动,内核运行起来后,在软件分层上分成了应用层和内核层,内核层可以随意的访问硬件,应用层无法随意的访问硬件资源,想要访问硬件资源必须open后产生软中断进入内核层。
内核的镜像文件是uImage,uImage是一个信息头+一个内核文件
#define IH_NMLEN 32 /* Image Name Length */
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; /* 加载地址 */
uint32_t ih_ep; /* 入口地址 */
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;
这个结构体就是uImage的头部信息,一共64字节,所以启动内核时要偏移64字节
二、嵌入式linux分区
一个完整的嵌入式系统,必须有bootloader、环境变量、kernel、rootfs等内容,他们都以镜像文件的形式存储在FLASH中,运行时重定位到SDRAM中。
嵌入式系统的分区,并不像PC机的分区一样,他的分区是写死的,记录在include/configs中
#define MTDIDS_DEFAULT "nand0=nandflash0"
#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader)," \
"128k(params)," \
"2m(kernel)," \
"-(root)"
三、uboot内核启动过程
1.启动内核第一步:加载内核到SDRAM中
内核镜像存储在NAND FLash的Kernel分区中,需要此时需要把真正的内核从FLASH读取到SDRAM的0x30008000这个地址。
但是我们查看uboot的环境变量,
加载内核到SDRAM中的指令是nand read.jffs2 0x30007FC0 kernel,这不对啊,地址不应该是0x30008000吗?因为内核镜像文件的头部占据的64字节,这样的话,真正的内核文件就读取到了0x30008000这个地址了。
2.启动内核
2.1校验内核的格式:uImage、Image、 zImage
内核有三种格式:uImage、Image、 zImage
Linux内核经过编译会生成一个elf格式的可重定位的文件,然后经过编译器的objcopy工具会生成一个可以烧录的镜像文件Image,这个制作烧录镜像主要目的就是缩减大小,节省磁盘。
zImage就是在Image的基础上再进行压缩
uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加上64字节的信息头生成uImage。
注意:uImage不关linux内核的事,linux内核只管生成zImage即可
在com_bootm.c的do_bootm函数中进行镜像文件的校验,校验成功才可以加载
data = (ulong)&header;
len = sizeof(image_header_t);
checksum = ntohl(hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
SHOW_BOOT_PROGRESS (3);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash(addr)){
len = ntohl(hdr->ih_size) + sizeof(image_header_t);
read_dataflash(addr, len, (char *)CFG_LOAD_ADDR);
addr = CFG_LOAD_ADDR;
}
#endif
/* for multi-file images we need the data part, too */
print_image_hdr ((image_header_t *)addr);
data = addr + sizeof(image_header_t);
len = ntohl(hdr->ih_size);
if (verify) {
puts (" Verifying Checksum ... ");
if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-3);
return 1;
}
puts ("OK\n");
}
SHOW_BOOT_PROGRESS (4);
len_ptr = (ulong *)data;
2.2启动内核
bootm 0x30007FC0 启动内核 bootm这个指令会将内核移动到加载地址,然后跳到入口地址
加载地址:内核镜像整体要放置的内存空间位置
入口地址:从入口地址开始执行内核代码(一般是_start指示的tag位置)
bootm命令可以分成两个部分
1.移动内核到内核的加载地址(uImage头部定义了in_load)
2.启动内核。调用do_boot_linux()函数。该函数会设置启动参数(RAM大小等),然后跳到入口地址。
2.1设置启动参数,在armlinux.c的do_bootm_linux()中有如下代码
#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 (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#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
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
其中的setup_xxxx_tag(db)就是用来设置各种启动参数,uboot会和内核约定一个位置用 来存放这些参数。
当启动参数设置后,在0x30000100的位置上会开始保存这些参数
2.2启动内核
void (*theKernel)(int zero, int arch, uint params);
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);