linux启动过程浅析(2)

在本文的第一篇中,我主要对bootsect.s进行了讲述. 在第二部分中,我将对setup.s进行描述,我将其视为是Linux启动的第二步骤.
操作系统的启动过程是一个漫长而有序的过程,各个阶段都有其不同的作用. boot;setup;init虽然看似很接近,但是却是完全不同的过程.他们各司其职,按部就班.boot比较准确的翻译应该是引导,而setup的翻译则是设置或者建立.这听上去可能有点微妙,不要怪我咬文嚼字,请看官少安毋躁,等你看完了setup.s的过程后,你就会有深刻的体会了.

这是本文的第二部分,我将接续第一部分的内容,直接从bootsect运行完毕,跳转到setup.s开始.
在开始之前,让我们先看看现在计算机处在什么样的状态.下图是一个假想的内存空间,当bootsect运行完毕后,计算机的物理内存分配情况如图所示:
内核的system模块现在处于0x10000起始的内存段中,bootsect.s的二进制码处于0x90000起始的内存段中,而现在即将运行的setup.s则被load在0x90200的内存段中. 需要说明的是,下图的运行结果是在0.11版本的Linux运行完毕之后的结果,如2.6这样的高版本,system模块可能已经相当大了,如果放在0x10000位置上,从0x10000到0x90000的内存空间已经容不下它.在这种情况下,system模块会被load至0x100000起始的地址空间中,这就是所谓的"高地址".当前的程序计数器指向0x90200位置上,既从setup.s开始运行.
+--------+
||
| |
| |
| |<---------0x100000 "高地址"
| |
| |
| |
|---------|
| |
| setup|
| |
|---------|<----0x90200 (程序计数器位置)
|bootsect|
|----------|<----0x90000
||
| |
| |
| |
| |
|---------|
| |
| S |
| y |
| s|
| t |
| e|
| m |
| 模块 |
| |
| |
|--------|<----0x10000 "低地址"
| |
| |
| |
+--------+<----0x00000

现在,让我们来看看setup.s究竟做了些什么工作.一下分析还是基于0.11版本的Linux为主,古老的版本更能清晰的展现出操作系统的原理.而如果直接拿高版本进行分析,反而经常会陷入其他方面的纠缠.
由于接下来的代码都会显得比较长,不会象bootsect.s那样简短(否则也不能称之为操作系统了,^_^).我将把一个文件分段讲述:

首先可以看到的是:
INITSEG = 0x9000! we move boot here - out of the way
SYSSEG = 0x1000! system loaded at 0x10000 (65536).
SETUPSEG = 0x9020! this is the current segment
这些符号定义你应该非常熟悉了,只需要使他们与bootsect.s中定义的一致就可以了. 在2.6中,他们都引用于同一个宏定义,更加确定了他们的一致性.

然后使程序的起始.应该说,bootsect.s运行完毕并且跳转的目的,就是下面的start:
entry start
start:

! ok, the read went well so we get current cursor position and save it for
! posterity.

!!下面几行代码的意义使使用0x10中断请求(int 0x10),ah=0x03,读取当前光标位置.
!!并且把结果存放在0x90000处.
movax,#INITSEG! this is done in bootsect already, but...
movds,ax
movah,#0x03! read cursor pos
xorbh,bh
int0x10! save it in known place, con_init fetches
mov[0],dx! it from 0x90000.!!注意,在bootsect中,基地址已经设置为0x9000.
!!所以起始使写倒了0x90000处,覆盖了原先的bootsect.s程序.
!!使用同样的方法取得内存大小.
! Get memory size (extended mem, kB)

movah,#0x88
int0x15
mov[2],ax

!!取显卡信息,呵呵,现在是不是觉得很无聊了.
! Get video-card data:

movah,#0x0f
int0x10
mov[4],bx! bh = display page
mov[6],ax! al = video mode, ah = window width

!!...... 建议跳过此断,接下来的工作都是重复的,只为了取得机器参数.
!!写程序有时就是体力劳动.
! check for EGA/VGA and some config parameters

movah,#0x12
movbl,#0x10
int0x10
mov[8],ax
mov[10],bx
mov[12],cx

! Get hd0 data

movax,#0x0000
movds,ax
ldssi,[4*0x41]
movax,#INITSEG
moves,ax
movdi,#0x0080
movcx,#0x10
rep
movsb

! Get hd1 data

movax,#0x0000
movds,ax
ldssi,[4*0x46]
movax,#INITSEG
moves,ax
movdi,#0x0090
movcx,#0x10
rep
movsb

! Check that there IS a hd1 :-)

movax,#0x01500
movdl,#0x81
int0x13
jcno_disk1
cmpah,#3
jeis_disk1
no_disk1:
movax,#INITSEG
moves,ax
movdi,#0x0090
movcx,#0x10
movax,#0x00
rep
stosb
is_disk1:

! now we want to move to protected mode ...

cli! no interrupts allowed !

! first we move the system to it's rightful place

movax,#0x0000
cld! 'direction'=0, movs moves forward
这整整一段起始有些沉闷. 就是使用BIOS中断请求,读取机器的各种参数,并且放到目标地址上去.

取完了信息,现在开始更有趣的工作:
! now we want to move to protected mode ...

cli! no interrupts allowed ! !!把中断禁了!

! first we move the system to it's rightful place
!!开始把system整块地往低位迁移,移动倒0x0000地址起始处地内存区域.
!!就是把整个system模块向低位移动了0x10000的距离.
movax,#0x0000
cld! 'direction'=0, movs moves forward
do_move:
moves,ax! destination segment
addax,#0x1000
cmpax,#0x9000
jzend_move
movds,ax! source segment
subdi,di
subsi,si
mov cx,#0x8000
rep
movsw
jmpdo_move

! then we load the segment descriptors

!!移动完成,接下来的工作很关键.
!!我们在正准备进入保护模式,所以需要在进入之前先设置GDT和LDT.
!!这里的设置也是暂时性的,只为了进入保护模式而做,之后我们会重新初始化这两张表.
!!对于不了解保护模式的读者,建议先找些资料了解一下.
end_move:
movax,#SETUPSEG! right, forgot this at first. didn't work :-)
movds,ax
lidtidt_48! load idt with 0,0 !!设置IDT
lgdtgdt_48! load gdt with whatever appropriate !!设置GDT
其中idt的地址是0x00000000,可以参照setup.s后的符号定义:
idt_48:
.word0! idt limit=0
.word0,0! idt base=0L
而gdt_48的定义为:
gdt_48:
.word0x800! gdt limit=2048, 256 GDT entries
.word512+gdt,0x9! gdt base = 0X9xxxx
这里我们就可以看到lgdt的操作数了:
首先是GDT的长度定义:0x800
然后是GDT的起始地址,0x9是基地址,512+gdt是偏移量.其中512=0x0200,而基地址左移16位,0x90000+0x0200=0x90200,正好就是setup.s的起始地址.
再加上gdt的符号偏移,就得到了GDT的地址.
我们再看看gdt的符号定义处:
gdt:
.word0,0,0,0! dummy

.word0x07FF! 8Mb - limit=2047 (2048*4096=8Mb)
.word0x0000! base address=0
.word0x9A00! code read/exec
.word0x00C0! granularity=4096, 386

.word0x07FF! 8Mb - limit=2047 (2048*4096=8Mb)
.word0x0000! base address=0
.word0x9200! data read/write
.word0x00C0! granularity=4096, 386
这里其实定义了3个描述符,一个描述符长度位4字节,32bit.
第一行的.word 0,0,0,0表示第一个描述符全为0.事实上,我们不会使用它.
接着就定义了两个描述符,关于描述符的意义,读者可以参照相关书籍.现在,这两个描述符都指向了0x0000地址.

下面这段牵涉到了一个历史问题.
callempty_8042
moval,#0xD1! command write
out#0x64,al
callempty_8042
moval,#0xDF! A20 on
out#0x60,al
callempty_8042
打开A20信号线,这里是一个很令人恼火的"历史"问题,即使知道现在,2.6版本中仍然保留着类似的功能.现在还有哪台机器内存没有超过1M的么?或者还有哪段程序会用到A20关闭时候的循环寻址特性么?在下才疏学浅,可能的确有这样的关键应用还在,只是不得而知了.不管怎样,这段代码应该不会影响到读者对Linux操作系统的理解,这个特性也是仅仅针对x86体系结构而存在的,对于考古学没有兴趣的读者大可跳过不看.

最后,这里的代码是关键的关键了:
movax,#0x0001! protected mode (PE) bit
lmswax! This is it! !!将EFLAGS的标志位置掉,这样我们就进入了i386保护模式了.
jmpi0,8! jmp offset 0 of segment 8 (cs) !!由于进入了保护模式,跳转语句就有所不同了,这行代码意为跳转到0x0000处,从system的头开始执行.
这里就有点迷惑了,jmpi的第二个操作符8的意思是在GDT中的偏移量,是否还记得在上面讨论过的GDT的定义?一个描述符占用32bit,在jmpi操作中的长度为8,也就是说这句jmpi的意思是说选取第二个描述符,并且偏移量为0.第一个描述符是不用的,只有从第二个描述符开始才有用.

你可能感兴趣的:(linux,工作)