先来引用一下这篇介绍“ARM Linux内核启动要求”的文章ARM Linux Kernel Boot Requirements,是ARM Linux内核的维护者Russell King写的。
引用:
* CPU register settings
o r0 = 0.
o r1 = machine type number.
o r2 = physical address of tagged list in system RAM.
* CPU mode
o All forms of interrupts must be disabled (IRQs and FIQs.)
o The CPU must be in SVC mode. (A special exception exists for Angel.)
* Caches, MMUs
o The MMU must be off.
o Instruction cache may be on or off.
o Data cache must be off and must not contain any stale data.
* Devices
o DMA to/from devices should be quiesced.
* The boot loader is expected to call the kernel image by jumping directly to the first instruction of the kernel image.
大致就是以上条件了,请特别关注一下第一条,这个基本上就是U-Boot的go命令和bootm命令之间的本质区别所在了。先来看看bootm命令的实现,在common/cmd_bootm.c的第119行开始有:
这里的预编译宏说明了,非 PPC体系结构的CPU的do_bootm_linux()函数都不是在这个文件内实现的(extern)。可想而知,这个函数的实现应该是和体系结构相关的,具体到arm体系结构的实现就是在lib_arm/armlinux.c这个文件当中。可以看到从lib_arm/armlinux.c中的第77 行开始就是do_bootm_linux()函数的实现。
其中第85行声明了这样一个函数指针theKernel:
看看它的名字和参数的命名我们也可以猜到这个其实就是内核的入口函数的指针了。几个参数的命名也说明了上文提到的ARM Linux内核启动要求的第一条,因为根据ACPS(ARM/Thumb Procedure Call Standard)的规定,这三个参数就是依次使用r0,r1和r2来传递的。
接下来第93行就是给这个函数指针赋值:
可以看到theKernel被赋值为hdr->ih_ep,这个hdr是指使用tools/mkimage工具程序制作uImage时加在linux.bin.gz前面的一个头部,而ih_ep结构体成员保存的就是使用mkimage时指定的-e参数的值,即内核的入口点(Entry Point)。知道了hdr->ih_ep的意义之后,给theKernel赋这个值也就是理所当然的了。
最后是对内核入口函数的调用,发生在第270行:
调用的时候对参数进行赋值,r0=0,r1=bd->bi_arch_number,r2=bd->bi_boot_params,一个都不少。至此U-Boot的使命完成,开始进入ARM Linux的美丽新世界。
====================================================================
要知道哪个地址是启动内核,哪个地址启动文件系统,要分析common/cmd_bootm.c中的函数do_bootm,因为引导kernel就是bootm这条命令的工作,do_bootm是命令bootm的执行函数。
现在我们来分析一下common/cmd_bootm.c中的函数do_bootm,这是bootm命令的处理函数。
读取uboot的环境变量verify,如果环境变量verify等于’n’,则局部变量verify赋值成为0;如果环境变量verify为空(即没有定义环境变量verify)或者环境变量verify不等于’n’,则局部变量verify赋值成为1。
如果参数个数小于2(即只是输入了bootm),使用缺省加载地址CFG_LOAD_ADDR;否则使用第二个参数作为加载地址。
将mkimage添加到映象文件头部的64字节提取到image_header_t 结构变量header中。
/* Copy header so we can blank CRC field for re-calculation */
定义了CONFIG_HAS_DATAFLASH,表示系统中存在ATMEL的数据Flash。
判断image header的magic是否匹配,如果不匹配,说明下载过程中发生了错误。
校验image header的CRC以及image data的CRC,如果校验不匹配,说明下载过程中发生了错误。
判断体系结构。
判断image类型。
判断image压缩类型
如果image header中指示的加载地址和bootm命令中参数2指定的地址不相同,则表示要从image header中指示的加载地址处把image data copy到bootm命令中参数2指定的地址处,然后再执行。
根据image 执行type来决定如何引导。
根据image 的OS type来决定如何引导
bootm命令是用来引导经过u-boot的工具mkimage打包后的kernel image的,什么叫做经过u-boot的工具mkimage打包后的kernel image,这个就要看mkimage的代码,看看它做了些什么,虽然我很希望大家不要偷懒,认真地去看看,但是我知道还是有很多人懒得去做这件,那么我就j将分析mkimage代码后得到的总结告诉大家,mkimage做了些什么,怎么用这个工具。
mkimage的用法
uboot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。
mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置, 入口点在内存的那个位置以及映象名是什么
引用:
root@Glym:/tftpboot# ./mkimage
Usage: ./mkimage -l image
-l ==> list image header information
./mkimage -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type'
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)
参数说明:
-A 指定CPU的体系结构:
取值 表示的体系结构
alpha Alpha
arm A RM
x86 Intel x86
ia64 IA64
mips MIPS
mips64 MIPS 64 Bit
ppc PowerPC
s390 IBM S390
sh SuperH
sparc SPARC
sparc64 SPARC 64 Bit
m68k MC68000
-O 指定操作系统类型,可以取以下值:
openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos
-T 指定映象类型,可以取以下值:
standalone、kernel、ramdisk、multi、firmware、script、filesystem
-C 指定映象压缩方式,可以取以下值:
none 不压缩
gzip 用gzip的压缩方式
bzip2 用bzip2的压缩方式
-a 指定映象在内存中的加载地址,映象下载到内存中时,要按照用mkimage制作映象时,这个参数所指定的地址值来下载
-e 指定映象运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头)
-n 指定映象名
-d 指定制作映象的源文件
引用:
* CPU register settings
o r0 = 0.
o r1 = machine type number.
o r2 = physical address of tagged list in system RAM.
* CPU mode
o All forms of interrupts must be disabled (IRQs and FIQs.)
o The CPU must be in SVC mode. (A special exception exists for Angel.)
* Caches, MMUs
o The MMU must be off.
o Instruction cache may be on or off.
o Data cache must be off and must not contain any stale data.
* Devices
o DMA to/from devices should be quiesced.
* The boot loader is expected to call the kernel image by jumping directly to the first instruction of the kernel image.
大致就是以上条件了,请特别关注一下第一条,这个基本上就是U-Boot的go命令和bootm命令之间的本质区别所在了。先来看看bootm命令的实现,在common/cmd_bootm.c的第119行开始有:
#ifdef CONFIG_PPC static boot_os_Fcn do_bootm_linux; #else extern boot_os_Fcn do_bootm_linux; #endif
这里的预编译宏说明了,非 PPC体系结构的CPU的do_bootm_linux()函数都不是在这个文件内实现的(extern)。可想而知,这个函数的实现应该是和体系结构相关的,具体到arm体系结构的实现就是在lib_arm/armlinux.c这个文件当中。可以看到从lib_arm/armlinux.c中的第77 行开始就是do_bootm_linux()函数的实现。
其中第85行声明了这样一个函数指针theKernel:
void (*theKernel)(int zero, int arch, uint params);
看看它的名字和参数的命名我们也可以猜到这个其实就是内核的入口函数的指针了。几个参数的命名也说明了上文提到的ARM Linux内核启动要求的第一条,因为根据ACPS(ARM/Thumb Procedure Call Standard)的规定,这三个参数就是依次使用r0,r1和r2来传递的。
接下来第93行就是给这个函数指针赋值:
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
可以看到theKernel被赋值为hdr->ih_ep,这个hdr是指使用tools/mkimage工具程序制作uImage时加在linux.bin.gz前面的一个头部,而ih_ep结构体成员保存的就是使用mkimage时指定的-e参数的值,即内核的入口点(Entry Point)。知道了hdr->ih_ep的意义之后,给theKernel赋这个值也就是理所当然的了。
最后是对内核入口函数的调用,发生在第270行:
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
调用的时候对参数进行赋值,r0=0,r1=bd->bi_arch_number,r2=bd->bi_boot_params,一个都不少。至此U-Boot的使命完成,开始进入ARM Linux的美丽新世界。
====================================================================
要知道哪个地址是启动内核,哪个地址启动文件系统,要分析common/cmd_bootm.c中的函数do_bootm,因为引导kernel就是bootm这条命令的工作,do_bootm是命令bootm的执行函数。
现在我们来分析一下common/cmd_bootm.c中的函数do_bootm,这是bootm命令的处理函数。
…… image_header_t header; ulong load_addr = CFG_LOAD_ADDR; /* Default Load Address */ int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { ulong iflag; ulong addr; ulong data, len, checksum; ulong *len_ptr; uint unc_len = 0x400000; int i, verify; char *name, *s; int (*appl)(int, char *[]); image_header_t *hdr = &header;
读取uboot的环境变量verify,如果环境变量verify等于’n’,则局部变量verify赋值成为0;如果环境变量verify为空(即没有定义环境变量verify)或者环境变量verify不等于’n’,则局部变量verify赋值成为1。
s = getenv ("verify"); verify = (s && (*s == 'n')) ? 0 : 1;
如果参数个数小于2(即只是输入了bootm),使用缺省加载地址CFG_LOAD_ADDR;否则使用第二个参数作为加载地址。
if (argc < 2) { addr = load_addr; } else { addr = simple_strtoul(argv[1], NULL, 16); } SHOW_BOOT_PROGRESS (1); printf ("## Booting image at %08lx ...\n", addr);
将mkimage添加到映象文件头部的64字节提取到image_header_t 结构变量header中。
/* Copy header so we can blank CRC field for re-calculation */
定义了CONFIG_HAS_DATAFLASH,表示系统中存在ATMEL的数据Flash。
#ifdef CONFIG_HAS_DATAFLASH if (addr_dataflash(addr)){ read_dataflash(addr, sizeof(image_header_t), (char *)&header); } else #endif memmove (&header, (char *)addr, sizeof(image_header_t));
判断image header的magic是否匹配,如果不匹配,说明下载过程中发生了错误。
if (ntohl(hdr->ih_magic) != IH_MAGIC) { #ifdef __I386__ /* correct image format not implemented yet - fake it */ if (fake_header(hdr, (void*)addr, -1) != NULL) { /* to compensate for the addition below */ addr -= sizeof(image_header_t); /* turnof verify, * fake_header() does not fake the data crc */ verify = 0; } else #endif /* __I386__ */ { puts ("Bad Magic Number\n"); SHOW_BOOT_PROGRESS (-1); return 1; } } SHOW_BOOT_PROGRESS (2);
校验image header的CRC以及image data的CRC,如果校验不匹配,说明下载过程中发生了错误。
data = (ulong)&header; len = sizeof(image_header_t); checksum = ntohl(hdr->ih_hcrc); hdr->ih_hcrc = 0; if (crc32 (0, (char *)data, len) != checksum) { puts ("Bad Header Checksum\n"); SHOW_BOOT_PROGRESS (-2); return 1; } SHOW_BOOT_PROGRESS (3); /* 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); #ifdef CONFIG_HAS_DATAFLASH if (addr_dataflash(addr)){ read_dataflash(data, len, (char *)CFG_LOAD_ADDR); data = CFG_LOAD_ADDR; } #endif if (verify) { puts (" Verifying Checksum ... "); if (crc32 (0, (char *)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; #if defined(__PPC__) if (hdr->ih_arch != IH_CPU_PPC) #elif defined(__ARM__) if (hdr->ih_arch != IH_CPU_ARM) #elif defined(__I386__) if (hdr->ih_arch != IH_CPU_I386) #elif defined(__mips__) if (hdr->ih_arch != IH_CPU_MIPS) #elif defined(__nios__) if (hdr->ih_arch != IH_CPU_NIOS) #elif defined(__M68K__) if (hdr->ih_arch != IH_CPU_M68K) #elif defined(__microblaze__) if (hdr->ih_arch != IH_CPU_MICROBLAZE) #else # error Unknown CPU type #endif { printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch); SHOW_BOOT_PROGRESS (-4); return 1; } SHOW_BOOT_PROGRESS (5);
判断image类型。
switch (hdr->ih_type) { case IH_TYPE_STANDALONE: name = "Standalone Application"; /* A second argument overwrites the load address */ if (argc > 2) { hdr->ih_load = simple_strtoul(argv[2], NULL, 16); } break; case IH_TYPE_KERNEL: name = "Kernel Image"; break; case IH_TYPE_MULTI: name = "Multi-File Image"; len = ntohl(len_ptr[0]); /* OS kernel is always the first image */ data += 8; /* kernel_len + terminator */ for (i=1; len_ptr; ++i) data += 4; break; default: printf ("Wrong Image Type for %s command\n", cmdtp->name); SHOW_BOOT_PROGRESS (-5); return 1; } SHOW_BOOT_PROGRESS (6); /* * 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(); #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
判断image压缩类型
switch (hdr->ih_comp) { case IH_COMP_NONE: 没有压缩 if(ntohl(hdr->ih_load) == addr) { 如果image header中指示的加载地址和bootm命令中参数2指定的地址相同,则表示不需要copy,可以就地执行。 printf (" XIP %s ... ", name); } else { #if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG) size_t l = len; void *to = (void *)ntohl(hdr->ih_load); void *from = (void *)data; printf (" Loading %s ... ", name); while (l > 0) { size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l; WATCHDOG_RESET(); memmove (to, from, tail); to += tail; from += tail; l -= tail; } #else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
如果image header中指示的加载地址和bootm命令中参数2指定的地址不相同,则表示要从image header中指示的加载地址处把image data copy到bootm命令中参数2指定的地址处,然后再执行。
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); #endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */ } break; case IH_COMP_GZIP: printf (" Uncompressing %s ... ", name); if (gunzip ((void *)ntohl(hdr->ih_load), unc_len, (uchar *)data, (int *)&len) != 0) { puts ("GUNZIP ERROR - must RESET board to recover\n"); SHOW_BOOT_PROGRESS (-6); do_reset (cmdtp, flag, argc, argv); } break; #ifdef CONFIG_BZIP2 case IH_COMP_BZIP2: printf (" Uncompressing %s ... ", name); /* * If we've got less than 4 MB of malloc() space, * use slower decompression algorithm which requires * at most 2300 KB of memory. */ i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load), &unc_len, (char *)data, len, CFG_MALLOC_LEN < (4096 * 1024), 0); if (i != BZ_OK) { printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i); SHOW_BOOT_PROGRESS (-6); udelay(100000); do_reset (cmdtp, flag, argc, argv); } break; #endif /* CONFIG_BZIP2 */ default: if (iflag) enable_interrupts(); printf ("Unimplemented compression type %d\n", hdr->ih_comp); SHOW_BOOT_PROGRESS (-7); return 1; } puts ("OK\n"); SHOW_BOOT_PROGRESS (7);
根据image 执行type来决定如何引导。
switch (hdr->ih_type) { case IH_TYPE_STANDALONE: if (iflag) enable_interrupts(); /* load (and uncompress), but don't start if "autostart" * is set to "no" */ if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) { char buf[32]; sprintf(buf, "%lX", len); setenv("filesize", buf); return 0; } appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep); (*appl)(argc-1, &argv[1]); return 0; case IH_TYPE_KERNEL: case IH_TYPE_MULTI: /* handled below */ break; 下面将有代码专门处理这两种image类型 default: if (iflag) enable_interrupts(); printf ("Can't boot image type %d\n", hdr->ih_type); SHOW_BOOT_PROGRESS (-8); return 1; } SHOW_BOOT_PROGRESS (8);
根据image 的OS type来决定如何引导
switch (hdr->ih_os) { 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, addr, len_ptr, verify); break; case IH_OS_NETBSD: do_bootm_netbsd (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; #ifdef CONFIG_LYNXKDI case IH_OS_LYNXOS: do_bootm_lynxkdi (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; #endif case IH_OS_RTEMS: do_bootm_rtems (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; #if (CONFIG_COMMANDS & CFG_CMD_ELF) case IH_OS_VXWORKS: do_bootm_vxworks (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; case IH_OS_QNX: do_bootm_qnxelf (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; #endif /* CFG_CMD_ELF */ #ifdef CONFIG_ARTOS case IH_OS_ARTOS: do_bootm_artos (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; #endif } SHOW_BOOT_PROGRESS (-9); #ifdef DEBUG puts ("\n## Control returned to monitor - resetting...\n"); do_reset (cmdtp, flag, argc, argv); #endif return 1; }
bootm命令是用来引导经过u-boot的工具mkimage打包后的kernel image的,什么叫做经过u-boot的工具mkimage打包后的kernel image,这个就要看mkimage的代码,看看它做了些什么,虽然我很希望大家不要偷懒,认真地去看看,但是我知道还是有很多人懒得去做这件,那么我就j将分析mkimage代码后得到的总结告诉大家,mkimage做了些什么,怎么用这个工具。
mkimage的用法
uboot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。
mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置, 入口点在内存的那个位置以及映象名是什么
引用:
root@Glym:/tftpboot# ./mkimage
Usage: ./mkimage -l image
-l ==> list image header information
./mkimage -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type'
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)
参数说明:
-A 指定CPU的体系结构:
取值 表示的体系结构
alpha Alpha
arm A RM
x86 Intel x86
ia64 IA64
mips MIPS
mips64 MIPS 64 Bit
ppc PowerPC
s390 IBM S390
sh SuperH
sparc SPARC
sparc64 SPARC 64 Bit
m68k MC68000
-O 指定操作系统类型,可以取以下值:
openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos
-T 指定映象类型,可以取以下值:
standalone、kernel、ramdisk、multi、firmware、script、filesystem
-C 指定映象压缩方式,可以取以下值:
none 不压缩
gzip 用gzip的压缩方式
bzip2 用bzip2的压缩方式
-a 指定映象在内存中的加载地址,映象下载到内存中时,要按照用mkimage制作映象时,这个参数所指定的地址值来下载
-e 指定映象运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头)
-n 指定映象名
-d 指定制作映象的源文件