基于I386的Linux2.4.18启动过程分析

本文详细解析了Linux内核的启动过程,包括BIOS引导、bootloader加载、内核镜像组成及其加载过程等内容。重点介绍了bootsect.S与setup.S的作用及其实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 
阅读顶层目录下的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()函数。
 
至此,系统启动完成。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值