1.3 开始向32位模式转变,为main函数的调用做准备
接下来操作系统要使计算机在32位保护模式下工作。这期间要做大量的重建工作,并且持续工作到操作系统的main函数的执行过程中。在本节中,操作系统执行的操作包括打开32位的寻址空间、打开保护模式、建立保护模式下的中断响应机制等与保护模式配套的相关工作、建立内存的分页机制,以及做好调用main函数的准备。
1.3.1 关中断并将system移动到内存地址起始位置0x00000
图1-16 关中断 |
如图1-16所示,这个准备工作先要关闭中断,即将CPU的标志寄存器(EFLAGS)中的中断允许标志(IF)置0。这意味着,在接下来的执行过程中,无论是否发生中断,系统都不再响应此中断,直到下一章要讲解的main函数中能够适应保护模式的中断服务体系被重建完毕才会打开中断,而那时候响应中断的服务程序将不再是BIOS提供的中断服务程序,取而代之的是由系统自身提供的中断服务程序。
代码为:
- //代码路径:boot/setup.s
- cli
小贴士
EFLAGS:标志寄存器,存在于CPU中,32位,包含一组状态标志、控制标志和系统标志。如第0位的CF(Carry Flag)为CPU计算用到的进位标志,以及图中关中断操作涉及的第9位IF(Interrupt Flag)中断允许标志。
点评
关中断(cli)和开中断(sti)操作将在操作系统代码中频繁出现,其意义深刻,用心良苦。慢慢地你会发现,cli和sti总是在一个完整的操作过程的两头出现,目的是为了避免中断在此期间介入。接下来的代码将为操作系统进入保护模式做准备,此处即将进行实模式下中断向量表和保护模式下中段描述符表(IDT)的交接工作。试想,如果没有cli,又恰好发生了中断,如用户不小心碰了一下键盘,中断就要切进来,就不得不面对实模式的中断机制已经废除,但保护模式的中断机制尚未完成的尴尬局面,结果就是系统崩溃。cli和sti保证了这个过程中中断描述符表能够完整创建,以避免不可预料的中断进入,从而造成中断描述符表创建不完整或新老中断机制混用。甚至可以理解为cli和sti是为了保护一个新的计算机生命的完整创建。
下面,setup程序做了一个影响深远的动作:将位于0x10000的内核程序拷贝至内存地址起始位置0x00000处!代码如下:
- //代码路径:boot/setup.s
- do_move:
- mov es,ax ! destination segment
- add ax, #0x1000
- cmp ax, #0x9000
- jz end_move
- mov ds, ax ! source segment
- sub di, di
- sub si, si
- mov cx, #0x8000
- rep
- movsw
- jmp do_move
图1-17准确标识了复制操作系统内核代码的源位置和目标位置及复制动作的方向。
图1-17 复制system模块至内存起始处 |
回顾一下图1-2的内容,0x00000这个位置原来存放着由BIOS建立的中断向量表及BIOS数据区,这个复制动作将BIOS中断向量表和BIOS数据区完全覆盖,使它们不复存在。直到新的中断服务体系构建完毕之前,操作系统不再具备响应并处理中断的能力。现在,我们开始体会到图1-16中的关中断操作的意义了。
点评
这样做能取得一箭三雕的效果:
(1)废除BIOS的中断向量表,等价于废除了BIOS提供的实模式下的中断服务程序。
(2)收回使用寿命刚刚结束的程序所占的内存空间。
(3)让内核代码占据内存物理地址最开始的、最天然的、最有利的位置。
“破旧立新”这个成语用在这里特别贴切,system模块复制到0x00000这个动作废除了BIOS的中断向量表,也就是废除了16位的中断机制。操作系统是不能没有中断的,对外设的使用、系统调用、进程调度都离不开中断。Linux操作系统是32位的现代操作系统,16位的中断机制对32位的操作系统而言显然是不合适的,这也是废除16位中断机制的根本原因。为了建立32位的操作系统,我们不但要“破旧”,还要“立新”—建立新的中断机制。