软件分为两个层次,一是操作系统,二是应用(用户)程序。
用户程序只关心问题的解,就是采用各种算法来解决实际问题。至于软件是怎么加载到内存的,怎么定位的,不是它所操心的事。但是,它有义务提供一些必要的信息,来帮助操作系统将自己加载到内存中。
操作系统则必须考虑采用什么方法来加载用户程序,并在适当的时候将处理器的执行流转移到用户代码中去。同时,为了减轻用户程序的工作量,操作系统还应当管理硬件,并提供大量的例程供用户程序使用。比如,显示一个字符串,就不要让用户自己来写代码了,直接调用操作系统的代码即可。但操作系统和用户程序应当协商一种机制,让用户程序能够在使用这些例程时,不必考虑和关心它们的位置。
13.2 内核的结构、功能和加载
【13.2.1 内核的结构】
内核分为四个部分,分别是初始化代码、内核代码段、内核数据段和公共例程段,主引导程序也是初始化代码的组成部分。
初始化代码用于从BIOS那里接管处理器和计算机硬件的控制权,安装最基本的段描述符,初始化最初的执行环境。然后,从硬盘上读取和加载内核的剩余部分,创建组成内核的各个内存段。
内核的代码和数据用于分配内存,读取和加载用户程序,控制用户程序的执行。
内核数据段提供了一段可读可写的空间,供内核自己使用。
公共例程段用于提供各种用途和功能的子过程以简化代码的编写。这些例程既可以用于内核,也供用户程序调用。
除了以上内容外,内核文件还包括一个头部,记录了各个段的汇编位置,这些统计数据用于告诉初始化代码如何加载内存。
本章的硬盘主引导扇区程序和上一章整体上差不多,具体内容略有区别。
1、数据段描述符仍然和上一章一样,段的基本数据为:基地址为0x00000000,段界限为0xFFFFF,粒度为4KB的可读可写向上扩展的段,它是可以访问整个0~4GB内存的。
2、代码段描述符与上一章一样,段的基本数据为:基地址为0x00007C00,界限为0x001FF,粒度为字节的只执行向上扩展的段。
3、堆栈段描述符与上一章一样,段的基本数据为:基地址为0x00007C00,界限为0xFFFFE,粒度为4KB的可读可写向下扩展的段。
4、因为不涉及读写代码段的要求,所以本章没有定义代码段的别名描述符。
5、因为需要在保护模式下在屏幕上显示内容,所以本章增加了显示缓冲区描述符。段的基本数据为:基地址0x000B8000,界限0x07FFF,粒度为字节的可读可写向上扩展的段。
6、因为本程序进入保护模式之后,还涉及到读取硬盘内容,因此还定义了两个常数,一个是core_base_address,值为0x00040000,它是我们设定的内核准备加载至的目的起始内存地址。另外一个是core_start_sector,它的值为0x00000001,它是我们设定的内核程序在硬盘上的起始逻辑扇区号,所以最后程序编译好之后,内核程序应该保存到硬盘上起始逻辑扇区号1的地方。
7、增加了硬盘数据读取过程read_hard_disk_0,每调用一次该过程,读取一个指定扇区号的扇区。
8、增加了一个过程,make_gdt_descriptor,该过程主要根据提供的入口参数构造一个描述符。入口参数:EAX提供段的线性基地址,EBX提供段界限,ECX提供段的属性(各属性位都在原始位置,其他与没用到或者与属性无关的位都置0)。返回:EDX描述符的高32位,EAX描述符的低32位。
在保护模式下,本例程内核程序要正常运行有几个条件:
1、它必须要被主引导扇区程序加载到内存中某个地址范围内;
2、它的各个段的描述符必须在运行前设置好,此时它自己还未运行,由谁来设置呢,当然是由主引导扇区程序来设置;
3、段描述符设置好并安装完成之后,还要再次设置正确的GDT。因为主引导扇区设置GDT的时候,内核程序的段描述符还没有被加进来,内核的段描述符安装完成之后,GDT变长了,所以它的段界限值必须被重新设定为新的正确的值。
4、再次清空流水线并串行化处理器,也就是用一个远程跳转指令,跳转到内核程序的入口点。
只有完成上述工作,内核才能真正开始按照我们设计意愿执行。