开机加电整体过程
BIOS
BIOS的启动由硬件完成,CPU的硬件逻辑设计为加电瞬间强行将CS的值射中为0xF000,IP设置为0xFFF0。这样CS:IP就指向0xFFFF0,作为BIOS的程序入口地址。
加载bootsect完成BIOS启动后,CPU会接收到 int 0x19 的中断向量,会加载相应的服务程序,将0号磁头对应软盘的0磁道1扇区MBR(Master Boot Record)内容复制到内存 0x7C00处
- header.S 源码解读
BOOTSEG = 0x07C0 SYSSEG = 0x1000 #ifndef SVGA_MODE # VGA 视频模式 #define SVGA_MODE ASK_VGA #endif #ifndef ROOT_RDonLY #define ROOT_RDonLY 1 #endif .code16 .section ".bstext", "ax" .global bootsect_start bootsect_start: # boostsect 启动 #ifdef CONFIG_EFI_STUB # 适配的pecoff格式,UEFI启动模式(区别于BOOT) # "MZ", MS-DOS header .byte 0x4d .byte 0x5a #endif # Normalize the start address ljmp $BOOTSEG, $start2 # 控制台跳转到 0x07C0 + S(offset) 处 也就是start2 start2: movw %cs, %ax # cs目前是0x07C0 movw %ax, %ds # 初始化 指令(CS)、数据(DS)、堆栈(SS)、其他(ES)寄存器 movw %ax, %es movw %ax, %ss xorw %sp, %sp sti # 使能中断 cld # 清方向标志位 movw $bugger_off_msg, %si # 将 si 指向 打印信息(bugger_off_msg) msg_loop: # 打印信息 lodsb # 将Si指向的内存读入累加器 andb %al, %al jz bs_die # 如果al 为0 则跳转 bs_die movb $0xe, %ah movw $7, %bx int $0x10 # 0x10是BIOS视频中断,用于打印字符 jmp msg_loop bs_die: # Allow the user to press a key, then reboot xorw %ax, %ax int $0x16 # 键盘中断,接收字符 int $0x19 # int 0x19 should never return. In case it does anyway, # invoke the BIOS reset code... ljmp $0xf000,$0xfff0 # 重设BIOS #ifdef CONFIG_EFI_STUB .org 0x3c # # Offset to the PE header. # .long pe_header #endif .section ".bsdata", "a" bugger_off_msg: # 打印的信息 .ascii "Use a boot loader.rn" .ascii "n" .ascii "Remove disk and press any key to reboot...rn" .byte 0 ... # 这里省略了针对UFI模式的启动适配 # Kernel attributes; used by setup. This is part 1 of the # header, from the old boot sector. .section ".header", "a" .globl sentinel sentinel: .byte 0xff, 0xff .globl hdr hdr: setup_sects: .byte 0 root_flags: .word ROOT_RDonLY syssize: .long 0 ram_size: .word 0 vid_mode: .word SVGA_MODE root_dev: .word 0 boot_flag: .word 0xAA55 # 检查启动代码是否合法 # offset 512, entry point # 第一扇区的字节数正好是512字节
上述字节数没有达到512B, 再看一下 setup.ld的源码:
. = 0;
.bstext : { *(.bstext) }
.bsdata: { *(.bsdata) }
. = 495; # 495 + 17 = 512
.header : { *(.header) }
.entrytext : { *(.entrytext) }
.inittext : { *(.inittext) }
.initdata: { *(.initdata) }
__end_init = .;
PS: 上述过程是软盘时代的引导过程,Linux为了兼容软盘加载所存在的代码,正好是512字节,放到第一个扇区内,最新的Linux 内核已经基本放弃了软盘启动, 已经有GRUB来管理系统的引导启动
- LILO 和 GRUB
LILO: LInux LOader
GRUB: GRandUnified Bootloader
//to do
上面说到.header偏移了495, 而512处正好是_start, 也就是入口,但是这里是存放 hdr的数据结构信息,那么需要保证不破坏数据结构.
_start: # Explicitly enter this as bytes, or the assembler # tries to generate a 3-byte jump here, which causes # everything else to push off to the wrong offset. .byte 0xeb # short (2-byte) jump .byte start_of_setup-1f
可以看见 这里其实是一个Jump
.hdr中会设置默认的大内核地址,
setup_move_size: .word 0x8000 # size to move, when setup is not # loaded at 0x90000. We will move setup # to 0x90000 then just before jumping # into the kernel. However, only the # loader knows how much data behind # us also needs to be loaded. code32_start: # here loaders can put a different # start address for 32-bit code. .long 0x100000 # 0x100000 = default for big kernel
当 with boot protocol version >= 2.02 ,内存布局如下:
~ ~ | Protected-mode kernel | 100000 +------------------------+ | I/O memory hole | 0A0000 +------------------------+ | Reserved for BIOS | Leave as much as possible unused ~ ~ | Command line | (Can also be below the X+10000 mark) X+10000 +------------------------+ | Stack/heap | For use by the kernel real-mode code. X+08000 +------------------------+ | Kernel setup | The kernel real-mode code. | Kernel boot sector | The kernel legacy boot sector. X +------------------------+ | Boot loader | <- Boot sector entry point 0000:7C00 001000 +------------------------+ | Reserved for MBR/BIOS | 000800 +------------------------+ | Typically used by MBR | 000600 +------------------------+ | BIOS use only | 000000 +------------------------+
接下来就是需要为调用mian做准备,设置堆栈等:
.section ".entrytext", "ax" start_of_setup: # Force %es = %ds movw %ds, %ax movw %ax, %es cld ... # 设置堆栈 # Check signature at end of setup cmpl $0x5a5aaa55, setup_sig jne setup_bad
setup.ld 内会把 setup_sig 设置:
.signature : {
setup_sig = .;
LONG(0x5a5aaa55)
}
然后清空 bss,跳转main
# Zero the bss movw $__bss_start, %di movw $_end+3, %cx xorl %eax, %eax subw %di, %cx shrw $2, %cx rep; stosl # Jump to C code (should not return) calll main



