一、常见linux镜像文件
启动到最后阶段后会将内核镜像加载到内存中,然后通过bootm,bootz等命令跳转到内核代码运行。内核镜像被加载到内存并获取到控制权限后,内核启动流程开始。嵌入式系统中内核镜像通常以压缩形式存储,目的是为了节省嵌入式系统的存储空间,并且这种压缩形式的镜像并不是一个可以执行的文件。所以内核阶段的首要工作是自解压内核镜像。
内核源码进行编译后生成vmlinux文件,通常对其进行压缩后得到uImage、zImage或者bzImage。
- vlinux:编译出来后未压缩最原始的内核文件,在PC的系统中一般用这个文件。
- zImage:vmlinux经过gzip压缩后的文件。并且在其头部包含了一个自解压的程序。
- uImage:它是uboot专用的映像文件,它是在zImage之前加上一个长度为64字节的“头”,说明这个内核的版本、加载位置、生成时间、大小等信息;其0x40之后与zImage没区别。
二、内核的自解压过程(kernel 4.1.15)
1.uboot的结束
uboot 2019.01版本中。通过下面调用关系跳转至内核。
do_bootm (cmd\bootm.c)
do_bootm_states (common\bootm.c)
boot_fn = bootm_os_get_boot_func(images->os.os); //boot_fn=do_bootm_linux
boot_fn //执行这个函数实际上是执行do_bootm_linux (arch\arm\lib\bootm.c)
boot_prep_linux (arch\arm\lib\bootm.c)
boot_jump_linux (arch\arm\lib\bootm.c)
kernel_entry(0, machid, r2); (kernel_entry = (void (*)(int, int, uint))images->ep;)将控制权交给内核, 启动内核
kernel_entry(0, machid, r2)形参r2如下:
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;
由上边可以看出进入内核前通用寄存器r0=0、r1=machid、r2=形参r2(如果有dts那么这个就是dts的地址否则就是bootargs)。
2.自解压程序汇编部分
内核的自解压代码位于内核源码的arch/arm/boot/compressed目录下,目录下的vmlinux.lds为自解压程序的链接文件,从中可以看到自解压程序的入口函数地址为“_start”
OUTPUT_ARCH(arm)
ENTRY(_start) /*指定自解压程序的入口地址*/
SECTIONS
{
/DISCARD/ : {
*(.ARM.exidx*)
*(.ARM.extab*)
/*
* Discard any r/w data - this produces a link error if we have any,
* which is required for PIC decompression. Local data generates
* GOTOFF relocations, which prevents it being relocated independently
* of the text/got segments.
*/
*(.data)
}
“_start”入口函数采用汇编代码实现,位于arch/arm/boot/compressed/head.S中。下面对改文件进行简要分析,了解大体流程即可
AR_CLASS( .arm )
start: @程序入口位置
.type start,#function
.rept 7
mov r0, r0
.endr
ARM( mov r0, r0 )
ARM( b 1f )
THUMB( adr r12, BSYM(1f) )
THUMB( bx r12 )
.word _magic_sig @ Magic numbers to help the loader
.word _magic_start @ absolute load/run zImage address
.word _magic_end @ zImage end address
.word 0x04030201 @ endianness flag
THUMB( .thumb )
1:
ARM_BE8( setend be ) @ go BE8 if compiled for BE8
AR_CLASS( mrs r9, cpsr )
#ifdef CONFIG_ARM_VIRT_EXT
bl __hyp_stub_install @ get into SVC mode, reversibly
#endif
/*通过uboot中kernel_entry的传参可以看出当前通用寄存器的r0=0,r1=machid,r2=启动参数的指针。
*将r1(machid)存放到r7中,将r2(启动参数指针)存放到了r8中。*/
mov r7, r1 @ save architecture ID
mov r8, r2 @ save atags pointer
#ifndef CONFIG_CPU_V7M
/*
* Booting from Angel - need to enter SVC mode and disable
* FIQs/IRQs (numeric definitions from angel arm.h source).
* We only do this if we were in user mode on entry.
*/
/*进入到SVC模式关闭FIQs/IRQs,因为在解压过程中不需要中断处理,也不希望被中断打断。*/
mrs r2, cpsr @ get current mode
tst r2, #3 @ not user?
bne not_angel
mov r0, #0x17 @ angel_SWIreason_EnterSVC
ARM( swi 0x123456 ) @ angel_SWI_ARM
THUMB( svc 0xab ) @ angel_SWI_THUMB
not_angel:
safe_svcmode_maskall r0
msr spsr_cxsf, r9 @ Save the CPU boot mode in
@ SPSR
#endif
....../*部分内容省略*/
#ifdef CONFIG_AUTO_ZRELADDR
/*
* Find the start of physical memory. As we are executing
* without the MMU on, we are in the physical address space.
* We just need to get rid of any offset by aligning the
* address.
*
* This alignment is a balance between the requirements of
* different platforms - we have chosen 128MB to allow
* platforms which align the start of their physical memory
* to 128MB to use this feature, while allowing the zImage
* to be placed within the first 128MB of memory on other
* platforms. Increasing the alignment means we place
* stricter alignment requirements on the start of physical
* memory, but relaxing it means that we break people who
* are already placing their zImage in (eg) the top 64MB
* of this range.
*/
/* 这里是计算真正的内核存放位置。计算过程是r4=pc,r4&=0xf8000000,r4+=TEXT_OFFSET。
*压缩的内核映射会被解压到这个地址。
*TEXT_OFFSET:内核便宜地址,即内核起始位置相对于内存起始位置的便宜,
*对于物理内存还是虚拟内存都是一样的结果。在/arch/arm/makefile中设定。*/
mov r4, pc
and r4, r4, #0xf8000000
/* Determine final kernel image address. */
add r4, r4, #TEXT_OFFSET
#else
/*zreladdr内核启动在RAM中的物理地址。压缩的内核映射被解压到这个地址,然后执行。
*这里是条件编译的,其作用和上面的类似。*/
ldr r4, =zreladdr
#endif
....../*部分内容省略*/
/*
* The C runtime environment should now be setup sufficiently.
* Set up some pointers, and start decompressing.
* r4 = kernel execution address
* r7 = architecture ID
* r8 = atags pointer
*/
/*下面跳转到decompress_kernel的C代码执行,实现解压过程,
*其中形参1是内核解压后的存放地址
*形参2是申请堆栈的首地址
*形参3是堆栈的结束地址
*形参4是machid。 */
mov r0, r4
mov r1, sp @ malloc space above stack
add r2, sp, #0x10000 @ 64k max
mov r3, r7
bl decompress_kernel
bl cache_clean_flush
bl cache_off
/*将R7和R8中保存的machid,启动参数的指针在回填到r1和r2中。
*目的是为跳转到真正的内核代码传参。*/
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
....../*这个中间省略掉一个条件编译,宏没被定义所以会跳过。*/
b __enter_kernel
....../*部分内容省略*/
__enter_kernel:
/*实现跳转到内核代码执行的汇编指令。*/
mov r0, #0 @ must be 0
ARM( mov pc, r4 ) @ call kernel
M_CLASS( add r4, r4, #1 ) @ enter in Thumb mode for M class
THUMB( bx r4 ) @ entry point is always ARM for A/R classes
....../*部分内容省略*/
到这里自解压的过程就已经完成,剩下的就是真正的内核被执行后,开始自己生命过程。
3.C语言实现的解压过程(decompress_kernel)
decompress_kernel调用关系如下:
decompress_kernel
do_decompress
__decompress
decompress_kernel (arch/arm/boot/compressed/misc.c)
/*由汇编代码可以看到下面传递参数的意义。
*output_start内核解压后的存放地址
*free_mem_ptr_p申请的堆栈地址(首地址)
*free_mem_ptr_end_p堆栈的结束地址
*arch_id是machid。 */
void
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
unsigned long free_mem_ptr_end_p,
int arch_id)
{
int ret;
__stack_chk_guard_setup();
output_data = (unsigned char *)output_start;
free_mem_ptr = free_mem_ptr_p;
free_mem_end_ptr = free_mem_ptr_end_p;
__machine_arch_type = arch_id;
arch_decomp_setup();
putstr("Uncompressing Linux...");
ret = do_decompress(input_data, input_data_end - input_data,
output_data, error);
if (ret)
error("decompressor returned an error");
else
putstr(" done, booting the kernel.\n");
}
do_decompress(arch/arm/boot/compressed/decompress.c)
#ifdef CONFIG_KERNEL_GZIP
#include "../../../../lib/decompress_inflate.c"
#endif
#ifdef CONFIG_KERNEL_LZO
#include "../../../../lib/decompress_unlzo.c"
#endif
#ifdef CONFIG_KERNEL_LZMA
#include "../../../../lib/decompress_unlzma.c"
#endif
#ifdef CONFIG_KERNEL_XZ
#define memmove memmove
#define memcpy memcpy
#include "../../../../lib/decompress_unxz.c"
#endif
#ifdef CONFIG_KERNEL_LZ4
#include "../../../../lib/decompress_unlz4.c"
#endif
int do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x))
{
return __decompress(input, len, NULL, NULL, output, 0, NULL, error);
}
若配置CONFIG_KERNEL_LZ4则__decompress(lib/decompress_unlzo.c)实现过程如下:
STATIC int INIT __decompress(unsigned char *buf, long len,
long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long),
unsigned char *out_buf, long olen,
long *pos,
void (*error)(char *x))
{
/*unlzo 可在decompress_unlzo.c中找到。*/
return unlzo(buf, len, fill, flush, out_buf, pos, error);
}
接下一篇:linux内核启动过程分析(二)