前面兜了一大圈,罗嗦了许多,就是为了更好地理解arch/x86/boot/compressed/head_32.S是如何工作的。
__HEAD
宏定义,在linux/init.h中:
#define __HEAD .section ".head.text","ax"
ENTRY(startup_32)
#现在是32位代码模式了
cld
/*
* Test KEEP_SEGMENTS flag to see if the bootloader is asking
测试KEEP_SEGMENTS标志,看看是否bootloader要求不要重新初始化段寄存器
* us to not reload segments
我们的场景中,没有设置这个标志
*/
testb $(1<<6), BP_loadflags(%esi)
#在generated/asm-offsets.h中:
#define BP_loadflags 529 /* offsetof(struct boot_params, hdr.loadflags) # */
esi指向boot_params
jnz 1f
cli 关中断
movl $__BOOT_DS, %eax
movl %eax, %ds
movl %eax, %es
movl %eax, %fs
movl %eax, %gs
movl %eax, %ss
1:
/*
* Calculate the delta between where we were compiled to run
* at and where we were actually loaded at. This can only be done
计算编译地址和加载地址之间的偏移。
* with a short local call on x86. Nothing else will tell us what
只能使用近距call指令。
* address we are running at. The reserved chunk of the real-mode
* data at 0x1e4 (defined as a scratch field) are used as the stack
* for this calculation. Only 4 bytes are needed.
保留的scratch字段用于此计算
*/
leal (BP_scratch+4)(%esi), %esp #是esp指向boot_params->e820_e820_entries,现在中断已关(realmode_switch_hook和前面的cli),不需要担心
/*
__u32 scratch; /* Scratch field! */ /* 0x1e4 */
__u8 e820_entries; /* 0x1e8 */
*/
call 1f
1: popl %ebp #ebp中是1的线性地址,同时被保存到scratch中
subl $1b, %ebp #ebp减去1的编译时地址,即为加载偏移
/*
* %ebp contains the address we are loaded at by the boot loader and %ebx
%ebp包含加载地址1M,%ebx包含编译地址16M
* contains the address where we should move the kernel image temporarily
* for safe in-place decompression.
*/
#ifdef CONFIG_RELOCATABLE #一般都定义了此宏
movl %ebp, %ebx # 0x100000,1M
movl BP_kernel_alignment(%esi), %eax # 一般是16M
decl %eax # 0x1000000->0xFFFFFF
addl %eax, %ebx# 0x10FFFFF
notl %eax# 0xFF00000
andl %eax, %ebx#0x1000000
#else
movl $LOAD_PHYSICAL_ADDR, %ebx # 0x1000000 = 16M
#endif
/* Target address to relocate to for decompression 重定向到地址,便于解压*/
addl $z_extract_offset, %ebx
/* Set up the stack */
leal boot_stack_end(%ebx), %esp//建立栈
/* Zero EFLAGS */
pushl $0
popfl
/*
* Copy the compressed kernel to the end of our buffer
复制压缩过的内核到缓冲区尾部,这样解压操作就安全了
* where decompression in place becomes safe.
*/
pushl %esi
leal (_bss-4)(%ebp), %esi #减去4,只复制_bss和startup_32之间的数据,即_bss地址前面的数据,包括压缩内核
leal (_bss-4)(%ebx), %edi
movl $(_bss - startup_32), %ecx//长度
shrl $2, %ecx
std #反向复制
rep movsl
cld
popl %esi//还原esi
/*
* Jump to the relocated address.跳到重定位的地址
*/
leal relocated(%ebx), %eax
jmp *%eax
ENDPROC(startup_32)
.text
relocated:#现在是16M+$z_extract_offset+$relocated的地址了
/*
* Clear BSS (stack is currently empty)
初始化BSS
*/
xorl %eax, %eax
leal _bss(%ebx), %edi
leal _ebss(%ebx), %ecx
subl %edi, %ecx
shrl $2, %ecx
rep stosl
/*
* Adjust our own GOT
调整GOT
*/
leal _got(%ebx), %edx
leal _egot(%ebx), %ecx
1:
cmpl %ecx, %edx
jae 2f
addl %ebx, (%edx)#修正
addl $4, %edx
jmp 1b
2:
/*
* Do the decompression, and jump to the new kernel..
执行解压,然后跳到新内核
*/
leal z_extract_offset_negative(%ebx), %ebp #即16M物理地址
/* push arguments for decompress_kernel: */
pushl %ebp /* output address */
pushl $z_input_len /* input_len */
leal input_data(%ebx), %eax #输入数据
pushl %eax /* input_data */
leal boot_heap(%ebx), %eax #临时堆
pushl %eax /* heap area */
pushl %esi /* real mode pointer */
call decompress_kernel #解压
addl $20, %esp #平栈
decompress_kernel定义在compressed/misc.c中
asmlinkage void decompress_kernel(void *rmode, memptr heap,
unsigned char *input_data,
unsigned long input_len,
unsigned char *output)
{
real_mode = rmode;
if (cmdline_find_option_bool("quiet"))
quiet = 1;
if (cmdline_find_option_bool("debug"))
debug = 1;
if (real_mode->screen_info.orig_video_mode == 7) {
vidmem = (char *) 0xb0000;
vidport = 0x3b4;
} else {
vidmem = (char *) 0xb8000;
vidport = 0x3d4;
}
lines = real_mode->screen_info.orig_video_lines;
cols = real_mode->screen_info.orig_video_cols;
console_init();
if (debug)
putstr("early console in decompress_kernel\n");
free_mem_ptr = heap; /* Heap */
free_mem_end_ptr = heap + BOOT_HEAP_SIZE;
if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1))
error("Destination address inappropriately aligned");
#ifdef CONFIG_X86_64
if (heap > 0x3fffffffffffUL)
error("Destination address too large");
#else
if (heap > ((-__PAGE_OFFSET-(128<<20)-1) & 0x7fffffff))
error("Destination address too large");
#endif
#ifndef CONFIG_RELOCATABLE
if ((unsigned long)output != LOAD_PHYSICAL_ADDR)
error("Wrong destination address");
#endif
if (!quiet)
putstr("\nDecompressing Linux... ");
decompress(input_data, input_len, NULL, NULL, output, NULL, error);
parse_elf(output);
if (!quiet)
putstr("done.\nBooting the kernel.\n");
return;
}
主要是调用decompress执行解压,目前内核的压缩和解压方法支持很多种,包括bzip2,inflate,lzma,lzo,xz等,根据配置不同可选用不同算法。
解压成功后,调用parse_elf
static void parse_elf(void *output)
{
#ifdef CONFIG_X86_64
Elf64_Ehdr ehdr;
Elf64_Phdr *phdrs, *phdr;
#else
Elf32_Ehdr ehdr;
Elf32_Phdr *phdrs, *phdr;
#endif
void *dest;
int i;
memcpy(&ehdr, output, sizeof(ehdr));
if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
ehdr.e_ident[EI_MAG2] != ELFMAG2 ||
ehdr.e_ident[EI_MAG3] != ELFMAG3) {
error("Kernel is not a valid ELF file");
return;
}//检查是否为ELF幻数
if (!quiet)
putstr("Parsing ELF... ");
phdrs = malloc(sizeof(*phdrs) * ehdr.e_phnum);//准备复制程序头表
if (!phdrs)
error("Failed to allocate space for phdrs");
memcpy(phdrs, output + ehdr.e_phoff, sizeof(*phdrs) * ehdr.e_phnum);
for (i = 0; i < ehdr.e_phnum; i++) {//遍历程序头
phdr = &phdrs[i];
switch (phdr->p_type) {
case PT_LOAD:
#ifdef CONFIG_RELOCATABLE
dest = output;//前面是ELF头和程序头,将代码移到16M处
dest += (phdr->p_paddr - LOAD_PHYSICAL_ADDR);//0x01000000-16M=0
#else
dest = (void *)(phdr->p_paddr);
#endif
memcpy(dest,
output + phdr->p_offset,
phdr->p_filesz);//移动代码和数据到正确的位置
break;
default: /* Ignore other PT_* */ break;
}
}
}
到这里,内核代码和数据已就位
#if CONFIG_RELOCATABLE
/*
* Find the address of the relocations.
*/
leal z_output_len(%ebp), %edi
%ebp值是16M,z_output_len值是内核大小(包括vmlinux.relocs),%edi是内核尾部地址,实际上是vmlinux.relocs的结束地址
/*
* Calculate the delta between where vmlinux was compiled to run
* and where it was actually loaded.
计算编译地址和加载地址的偏移
*/
movl %ebp, %ebx
subl $LOAD_PHYSICAL_ADDR, %ebx//计算差值
jz 2f /* Nothing to be done if loaded at compiled addr. 地址未变动*/
/*
* Process relocations.处理重定位
*/
1: subl $4, %edi//地址由高往低
movl (%edi), %ecx//取重定位地址
testl %ecx, %ecx//主要,前面生成vmlinux.relocs,最前面加入一个双字0,作为重定位结束标志
jz 2f//已结束重定位
addl %ebx, -__PAGE_OFFSET(%ebx, %ecx)//%ecx的值是内核线性地址,从PAGE_OFFSET(一般3G)开始,所有要减去PAGE_OFFSET,成为物理地址,加上加载偏移,得到真实的重定位位置,将该处值再加上加载偏移,进行修正
jmp 1b//循环
2:
#endif
/*
* Jump to the decompressed kernel.
*/
xorl %ebx, %ebx
jmp *%ebp//跳到16M处,即arch/x86/kernel/head_32.S的startup_32处
/*
* Stack and heap for uncompression
*/
.bss
.balign 4
boot_heap:
.fill BOOT_HEAP_SIZE, 1, 0 ##define BOOT_HEAP_SIZE 0x8000
boot_stack:
.fill BOOT_STACK_SIZE, 1, 0##define BOOT_STACK_SIZE 0x1000
boot_stack_end: