阅读顶层目录下的readme:
该文件对Linux进行了简单的介绍,介绍了其硬件的无关性以及相关的说明文档。然后对Linux内核的安装、配置、编译以及内核的bug报告进行了介绍。
阅读源代码中documentation/i386/boot.txt:
该文件包含了i386体系结构中Linux内核的启动协议,内核启动代码的编写均依照此协议进行。文件对Linux内核启动时各部分代码在内存的分布情况进行了描述:
| |
0A0000 +------------------------+
| Reserved for BIOS | Do not use. Reserved for BIOS EBDA.
09A000 +------------------------+
| Stack/heap/cmdline | For use by the kernel real-mode code.
098000 +------------------------+
| Kernel setup | The kernel real-mode code.
090200 +------------------------+
| Kernel boot sector | The kernel legacy boot sector.
090000 +------------------------+
| Protected-mode kernel | The bulk of the kernel image.
010000 +------------------------+
| Boot loader | <- Boot sector entry point 0000:7C00
001000 +------------------------+
| Reserved for MBR/BIOS |
000800 +------------------------+
| Typically used by MBR |
000600 +------------------------+
| BIOS use only |
000000 +------------------------+
其中,0x000000~0x001000和0x9A000~0x0A0000用于保留给BIOS,0x001000~0x010000部分用于BIOS将boot loader程序装载到0000:7C00处,而该程序又将自己复制到0x090000处,并将kernel setup复制到其后的0x090200处。
阅读顶层目录下的Makefile,阅读i386的Makefile,阅读i386/boot的Makefile:
Makefile首先对一些命令进行了定义以方便后面使用。当我们使用make命令时,make程序将首先找到当前目录下的Makefile文件。根据Makefile文件的语法进行处理。根目录下的Makefile文件指明了内核编译的步骤:
000 all: do-it-all
001
002 #
003 # Make "config" the default target if there is no configuration file or
004 # "depend" the target if there is no top-level dependency information.
005 #
006
007 ifeq (.config,$(wildcard .config))
008 include .config
009 ifeq (.depend,$(wildcard .depend))
010 include .depend
011 do-it-all: Version vmlinux
012 else
013 CONFIGURATION = depend
014 do-it-all: depend
015 endif
016 else
017 CONFIGURATION = config
018 do-it-all: config
019 endif
007~008行表明首先对内核进行配置,009~010行表明其次对内核检查依赖关系,011行表明建立目标vmlinux,据此,我们在这个文件中查找vmlinux生成的依赖关系:
vmlinux: include/linux/version.h $(CONFIGURATION) init/main.o init/version.o linuxsubdirs
$(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o /
--start-group /
$(CORE_FILES) /
$(DRIVERS) /
$(NETWORKS) /
$(LIBS) /
--end-group /
-o vmlinux
$(NM) vmlinux | grep -v '/(compiled/)/|/(/.o$$/)/|/( [aUw] /)/|/(/./.ng$$/)/|/(LASH[RL]DI/)' | sort > System.map
由以上代码可知,vmlinux的生成依赖的文件是:i386/kernel/head.S+ init/main.c + init/version.o+ CORE_FILES + DRIVERS+ NETWORKS + LIBS
根据一层层的依赖关系,我们得知Linux最终内核镜像的生成规则依赖i386/boot的Makefile文件,通过对该文件的分析,我们可以得知,内核镜像的组成部分为:
Bootsect+setup+vmlinux
这样,我们就可以针对性地分析每个部分生成的依赖文件(这里我们只分析启动相关的部分):
Bootsect<-arch/i386/boot/bootsect.S
Setup<-arch/i386/boot/setup.S
Vmlinux<-arch/i386/boot/compressed/head.S+arch/i386/kernel/head.S+ init/main.c
在bootsect.S中:
首先宏定义了启动相关的内存的地址以及全局标识符,当PC机电源打开之后,80x86的CPU将自动进入实模式,并从地址0xffff0开始自动执行程序代码,这个地址通常是ROM-BIOS的地址。BIOS将进行系统的相关检测,并在物理地址0处初始化中断向量。此后,它将可启动设备的第一个扇区读入内存的绝对地址0x7C00处,并跳转到这个地方执行。该扇区的内容通常是bootloader程序(bootsect.S),该程序把自己复制到0x9000处,并把启动设备中后2KB字节代码(setup.S)读入到0x9200处,内核的其它部分(vmlinux)如果是低装载,则被读入到0x10000处,如果是高装载则被读入到0x100000处。
movw $BOOTSEG, %ax
movw %ax, %ds # %ds = BOOTSEG
movw $INITSEG, %ax
movw %ax, %es # %ax = %es = INITSEG
movw $256, %cx
subw %si, %si
subw %di, %di
cld
rep
movsw
ljmp $INITSEG, $go
该部分执行的便是把bootloader程序复制到0x9000处。后面的程序是进行堆栈的设置,并获取磁盘的参数,并显示“Loading…”。
movw $0x0001, %ax # set sread (sector-to-read) to 1 as
movw $sread, %si # the boot sector has already been read
movw %ax, (%si)
xorw %ax, %ax # reset FDC
xorb %dl, %dl
int $0x13
movw $0x0200, %bx # address = 512, in INITSEG
next_step:
movb setup_sects, %al
movw sectors, %cx
subw (%si), %cx # (%si) = sread
cmpb %cl, %al
jbe no_cyl_crossing
movw sectors, %ax
subw (%si), %ax # (%si) = sread
no_cyl_crossing:
call read_track
pushw %ax # save it
call set_next # set %bx properly; it uses %ax,%cx,%dx
popw %ax # restore
subb %al, setup_sects # rest - for next step
jnz next_step
该部分执行的便是把启动设备中后2KB字节代码(setup.S)读入到0x9200处。
pushw $SYSSEG
popw %es # %es = SYSSEG
call read_it
call kill_motor
call print_nl
该部分执行的便是把内核的其它部分(vmlinux)进行低装载,读入到0x10000处。
至此,bootsect.S便完成了它的使命,以后便跳转到setup处进行执行。下面我们开始分析setup.S文件。
在setup.S中:
Setup.S负责从BIOS中获取数据,并将数据放到系统内存适当的地方。这时,setup.s和system已经由bootsect加载到内存中。这段代码查询BIOS中有关内存、磁盘以及其它必要参数,并将它们拷贝到0x90000~0x901ff,然后在缓冲块覆盖之前交由system读取。
在进行了签名以及内核检测等必要的程序之后,该文件的程序进行内存检测、键盘、视频、磁盘控制器、IBM微通道总线MCA、PS/2设备(总线鼠标)、APM BIOS参数的配置。如果是zImage,则将其解压缩至0x10000处,此时将vmlinux移动到0x1000,如果是bzImage,则不动。设置临时IDT和临时GDT。设置中断向量,并由实模式进入到保护模式(bzImage情况下)。剩下的部分交由starup_32函数进行处理。
如果是bzImage,由于其没有进行压缩,所以它的starup_32函数在head.S中;如果是zImage,由于进行了压缩,head.S中的starup_32函数现在不可用,只有compressed/head.S的startup_32是可用的。在compressed/head.S中:初始化段寄存器和一个临时堆栈,初始化BSS段,解压缩(高装载或低装载->解压缩至0x100000(1MB)),跳转到0x100000处。至此,zImage与bzImage两种情况都到达相同的函数入口地址,剩下来两种情况都跳转到head.S中进行执行。
在head.S中:
加载各个数据段寄存器,初始化页表进入分页状态,建立进程0的内核堆栈,重新设置中断描述符表IDT,拷贝系统参数,识别处理器,然后重新设置全局描述符表GDT以及IDT,最后跳转到init/main.c中的start_kernel()函数。
至此,系统启动完成。