本文共 2996 字,大约阅读时间需要 9 分钟。
1.2.2 加载第二部分代码—setup(2)
此前的0x07C00这个位置是根据“两头约定”和“定位识别”而确定的。从现在起,操作系统已经不需要完全依赖BIOS,可以按照自己的意志把自己的代码安排在内存中的某个位置了。
点评
jmpi go, INITSEG go: mov ax, cs
这两行代码写得很巧。bootsect复制完成后,在内存的0x07C00和0x90000位置处有两段完全相同的代码。请大家注意,复制代码这件事本身也是要靠指令执行的,执行指令的过程就是CS和IP不断变化的过程。执行到jmpi go, INITSEG这行之前,代码的作用就是复制代码自身;执行了jmpi go, INITSEG之后,程序就转到执行0x90000这边的代码了。Linus的设计意图是跳转之后在新位置接着执行后面的mov ax, cs,而不是死循环。jmpi go, INITSEG与go: mov ax, cs配合,巧妙地实现了“到新位置后接着原来的执行序继续执行下去”的目的。
由于bootscet复制到了新的地方,并且要在新的地方继续执行。因为代码的整体位置发生了变化,所以代码中的各个段也会发生变化,前面已经改变了CS,现在对DS、ES、SS和SP进行调整。我们看看下面的代码:
//代码路径:boot/bootsect.s go: mov ax, cs mov ds, ax mov es, ax ! put stack at 0x9ff00. mov ss, ax mov sp, #0xFF00 ! arbitrary value >>512 ! load the setup-sectors directly after the bootblock. ! Note that 'es' is already set up.
上述代码的作用是通过ax,用CS的值0x9000来把数据段寄存器(DS)、附加段寄存器(ES)、栈基址寄存器(SS)设置成与代码段寄存器CS相同的位置,并将栈顶指针sp指向偏移地址为0xFF00处。图1-8对此做了非常直观的描述。
图1-8 调整各个段寄存器的值 |
下面着重介绍一下与栈操作相关的寄存器的设置。SS和SP联合使用构成了栈数据在内存中的位置值,对这两个寄存器的设置为后面程序的栈操作(如push和pop等)打下了基础。
现在可以观察一下bootsect中的程序,在执行设置SS和SP的代码之前,没有出现过栈操作指令,而在此之后就陆续使用了。这里对SS和SP进行的设置是分水岭。它标志着从现在开始,程序可以执行更为复杂一些的数据运算类指令了。
栈操作是有方向的,图1-8中标识了压栈的方向。注意,是由高地址到低地址的方向。
小贴士
DS/ES/FS/GS/SS:数据段寄存器,存在于CPU中,其中SS(Stack Segment)指向栈段,此区域将按栈机制进行管理。
SP(Stack Pointer):栈顶指针寄存器,指向栈段的当前栈顶。
注意,很多计算机书上使用“堆栈”这个词。本书用堆、栈表示两个概念。栈表示stack,特指在C语言程序的运行时结构中,以“后进先出”机制运作的内存空间;堆表示heap,特指用C语言库函数malloc创建、free释放的动态内存空间。
至此,bootsect的第一步操作:规划内存并把自身从0x07C00的位置复制到0x90000的位置的动作已经完成了。
3. 将Setup程序加载到内存中
下面,bootsect程序就要执行它的第二步操作了:将setup程序加载到内存中。
加载setup这个程序,要借助BIOS提供的int 0x 13h中断向量所指向的中断服务程序(也就是磁盘服务程序)来完成,图1-9标注了int 0x 13h中断向量的位置以及这个中断向量所指向的磁盘服务程序的入口位置。
图1-9 调用int 0x 13h中断 |
这个中断服务程序的执行过程与图1-3和图1-4中讲解过的int 0x 19h中断向量所指向的启动加载服务程序不同:
int 0x 19h中断向量所指向的启动加载服务程序是BIOS执行的,int 0x 13h的中断服务程序是Linux操作系统自身的启动代码bootsect执行的。
int 0x 19h的中断服务程序只负责把软盘的第一扇区的代码加载到0x07C00位置,int 0x 13h的中断服务程序则不然,它可以根据设计者的意图,把指定扇区的代码加载到内存的指定位置。
针对服务程序的这个特性,使用int 0x 13h中断时,就要事先将指定的扇区和加载的内存位置等信息传递给服务程序,即传参。
执行代码如下:
//代码路径:boot/bootsect.s load_setup: mov dx, #0x0000 ! drive 0, head 0 mov cx, #0x0002 ! sector 2, track 0 mov bx, #0x0200 ! address = 512, in INITSEG mov ax, #0x0200+SETUPLEN ! service 2, nr of sectors int 0x13 ! read it jnc ok_load_setup ! ok-continue mov dx, #0x0000 mov ax, #0x0000 ! reset the diskette int 0x13 j load_setup ok_load_setup:
从代码开始处的4个mov指令可以看出,系统给BIOS中断服务程序传参是通过几个通用寄存器实现的,这是汇编程序的常用方法,与C语言的函数调用形式有很大不同。
参数传递完毕后,执行int 0x13指令,产生0x13中断,通过中断向量表找到这个中断服务程序,将软盘从第2扇区开始的4个扇区,即setup.s对应的程序加载至内存的SETUPSEG (0x90200)处。根据图1-5的讲解,复制后的bootsect的起始位置是0x90000,占用512字节的内存空间,不难看出0x90200紧挨着bootsect的尾端,所以bootsect和setup是连在一起的。图1-10表示了软盘中所要加载的扇区位置和扇区数,以及载入内存的目标位置和占用的空间。
图1-10 加载setup程序 |
现在,操作系统已经从软盘中加载了5个扇区的代码。等bootsect执行完毕后,setup这个程序就要开始工作了。
注意,图1-8中SS:SP指向的位置为0x9FF00,这与setup程序的起始位置0x90200还有很大的距离,即便setup加载进来后,系统仍然有足够的内存空间用来执行数据压栈操作。而且,在启动部分,要压栈的数据毕竟也是有限的,大家在后续的章节中会逐渐体会到,设计者在此是进行过精密测算的。
转载地址:http://mkxbn.baihongyu.com/