简介
本文选取linux 0.11版本,描述操作系统是如何加载起来的。读者可以参考《linux内核完全注释》这本书,作者赵炯。本文主要选取这一主题最重要的方面进行说明。
当开发linux 0.11版本时,linus torwards自己的pc是80386的cpu。
当PC的电源打开后,80x86结构的CPU将自动进入实模式,并从地址0xFFFF0开始自动执行程序代码,这个地址通常是ROM-BIOS中的地址。
PC机的BIOS将执行某些系统的检测,并在物理地址0处开始初始化中断向量。此后,它将可启动设备的第一个扇区(磁盘引导扇区,512
字节)读入内存绝对地址0x7C00处,并跳转到这个地方。启动设备通常是软驱或是硬盘。
注意:这里有一个可启动设备的概念。相信大多数同学安装过windows操作系统,在BIOS设置菜单中可以设置可启动设备的优先顺序,假定系统中只有2种可启动设备,一块硬盘和一块光盘。并且设置成了硬盘优先,那么会出现一种状况:无法安装新的操作系统,因为始终启动硬盘中已经存在的操作系统。 此时,就需要进入BIOS设置菜单中,调整可启动设备优先顺序,优先启动光盘,保存配置重启后。BIOS检测到光盘中有可启动系统,则执行光盘中的程序,然后我们就可以安装操作系统了。
启动
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
SETUPLEN = 4 ! nr of setup-sectors
BOOTSEG = 0x07c0 ! original address of boot-sector
INITSEG = 0x9000 ! we move boot here - out of the way
SETUPSEG = 0x9020 ! setup starts here
SYSSEG = 0x1000 ! system loaded at 0x10000 (65536).
ENDSEG = SYSSEG + SYSSIZE ! where to stop loading
! ROOT_DEV: 0x000 - same type of floppy as boot.
! 0x301 - first partition on first drive etc
ROOT_DEV = 0x306
上述代码使用了8086的汇编语言,当前CPU工作在实模式。本段代码声明了数据段,代码段,BSS段(未初始化数据段)当前是共享的。上述那些常量并不占用内存,只在实际使用的时候替换对应的汇编指令?
需要说明的是:BOOTSEG = 0x07c0这个地址即OS与BIOS之间的约定,BIOS将操作系统的第一行代码加载到这个位置。不同的CPU架构,这个代码位置是不同的。
entry start
start:
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep
movw
jmpi go,INITSEG
go: mov ax,cs
start开始即为操作系统的第1条可执行语句(mov ax,#BOOTSEG),其存放位置就是0x07c00。
这段代码的含义是将自身(512字节)拷贝到另外一个内存区(INITSEG)区域。即从内存(0x07c00–0x07e00)拷贝到(0x90000–0x90200)。然后跳转到0x90000对应的内存区去执行go标签对应的语句——即已经在另外一个内存区执行了指令mov ax, cs了。原来的内存区中只会执行前十行代码。
为什么需要将这512字节的代码和数据移动到另外一个地方,不移动行不行? 在后面的代码会看到,启动代码分成了3部分
- bootsect.s
- setup.s
- system(剩余的的内核代码打包而成)
bootsect.s主要负责将setup.s和system加载进内存。考虑到中断向量表在0地址附近,在setup.s中还需要使用,所以system先加载到0x10000以后。并且linus 为system预留了0x80000字节空间(在当时看,这个空间足够了)。
当setup.s使用完中断向量(利用中断获取一些硬件配置参数)以后,则把system从0x10000~0x8ffff移动到0x00000~0x7fffff。因此,setup.s必须处于0x90000以后,否则setup.s执行system移动过程中会修改代码自身(假定setup.s放在0x00000~0x10000内存区)。
而且setup.s获取到的参数也必须保存在0x90000以后。否则其会修订system代码或者被system代码修改。
结论:bootsect.s移动到0x90000不是必须的,但setup.s移动到0x90000之后是必须的,linus torwards先移动了bootsect.s,然后让setup.s(0x90200)位于bootsect.s之后,最后setup.s获取的参数覆盖bootsect.s(0x90000~0x90200)的内存区。到达了上述要求。
加载setup.s到0x90000之外是必须的,且setup.s的数据存储在0x90000之外也是必须的。