这里先直接给出答案:中断
此时,操作系统用短短几行代码,将数据段寄存器ds和代码段寄存器cs设置为了0x9000,方便之后的程序访问代码和数据,并且将栈顶地ss:sp设置在了远离代码的位置0x9000足够遥远的0x9FF00,保证了栈向下发展时不会轻易覆盖掉操作系统的代码。
简单来说,就是设置了如何访问数据段、如何访问代码段以及如何访问栈段,也就是做了一次初步的内存规划。因为从CPU的角度出发,访问内存就是这么三个地方而已。
做好这些基础工作之后,我们现在仅仅是把硬盘中最开始的512字节加载到内存中了,但操作系统还有很多代码仍然在硬盘中的其他扇区里,不能抛下它们不管啊🤣
把剩下的操作系统的代码从硬盘请到内存中来
所以下一步,自然是把仍然在硬盘中的操作系统代码请到内存中来。
接着往下看代码:
load_setup:
mov dx,#0x0000 ; drive 0, head 0
mov cx,#0x0002 ; sector 2, track 0
mov bx,#0x0200 ; address = 512, in 0x9000
mov ax,#0x0200+4 ; 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
jmp load_setup
ok_load_setup:
...
这里有两个我们值得关注的点int指令。
这个int指令在汇编中指的是表示中断指令,不是C语言里的int类型🤣,int 0x13表示发起0x13中断,这条指令的各种mov指令都是用来给dx,cx,bx,ax赋值,这四个寄存器都是作为这个中断程序的参数,这叫通过寄存器来传参。与之相对应的是另一种传参方式是通过栈传递,在C语言中应用很广。
中断可以简单理解为一种通知机制,用来打断当前CPU正在执行的指令,转而去执行相应的中断处理程序的过程。更详细的原理和分析,可以自行Google。
发起中断后,CPU就会通过这个中断号0x13,去寻找对应的中断处理程序的入口地址,并跳转过去执行,逻辑上就相当于执行了一个函数。
而0x13号中断的处理程序,是BIOS提前给我们写好的,具体可以去Google BIOS的中断,0x13表示的是读取磁盘的中断函数,之后真正进入操作系统内核之后,中断处理程序还要我们自己编写,目前先用的是BIOS的中断函数。
可见即便是操作系统的源码,有时也需要去调用现成的函数方便自己工作,并不是造轮子的人就非得完全从头开始造。
Linux在用这个0x13中断干了什么呢?我们直接看代码:
load_setup:
mov dx,#0x0000 ; drive 0, head 0
mov cx,#0x0002 ; sector 2, track 0
mov bx,#0x0200 ; address = 512, in 0x9000
mov ax,#0x0200+4 ; service 2, nr of sectors
int 0x13 ; read it
...
这里的代码注释已经很清楚了, 直接说它的作用——从硬盘的第二个扇区开始,把数据加载到内存0x90200处,共加载4个扇区。图示就是这样的:
load_setup:
...
jnc ok_load_setup ; ok - continue
...
jmp load_setup
ok_load_setup:
...
再往后的jnc和jmp,表示成功和失败分别跳转到哪个标签处,相当于我们高级语言中的if-else。
可以看到,如果复制成功,就跳转到 ok_load_setup 这个标签;如果失败,则会不断重复执行这段代码,也就是重试。因此我们先不需要关心重试逻辑了,直接看成功后跳转的 ok_load_setup 这个标签后的代码:
ok_load_setup:
...
mov ax,#0x1000
mov es,ax ; segment of 0x10000
call read_it
...
jmpi 0,0x9020
剩下的核心代码就都写在这里了,就这么几行,其作用是把从硬盘第 6 个扇区开始往后的 240 个扇区,加载到内存 0x10000 处,和之前的从硬盘复制到内存是一个道理。
至此,整个操作系统的全部代码,就已经全部从硬盘加载到内存中了。然后这些代码,又通过一个熟悉的段间跳转指令 jmpi 0,0x9020,跳转到 0x90200 处,就是硬盘第二个扇区开始处的内容。
那这里的内容是什么呢?就是我们要阅读的第二个操作系统源代码文件 setup.s。
聊聊操作系统的编译过程不过先不急,进入第二个源代码文件之前,我们先简单复盘一下整个操作系统的编译过程。整个编译过程,就是通过 Makefile 和 build.c 配合完成的,最终达到这样一个效果:1. 把 bootsect.s 编译成 bootsect 放在硬盘的 1 扇区;
2. 把 setup.s 编译成 setup 放在硬盘的 2~5 扇区;
3. 把剩下的全部代码(head.s 作为开头,与各种 .c 和其他 .s 等文件一起)编译并链接成 system,放在硬盘的随后 240 个扇区。所以整个路径如下图所示:
总结
- bootsect.s文件就是把操作系统的代码挪来挪去,最后形成上面的内存结构i,然后做了一次比较重要的内存规划,方便后续代码的执行时,数据段和代码段以及栈段的寻址方式。