1. 引导内核: 首先要在第一个扇区编写小于512字节的启动代码,或者使用现存的bootloader,如: LILO, GRUB等。
2. 加载内核: 将内核放在物理内存的某个物理地址上。
3. 执行内核: 指令指针转移到内核中与平台相关(如: x86, ARM)的机器码的位置,并初始化基本的功能,平台相关的代码大多用汇编编写。随后转移到平台无关的代码,完成系统的初始化,最后切换到正常的运转模式(如: IA-32下一般需要激活分页功能,并且打开保护模式的标志位跳转到保护模式)。
首先看第一阶段linux内核的实现过程(以下将linux2.6.11内核统称为linux内核,各版本代码不一致):
看一下当前根目录下的Makefile,有以下语句:
点击(此处)折叠或打开
- include $(srctree)/arch/$(ARCH)/Makefile
$(srctree)指的就是当前的根目录,ARCH可以由我们在编译内核的时候指定,如果不指定默认为当前系统的体系结构:
点击(此处)折叠或打开
- SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
- -e s/arm.*/arm/ -e s/sa110/arm/ \
- -e s/s390x/s390/ -e s/parisc64/parisc/ )
为了方便默认为i386,具体数值可以把命令Copy一下自己在shell执行一下。
来看一下bootsect.S,该代码是较早的linux版本的引导代码。和基本的引导代码一样,首先跳转到0x7c00,随后执行代码,添加0xaa55的标记,最后让代码填充到512字节。
但是在目前的版本抛弃了该执行过程,看代码(arch/$(ARCH)/boot/bootsect.S):
点击(此处)折叠或打开
- movw $bugger_off_msg, %si
- msg_loop:
- lodsb
- andb %al, %al
- jz die
- movb $0xe, %ah
- movw $7, %bx
- int $0x10
- jmp msg_loop
- 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
- bugger_off_msg:
- .ascii "Direct booting from floppy is no longer supported.\r\n"
- .ascii "Please use a boot loader program instead.\r\n"
- .ascii "\n"
- .ascii "Remove disk and press any key to reboot . . .\r\n"
- .byte 0
很明显Linux内核使用bootloader启动。
继续分析代码文件(arch/$(ARCH)/boot/setup.S[使用LILO的启动代码文件]):
点击(此处)折叠或打开
- /* Signature words to ensure LILO loaded us right */
- #define SIG1 0xAA55
- #define SIG2 0x5A5A
- INITSEG = DEF_INITSEG # 0x9000, we move boot here, out of the way
- SYSSEG = DEF_SYSSEG # 0x1000, system loaded at 0x10000 (65536).
- SETUPSEG = DEF_SETUPSEG # 0x9020, this is the current segment
- # ... and the former contents of CS
其他的代码就是一些启动代码和从磁盘读取内核的代码,例如: 使能a20地址线,读硬盘,加载GDT,LDT,开启分页或者PAE等功能然后跳转到保护模式进入32位代码等等。
本文不叙述(太多了!), 其他文章进行详细分析0-0。
略过其他代码,但还是要看一段:
点击(此处)折叠或打开
- .byte 0x66, 0xea # prefix + jmpi-opcode
- code32: .long 0x1000 # will be set to 0x100000
- # for big kernels
jmpi 0x1000或者0x100000, __BOOT_CS,code32标签的地址定义为如下代码:
点击(此处)折叠或打开
- code32_start:
- # start address for 32-bit code.
- #ifndef __BIG_KERNEL__
- .long 0x1000 # 0x1000 = default for zImage
- #else
- .long 0x100000 # 0x100000 = default for big kernel
- #endif
跳转到startup_32之后做一些初始化工作: 清理BSS等。随后解压内核调用decompress_kernel函数(boot/compress/misc.c),并且跳转到解压后的内核代码位置(arch/$(ARCH)/kernel/head.S)该代码做一些最后的初始化功能:
1. 激活分页
2. 清理bss(用0字节填充__bss_start和_bss_end之间的内容)
3. 初始化中断描述符表
4. 检测处理器类型
最后调用start_kernel函数。内核就真正的开始启动了。
EMAIL回复会比较及时
深入Linux内核架构(中文版) Wolfgang Manuerer著 郭旭译