linux 启动

这篇文章的目的,在将 linuxkernel 的 boot 部份做一个介绍,因为笔者觉得很少有这样的文章介绍一个作业系统最最开始的一步 -- 把 kernel 本身载入至内存中,同时进行一些机器相关 (machinedependent) 的初始化工作,由于 linux 刚好使用的是大家最熟悉的 386 , 486 系列 PC ,所以在说明其程序流程时,也刚好可以对其相关的 PC 硬体架构做探讨,可以说是一举两得,不过,我必须假设读者对于组合语言及 PC 最基础的架构,如寄存器,分段,分页,中断服务等有大概的认识。
     读者可在 linuxsourcecode 的 /boot 子目录下找到几个以 .S 作为副档名的组合语言档,本文要说明的即是其中的 bootsect.S 及 setup.S 两个档案,及尽量简单的说明其所牵涉的相关硬体部份。
bootsect.S
  这个程序是 linuxkernel 的第一个程序,包括了 linux 自己的 bootstrap 程序,但是在说明这个程序前,必须先说明一般 IBMPC 开机时的动作 ( 此处的开机是指 " 打开 PC 的电源 "):
  一般 PC 在电源一开时,是由内存中地址 FFFF:0000 开始执行 ( 这个地址一定在 ROMBIOS 中, ROMBIOS 一般是在 FEOOOh 到 FFFFFh 中 ) ,而此处的内容则是一个 jump 指令, jump 到另一个位于 ROMBIOS 中的位置,开始执行一系列的动作,包括了检查 RAM , keyboard ,显示器,软硬磁盘等等,这些动作是由系统测试码 (systemtestcode) 来执行的,随着制作 BIOS 厂商的不同而会有些许差异,但都是大同小异,读者可自行观察自家机器开机时,萤幕上所显示的检查讯息。
  紧接着系统测试码之后,控制权会转移给 ROM 中的启动程序 (ROMbootstraproutine) ,这个程序会将磁盘上的零道零扇区读入内存中 ( 这就是一般所谓的 bootsector ,如果你曾接触过电脑病毒,就大概听过它的大名 ) ,至于被读到内存的哪里呢 ?-- 绝对位置 07C0:0000( 即 07C00h 处 ) ,这是 IBM 系列 PC 的特性。而位在 linux 开机磁盘的 bootsector 上的正是 linux 的 bootsect 程序,也就是说, bootsect 是第一个被读入内存中并执行的程序。现在,我们可以开始来看看到底 bootsect 做了什么。

第一步
  首先, bootsect 将它 " 自己 " 从被 ROMBIOS 载入的绝对地址 0x7C00 处搬到 0x90000 处,然后利用一个 jmpi(jumpindirectly) 的指令,跳到新位置的 jmpi 的下一行去执行,关键的 assemblycode 如下 :
.
( 搬移 bootsect 本身 )
.
.
jmpigo,INITSEC
go:
.
.
.
  表示将跳到 CS 为 0x9000 , IP 为 offset"go" 的位置 (CS:IP=0x9000:offsetgo) ,其中 INITSEC=0x9000 定义于程序开头的部份,而 go 这个 label 则恰好是下一行指令所在的位置。
第二步
  接着,将其它 segmentregisters 包括 DS , ES , SS 都指向 0x9000 这个位置,与 CS 看齐。另外将 SP 及 DX 指向一任意位移地址 (offset) ,这个地址等一下会用来存放磁盘参数表 (diskpara-metertable)
  提到磁盘参数表,就必须提到 BIOS 中断 1Eh 。先简单的介绍一下 BIOS 的中断服务 :80x86 将内存最低的 256*4byte 保留给 256 个中断向量 ( 每个 interruptvector 大小为 4byte ,所以一共有 256*4=1024byte) ,而其中的第 1Eh 个向量指向 " 磁盘参数表 " ,这个表会告诉电脑如何去读取磁盘机,而我们所要做的事是搬移磁盘参数表到刚才所设定的任意地址。
  接着,改变搬移来的参数表的参数,以符合我们的需要。再将中断向量 1Eh 指向我们所修改过的磁盘参数表,然后呼叫 BIOSinterrupt 的 int13h(function0 ,即 AH=0) 重置磁盘控制卡及磁盘驱动器,之后磁盘机就会照我们的意思动作了。如果你曾 trace 过 DOS 的 kernel ,你会发现,上述的动作在 DOS 中也有类似的对应流程。
现在让我们来看看关键的程序码 :.
.
.
push#0
popfs
movbx,#0x78
.
( 使 GS:SI=FS:BX ,指向磁盘参数表,
再将 GS:SI 所指地址的内容搬移 6 个
word 至 ES:DI 所指的地址 )
.
.
  此段程序是将 FS:BX 调整成 0000:0078 ,接着再将 GS:SI 的内容设成与 FS:BX 相同,此处 0x78h 即为 int1Eh 的起始位置 (7*16+8=120,(1*16+14)*4=120) 。调整 ES:DI 为刚才所设定的任意地址,从 GS:SI 搬移 6 个 word( 即 12byte) 到 ES:DI 所指的位置,显然磁盘参数表的长度就是 6 个 word , ( 不过事实上,磁盘参数表的确实长度是 11 个 byte) 。关于磁盘参数表,有兴趣的读者可自行参阅讲述 BIOSinterruptservices 的技术手册,会有详细的说明。
  读者可以用 debug 自行观察自家机器上 DOS 的磁盘参数表的起始位置 ( 即 int1Eh 的内容 ) 。以下是笔者机器的情形 ( 笔者使用的作业系统是 MSDOS6.2):
C:>debug
-d0000:0000
0000:00008A101601F4067000-1600CB04F4067000......p.......p.
0000:0010F40670000301790E-43EB00F0EBEA00F0..p...y.C.......
0000:002004108E340C118E34-5700CB046F00CB04...4...4W...o...
0000:00308700CB0408079433-B700CB04F4067000.......3......p.
0000:00400C01790E4DF800F0-41F800F0BA165F06..y.M...A....._.
0000:005039E700F01B01790E-70118E341201790E9.....y.p..4..y.
0000:006000E000F085175F06-6EFE00F0EE067000......_.n.....p.
0000:007053FF00F0A4F000F0-220500003E4600C0S......."...>F..
^^^^^^^^
由上图中可知,在 DOS 中磁盘参数表的起始位置 (int1Eh 的内容 ) 为 0000:0522 。接着观察 DOS 中位置 0000:0522 开始的 11 个 byte ,也就是磁盘参数表的内容
C:>debug
-d0000:0520l10
0000:05204D53DF022502121B-FF54F60F08000000MS..%....T......
^^^^^^^^^^^^^^^^^^^^^^
此 11byte 即为磁盘参数表的内容 ( 分别是 byte00h 到 0Ah)
  在程序中我们所更动的是第五个 byte(byte04h) ,改为 18h( 在上图例子中为 12h) ,这个 byte 的功能是定义磁轨上一个磁区的资料笔数。关键的程序码如下 :
.
movb4(di),*18
.
 
第叁步
  接着利用 BIOS 中断服务 int13h 的第 0 号功能,重置磁盘控制器,使得刚才的设定发挥功能。
.
.
xorah,ah
xordl,dl
int0x13
.
.
第四步
  完成重置磁盘控制器之后, bootsect 就从磁盘上读入紧邻着 bootsect 的 setup 程序,也就是以后将会介绍的 setup.S ,此读入动作是利用 BIOS 中断服务 int13h 的第 2 号功能。 setup 的 image 将会读入至程序所指定的内存绝对地址 0x90200 处,也就是在内存中紧邻着 bootsect 所在的位置。待 setup 的 image 读入内存后,利用 BIOS 中断服务 int13h 的第 8 号功能读取目前磁盘机的参数。
第五步
  再来,就要读入真正 linux 的 kernel 了,也就是你可以在 linux 的根目录下看到的 "vmlinuz" 。在读入前,将会先呼叫 BIOS 中断服务 int10h 的第 3 号功能,读取游标位置,之后再呼叫 BIOS 中断服务 int10h 的第 13h 号功能,在萤幕上输出字符串 "Loading" ,这个字符串在 bootlinux 时都会首先被看到,相信大家应该觉得很眼熟吧。
   linux 的 kernel 将会被读入至内存绝对地址 0x10000 处,键关的程序码如下 :
.
.
movax,#SYSSEG
moves,ax
callread_it
callkill_motor
.
.
  其中 SYSSEG 于程序开头时定义为 0x1000 ,先将 ES 内容设为 0x1000 ,接着在 read_it 这个子程序便以 ES 为目的地的节地址,将 kernel 读入内存中,至于 read_it 子程序的详细内容笔者并不想一一介绍,不过聪明的读者们应该已经猜到, read_it 一定又利用了 BIOSint13h 与磁盘有关的 I/O 中断服务了。
  至于 kill_motor 子程序,它的功能在于停止软盘机的马达 ( 各位聪明的读者会不会觉得这个子程序的名称取得颇为传神呢 ?) ,其程序码如下 :
.
.
kill_motor:
pushdx
movdx,#0x3f2
xoral,al
outb
popdx
ret
.
.
  首先利用 DX 指定要输出的 port ,而 03f2 这个 port 则是代表了软盘控制器 (floppydiskcontroller) 的所在,再利用 outb 将资料送出,而我们送出的资料,当然就是归零过的 AL 了。如此一来,软盘的马达就停止了。
第六步
  接下来做的事是检查 rootdevice ,之后就仿照一开始的方法,利用 indirectjump 跳至刚刚已读入的 setup 部份,程序码如下 :
.
.
jmpi0,SETYPSEG
  其中 SETUPSEG 已在先前定义为 0x9020 ,所以 CS:IP 会设定为 9020:0000 ,即跳到绝对地址为 0x90200 ,也就是 setup 的起点。而 bootsect 也大功告成了。
到此为止,内存的内容应该如下图所示 :
比较
  把大家所熟知的 MSDOS 与 linux 的开机部份做个粗浅的比较, MSDOS 由位于磁盘上 bootsector 的 boot 程序负责把 IO.SYS 载入内存中,而 IO.SYS 则负有把 DOS 的 kernel--MSDOS.SYS 载入内存的重责大任。而 linux 则是由位于 bootsector 的 bootsect 程序负责把 setup 及 linux 的 kernel 载入内存中,再将控制权交给 setup 。
  至于 setup.S ,就留到下一次再来讨论了。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值