Linux 内核引导过程 学习
开机加电整体过程
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 /* original address of boot-sector */
SYSSEG = 0x1000 /* historical load address >> 4 */
#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 /* CONFIG_EFI_STUB */
.section ".bsdata", "a"
bugger_off_msg: # 打印的信息
.ascii "Use a boot loader.\r\n"
.ascii "\n"
.ascii "Remove disk and press any key to reboot...\r\n"
.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 /* Used to detect broken loaders */
.globl hdr
hdr:
setup_sects: .byte 0 /* Filled in by build.c 加载多少扇区作为启动*/
root_flags: .word ROOT_RDONLY
syssize: .long 0 /* Filled in by build.c 加载多少16字节作为保护模式的代码*/
ram_size: .word 0 /* Obsolete */
vid_mode: .word SVGA_MODE
root_dev: .word 0 /* Filled in by build.c */
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
SETUP
上面说到.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