uboot的本质就是一个复杂点的裸机程序,和我们编写的用于操作某个硬件模块(如led)的裸机程序并无差别,只不过功能更复杂,包含了许多软件设计思想:分层、分离、耦合度等等。
(1)操作系统内核本身就是一个裸机程序,和uboot、和其他裸机程序并没有本质区别。
(2)区别就是操作系统运行起来后在软件上分为 内核层 和 应用层,分层后两层的权限不同,内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能被限制的访问硬件和内存地址,为了安全管控)。
直观来看:uboot的镜像是u-boot.bin,linux系统的镜像是zImage(之所以没带.bin,是起名字的问题,本质是和uboot.bin一样的),这两个东西其实都是两个裸机程序镜像。从系统的启动角度来讲,内核其实就是一个大的复杂点裸机程序。
使用无盘系统的计算机将不使用本机的硬盘获得启动系统镜像,而是通过网络的指定服务器去获得启动系统的镜像,并下载回本机后用于机器启动。同时也不需要使用传统硬盘读取资料,而是通过局域网内的服务器读取资料。掉电后电脑的数据不保存。例如网吧中的电脑。
(1)一个完整的 软件 + 硬件 的嵌入式系统,静止时(未上电时)bootloader、kernel、rootfs等必须的软件都以镜像的形式存储在启动介质中(X210中是iNand/SD卡);运行时都是在DDR内存中运行的,与存储介质无关。上面2个状态都是稳定状态,第3个状态是动态过程,即从静止态到运行态的过程,也就是启动过程。
(2)动态启动过程就是一个从SD卡逐步搬移到DDR内存,并且运行启动代码进行相关的硬件初始化和软件架构的建立,最终达到运行时稳定状态。
(3)静止时u-boot.bin、zImage、rootfs都在SD卡中,他们不可能随意存在SD卡的任意位置,因此需要对SD卡(我的开发板是SD以实际存放这些文件的介质为准)进行一个分区,然后将各种镜像各自存在各自的分区中,这样在启动过程中uboot、内核等就知道到哪里去找谁。(uboot和kernel中的分区表必须一致,同时和SD卡的实际使用的分区要一致,否则无法读取相应的镜像进行启动。)
(1)uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR的0xc3e00000地址处,这个地址就是uboot的链接地址。
(2)内核也有类似要求,uboot启动内核时将内核从SD卡读取放到DDR中(其实就是个重定位的过程),不能随意放置,必须放在内核的链接地址处,否则启动不起来。譬如我们使用的内核链接地址是0x30008000。
(1)uboot是无条件启动的,从零开始启动的。
(2)内核是不能开机自动完全从零开始启动的,内核启动要别人帮忙。uboot要帮助内核实现重定位(从SD卡到DDR),uboot还要给内核提供启动参数。
uboot要启动内核,分为2个步骤:第一步是将内核镜像从启动介质中加载到DDR中,第二步是去DDR中启动内核镜像。(内核代码自己本身根本就没考虑重定位,因为内核知道会有uboot之类的把自己加载到DDR中链接地址处的,所以内核直接就是从链接地址处开始运行的)
(1)SD卡/iNand/Nand/NorFlash等:raw(原始)分区
常规启动时各种镜像都在SD卡(这里SD卡只是一个代称,具体以自己开发板的启动介质为主,如inand、nand、emmc)中,因此uboot只需要从SD卡的kernel分区去读取内核镜像到DDR中即可。读取要使用uboot的命令来读取(譬如X210的iNand版本是movi命令,X210的Nand版本就是Nand命令),可通过fastboot在uboot命令行查看分区
(2)加载ddr,使用命令:movi read kernel 30008000。其中kernel指的是uboot中的kernel分区(就是uboot中规定的SD卡中的一个区域范围,这个区域范围被设计来存放kernel镜像,就是所谓的kernel分区)
(2)也可通过tftp、nfs等网络下载方式从远端服务器获取镜像
uboot还支持远程启动,也就是内核镜像不烧录到开发板的SD卡中,而是放在主机的服务器中,然后需要启动时uboot通过网络从服务器中下载镜像到开发板的DDR中。
分析总结:最终结果要的是内核镜像到DDR中特定地址即可,不管内核镜像是怎么到DDR中的。以上2种方式各有优劣。产品出厂时会设置为从SD卡中启动(避免客户还要搭建tftp服务器才能用···);tftp下载远程启动这种方式一般用来开发,测试kernel镜像,先不用fastboot烧录,通过服务器的方式将其下载到DDR内存中取运行,看是否可用。
内核一定要放在链接地址处,链接地址去内核源代码的链接脚本或者Makefile中去查找。我的开发板中是0x30008000。
(1)命令名前加do_即可构成这个uboot命令对应的函数,因此当我们bootm命令执行时,uboot实际执行的函数叫do_bootm函数,在cmd_bootm.c。
(2)do_bootm刚开始定义了一些变量,然后用宏来条件编译执行了secureboot的一些代码(主要进行签名认证,进行校验确保是正确的),先不管它;然后进行了一些一些细节部分操作,也不管它。然后到了CONFIG_ZIMAGE_BOOT,用这个宏来控制进行条件编译一段代码,这段代码是用来支持zImage格式的内核启动的。
(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的技术)。
Ubuntu直接使用vmlinux进行启动,因为其是桌面系统使用硬盘,硬盘空间足够大,不需要考虑空间和成本的问题。而且这样做省时间,不需要解压。
(4)uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生成uImage。
注意:uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可(利用mkimage.c 与 mkimage.h生成的mkimage工具)。
早期Linux内核不是用uboot启动的,而是用别的bootloader。
(4)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了CONFIG_ZIMAGE_BOOT这个宏。所以大家可以看出:有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。
如果直接在kernel底下去make uImage会显示mkimage command not found。解决方案是去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统目录下。再去make uImage即可。
uboot开始并不支持zImage启动,是之后添加上去的
/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
image_header_t *hdr;
ulong addr;
ulong iflag;
const char *type_name;
uint unc_len = CFG_BOOTM_LEN;
uint8_t comp, type, os;
void *os_hdr;
ulong os_data, os_len;
ulong image_start, image_end;
ulong load_start, load_end;
ulong mem_start;
phys_size_t mem_size;
struct lmb lmb;
#if defined(CONFIG_SECURE_BOOT)
int rv;
#endif
#if defined(CONFIG_SECURE_BOOT)//一般手机这些设备·不需要这么严格的检查,所以并未定义这个宏进行安全校验
rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR,
(unsigned char*)CONFIG_SECURE_KERNEL_BASE,
CONFIG_SECURE_KERNEL_SIZE-128,
(unsigned char*)(CONFIG_SECURE_KERNEL_BASE+CONFIG_SECURE_KERNEL_SIZE-128),
128 );
if(rv != SB_OK) {
printf("Kernel Integrity check fail\nSystem Halt....");
while(1);
}
printf("Kernel Integirty check success.\n");
rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR,
(unsigned char*)CONFIG_SECURE_ROOTFS_BASE,
CONFIG_SECURE_ROOTFS_SIZE-128,
(unsigned char*)(CONFIG_SECURE_ROOTFS_BASE+CONFIG_SECURE_ROOTFS_SIZE-128),
128 );
if(rv != SB_OK) {
printf("rootfs Integrity check fail\nSystem Halt....");
while(1);
}
printf("rootfs Integirty check success.\n");
#endif
memset ((void *)&images, 0, sizeof (images));
images.verify = getenv_yesno ("verify");
images.lmb = &lmb;
lmb_init(&lmb);
mem_start = getenv_bootm_low();
mem_size = getenv_bootm_size();
lmb_add(&lmb, (phys_addr_t)mem_start, mem_size);
board_lmb_reserve(&lmb);
#ifdef CONFIG_ZIMAGE_BOOT //支持zImage启动
#define LINUX_ZIMAGE_MAGIC 0x016f2818
/* find out kernel image address */
if (argc < 2) {//函数参数argc的值
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) {//zImage头部开始的第37-40字节处存放着zImage标志魔数
printf("Boot with zImage\n"); //从这个位置取出然后对比LINUX_ZIMAGE_MAGIC
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
/* get kernel image header, start address and length */
os_hdr =
(cmdtp, flag, argc, argv,
&images, &os_data, &os_len);
if (os_len == 0) {
puts ("ERROR: can't get kernel image!\n");
return 1;
}
/* get image parameters */
switch (genimg_get_format (os_hdr)) {
case IMAGE_FORMAT_LEGACY://uImage方式
type = image_get_type (os_hdr);
comp = image_get_comp (os_hdr);
os = image_get_os (os_hdr);
image_end = image_get_image_end (os_hdr);
load_start = image_get_load (os_hdr);
break;
#if defined(CONFIG_FIT)//设备树方式
case IMAGE_FORMAT_FIT:
if (fit_image_get_type (images.fit_hdr_os,
images.fit_noffset_os, &type)) {
puts ("Can't get image type!\n");
show_boot_progress (-109);
return 1;
}
if (fit_image_get_comp (images.fit_hdr_os,
images.fit_noffset_os, &comp)) {
puts ("Can't get image compression!\n");
show_boot_progress (-110);
return 1;
}
if (fit_image_get_os (images.fit_hdr_os,
images.fit_noffset_os, &os)) {
puts ("Can't get image OS!\n");
show_boot_progress (-111);
return 1;
}
image_end = fit_get_end (images.fit_hdr_os);
if (fit_image_get_load (images.fit_hdr_os, images.fit_noffset_os,
&load_start)) {
puts ("Can't get image load address!\n");
show_boot_progress (-112);
return 1;
}
break;
#endif
default:
puts ("ERROR: unknown image format type!\n");
return 1;
}
image_start = (ulong)os_hdr;
load_end = 0;
type_name = genimg_get_type_name (type);
/*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/
iflag = disable_interrupts();
#if defined(CONFIG_CMD_USB)
/*
* turn off USB to prevent the host controller from writing to the
* SDRAM while Linux is booting. This could happen (at least for OHCI
* controller), because the HCCA (Host Controller Communication Area)
* lies within the SDRAM and the host controller writes continously to
* this area (as busmaster!). The HccaFrameNumber is for example
* updated every 1 ms within the HCCA structure in SDRAM! For more
* details see the OpenHCI specification.
*/
usb_stop();
#endif
#ifdef CONFIG_AMIGAONEG3SE
/*
* We've possible left the caches enabled during
* bios emulation, so turn them off again
*/
icache_disable();
invalidate_l1_instruction_cache();
flush_data_cache();
dcache_disable();
#endif
switch (comp) {
case IH_COMP_NONE:
if (load_start == (ulong)os_hdr) {
printf (" XIP %s ... ", type_name);
} else {
printf (" Loading %s ... ", type_name);
memmove_wd ((void *)load_start,
(void *)os_data, os_len, CHUNKSZ);
}
load_end = load_start + os_len;
puts("OK\n");
break;
case IH_COMP_GZIP://GZIP压缩格式,解压缩代码,但kernel本身有解压缩代码,所以用不到
printf (" Uncompressing %s ... ", type_name);
if (gunzip ((void *)load_start, unc_len,
(uchar *)os_data, &os_len) != 0) {
puts ("GUNZIP: uncompress or overwrite error "
"- must RESET board to recover\n");
show_boot_progress (-6);
do_reset (cmdtp, flag, argc, argv);
}
load_end = load_start + os_len;
break;
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2://BZIP2压缩格式
printf (" Uncompressing %s ... ", type_name);
/*
* If we've got less than 4 MB of malloc() space,
* use slower decompression algorithm which requires
* at most 2300 KB of memory.
*/
int i = BZ2_bzBuffToBuffDecompress ((char*)load_start,
&unc_len, (char *)os_data, os_len,
CFG_MALLOC_LEN < (4096 * 1024), 0);
if (i != BZ_OK) {
printf ("BUNZIP2: uncompress or overwrite error %d "
"- must RESET board to recover\n", i);
show_boot_progress (-6);
do_reset (cmdtp, flag, argc, argv);
}
load_end = load_start + unc_len;
break;
#endif /* CONFIG_BZIP2 */
default:
if (iflag)
enable_interrupts();
printf ("Unimplemented compression type %d\n", comp);
show_boot_progress (-7);
return 1;
}
puts ("OK\n");
debug (" kernel loaded at 0x%08lx, end = 0x%08lx\n", load_start, load_end);
show_boot_progress (7);
if ((load_start < image_end) && (load_end > image_start)) {
debug ("image_start = 0x%lX, image_end = 0x%lx\n", image_start, image_end);
debug ("load_start = 0x%lx, load_end = 0x%lx\n", load_start, load_end);
if (images.legacy_hdr_valid) {
if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)
puts ("WARNING: legacy format multi component "
"image overwritten\n");
} else {
puts ("ERROR: new format image overwritten - "
"must RESET the board to recover\n");
show_boot_progress (-113);
do_reset (cmdtp, flag, argc, argv);
}
}
show_boot_progress (8);
lmb_reserve(&lmb, load_start, (load_end - load_start));
#if defined(CONFIG_ZIMAGE_BOOT)
after_header_check:
os = hdr->ih_os;
#endif
//以上为启动镜像的格式及校验过程
switch (os) {//支持多种内核,我们选用的是linux
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv, &images);//存储了镜像的有效信息
break;
case IH_OS_NETBSD:
do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
break;
#ifdef CONFIG_LYNXKDI
case IH_OS_LYNXOS:
do_bootm_lynxkdi (cmdtp, flag, argc, argv, &images);
break;
#endif
case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv, &images);
break;
#if defined(CONFIG_CMD_ELF)
case IH_OS_VXWORKS:
do_bootm_vxworks (cmdtp, flag, argc, argv, &images);
break;
case IH_OS_QNX:
do_bootm_qnxelf (cmdtp, flag, argc, argv, &images);
break;
#endif
#ifdef CONFIG_ARTOS
case IH_OS_ARTOS:
do_bootm_artos (cmdtp, flag, argc, argv, &images);
break;
#endif
}
show_boot_progress (-9);
#ifdef DEBUG
puts ("\n## Control returned to monitor - resetting...\n");
do_reset (cmdtp, flag, argc, argv);
#endif
if (iflag)
enable_interrupts();
return 1;
}
注意:以下分析的内容均在上边这个程序中,整个uboot源码文件链接在《uboot源码分析(基于S5PV210)之启动第二阶段》提供。
(1)这个是一个定义的魔数,这个数等于0x016f2818,表示这个镜像是一个zImage。也就是说zImage格式的镜像中在头部的一个 固定位置 存放了这个数作为格式标记。如果我们拿到了一个image,去他的那个位置去取4字节判断它是否等于LINUX_ZIMvAGE_MAGIC,则可以知道这个镜像是不是一个zImage。
(2)int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
命令 bootm 0x30008000,所以do_boom的argc=2,argv[0]=bootm argv[1]=0x30008000。
但是实际bootm命令还可以不带参数执行。如果不带参数直接bootm,则会从CFG_LOAD_ADDR
地址去执行(定义在x210_sd.h中)。
(3)(cmd_bootm.c的209行)zImage头部开始的第37-40字节处 特定位置 存放着zImage标志魔数,从这个位置取出然后对比LINUX_ZIMAGE_MAGIC。可以用二进制阅读软件来打开zImage查看,就可以证明。很多软件都可以打开二进制文件,如winhex、UltraEditor、Notepad++安装一个插件也可以。
(1)这个数据结构是我们uboot启动内核使用的一个标准启动数据结构,zImage头信息也是一个image_header_t,但是在实际启动之前需要进行一些改造。
hdr->ih_os = IH_OS_LINUX;
hdr->ih_ep = ntohl(addr);这两句就是在进行改造。
(2)images全局变量是do_bootm函数中使用,用来完成启动过程的。zImage的校验过程其实就是先确认是不是zImage,确认后再修改zImage的头信息到合适,修改后用头信息去初始化images这个全局变量(cmd_bootm.c的216-221行),然后就完成了校验。
cmd_bootm.c的123行:
static bootm_headers_t images; /* pointers to os/initrd/fdt images */
(1)LEGACY(遗留的),在do_bootm函数中,这种方式指的就是uImage的方式。
(2)uImage方式是uboot本身发明的支持linux启动的镜像格式,但是后来这种方式被一种新的方式替代,这个新的方式就是设备树方式(在do_bootm方式中叫FIT)
(3)uImage的启动校验主要在boot_get_kernel函数中,主要任务就是校验uImage的头信息,并且得到真正的kernel的起始位置去启动。
(1)设备树方式启动暂时不讲,我的开发板并没有使用设备树进行启动,要学习设备树相关的知识,请阅读我的《设备树学习》专栏。
uboot本身设计时只支持uImage启动,原来uboot的代码也是这样写的。后来有了fdt方式之后,就把uImage方式命名为LEGACY方式,fdt方式命令为FIT方式,于是乎多了写#if #endif添加的代码。后来移植的人又为了省事添加了zImage启动的方式,又为了省事把zImage启动方式直接写在了uImage和fdt启动方式之前,于是乎又有了一对#if #endif。于是乎整天的代码看起来很恶心。
第二阶段校验头信息结束,下面进入第三阶段,第三阶段主要任务是启动linux内核,调用do_bootm_linux函数来完成。
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
bootm_headers_t *images)
{
ulong initrd_start, initrd_end;
ulong ep = 0;
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number;//机器码,写死的一个,优先级低于环境变量
void (*theKernel)(int zero, int arch, uint params);//函数指针
int ret;
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");//读取环境变量的值
#endif
/* find kernel entry point */
if (images->legacy_hdr_valid) {//在之前的函数设置过值,zimage与uimage都走这条路
ep = image_get_ep (&images->legacy_hdr_os_copy);
#if defined(CONFIG_FIT)//设备树的路
} else if (images->fit_uname_os) {
ret = fit_image_get_entry (images->fit_hdr_os,
images->fit_noffset_os, &ep);
if (ret) {
puts ("Can't get entry point property!\n");
goto error;
}
#endif
} else {
puts ("Could not find kernel entry point!\n");
goto error;
}
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);
}
ret = boot_get_ramdisk (argc, argv, images, IH_ARCH_ARM,
&initrd_start, &initrd_end);
if (ret)
goto error;
show_boot_progress (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
#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);//传递一个起始tag
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);//static struct tag *params;一个全局变量
#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
#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);//uboot的最后一句。启动内核,uboot就死掉了
/* does not return */
return;
error:
do_reset (cmdtp, flag, argc, argv);
return;
}
(1)函数在uboot/lib_arm/bootm.c中。
(2)SI(SourceInsight)工具找不到(是黑色的)不代表就没有,要搜索一下才能确定;搜索不到也不能代表就没有,因为我们在向SI工程中添加文件时,SI只会添加它能识别的文件格式的文件,有一些像Makefile、xx.conf等不识别的文件是没有被添加的。所以如果要搜索的关键字在makefile中或者脚本中,可能就是搜索不到的。(譬如TEXT_BASE)
(1)ep就是entrypoint的缩写,就是程序入口。一个镜像文件的起始执行部分不是在镜像的开头(镜像开头有n个字节的头信息),真正的镜像文件执行时第一句代码在镜像的中部某个字节处,相对于头是有一定的偏移量的。这个偏移量记录在头信息中。
(2)一般执行一个镜像都是:第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定镜像种类;第二步对镜像进行校验;第三步再次读取头信息,由特定地址知道这个镜像的各种信息(镜像长度、镜像种类、入口地址);第四步就去entrypoint处开始执行镜像。
(3)theKernel = (void (*)(int, int, uint))ep;将ep赋值给theKernel,则这个函数指针就指向了内存中加载的OS镜像的真正入口地址(就是操作系统的第一句执行的代码)。
(1)uboot在启动内核时,机器码要传给内核。uboot传给内核的机器码是怎么确定的?
第一顺序备选是环境变量machid,第二顺序备选是gd->bd->bi_arch_num(x210_sd.h中硬编码配置的)若无第一个,则使用第二个备选。环境变量是可以覆盖后者的,若想修改机器码,定义machid赋值即可。
(1)bootm.c从110行到144行就是uboot 对 准备给linux内核传递的参数 进行处理。
(2)Starting kernel … 这个是uboot中最后一句打印出来的东西。这句如果能出现,说明uboot整个是成功的,也成功的加载了内核镜像,也校验通过了,也找到入口地址了,也试图去执行了。如果这句后串口就没输出了,说明内核并没有被成功执行。原因一般是:传参(80%的原因)、内核在DDR中的加载地址·······
(1)struct tag(包含了一个struct tag_header和一个共用体),tag是一个数据结构,在uboot和linux kernel中都有定义tag数据结构,而且定义是一样的。
struct tag_header {
u32 size;//tag的大小
u32 tag;//什么样的tag,宏定义了一些魔数,如#define ATAG_CORE 0x54410001等
};
struct tag {
struct tag_header hdr;//其中记录了tag的类型,从而确定下面的共用体的选择
union { //不同种类的tag
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;
};
(2)tag_header和tag_xxx(结构体)。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理。
(3)tag_start与tag_end。kernel接收到的传参是若干个tag构成的,这些tag由tag_start起始,到tag_end结束。
(4)tag传参的方式是由linux kernel发明的,kernel定义了这种向我传参的方式,uboot只是实现了这种传参方式从而可以支持给kernel传参。
(1)CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存配置信息。
(2)CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs.
(3)CONFIG_INITRD_TAG
(4)CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。
(5)起始tag是ATAG_CORE、结束tag是ATAG_NONE,其他的ATAG_XXX都是有效信息tag。
思考:内核如何拿到这些tag?
uboot最终是调用theKernel函数来执行linux内核的,uboot调用这个函数(其实就是linux内核)时传递了3个参数。这3个参数就是uboot直接传递给linux内核的3个参数,通过寄存器来实现传参的。(第1个参数就放在r0中,第二个参数放在r1中,第3个参数放在r2中)第1个参数固定为0,第2个参数是机器码,第3个参数传递的就是大片传参tag的首地址。
(1)uboot移植时一般只需要配置相应的宏即可
(2)kernel启动不成功,注意传参是否成功。传参不成功首先看uboot中bootargs设置是否正确,其次看uboot是否开启了相应宏以支持传参。
1、启动4步骤
第一步:将内核搬移到DDR中(链接地址处 )
第二步:校验内核格式、CRC(一种校验方法)等
第三步:准备传参(以tag的形式准备好传的参数)
第四步:跳转执行内核(用函数指针进行远跳转)
2、涉及到的主要函数是:do_boom和do_bootm_linux
3、uboot能启动的内核格式:zImage uImage fdt方式(设备树)
4、跳转与函数指针的方式运行内核
注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来、引用了百度百科、部分他人博客的内容并结合自己实际开发经历,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。