《操作系统引导探究》链接:http://purec.binghua.com/Article/Class6/Class7/200411/81.htm
图1. 电源开启,PC处理流程
图1中最后一个流程,BIOS检查磁盘的第一个扇区(512B)载入内存的0x0000:0x7c00处,如果这个扇区的最后两个字节是”55AA”,那么这就是一个引导扇区,同理这个磁盘就是一个引导盘,通常这个大小为512B的程序就称为引导程序。
特点:
1、 它的大小是且仅是512B;
2、 结尾两个字节必须为”55AA”,这是引导扇区的标志;
3、 放在磁盘的0磁头0磁道1扇区。
因为BIOS 一次只读一个扇区也即512 字节的数据到内存中,这显然是不够的,现在操
作系统都比较庞大,因此我们必须在引导扇区里将存在磁盘上的操作系统的核心部分读进内
存,然后再跳转到操作系统的核心部分去执行。
不通过操作系统读磁盘,一般有两种方式:
1、直接读写磁盘的I/O端口;
2、通过BIOS中断实现(其建立在第一种方法的基础之上)。
3.1、BIOS的中断处理
图2. 中断处理过程
由上图我们可以看出,产生中断信号后,由中断地址形成部件形成中断向量,中断向量就是中断处理程序的入口地址。这样一块中断程序的入口地址形成了中断向量表,即内存中0x0000~0x03ff之间的这一块内存,找到中断向量之后,在这个中断向量中指出跳转到哪个位置去执行中断的处理。向量地址其实就是指向一个实际内存地址的指针,而这个实际内存地址中往往按排一条跳转指令(jmp)跳转到实际处理此中断的中断服务程序中去执行。中断的时候,一般以寄存器里的值作为中断的参数。
3.2、系统的内存安排
CPU在加电的时候,最初的1M内存是由系统安排的,其安排如下:
图3. 系统安排的1M内存部局
3.3、利用BIOS的13号中断读取磁盘扇区
BIOS利用13号中断读取磁盘,在中断之前先设置寄存器的值,以作为中断的参数:
AH寄存器:存放功能号,为2 的时候,表示使用读磁盘功能
DL寄存器:存驱动器号,表示欲读哪一个驱动器
CH寄存器:存磁头号,表示欲读哪一个磁头
CL寄存器:存扇区号,表示欲读的启始扇区
AL寄存器:存计数值,表示欲读入的扇区数量
”int 13”调用13号中断,将磁盘扇区读入ES:BX 处,因此,在调用它之前,我们实际上还需要设置ES与BX寄存器,以指出数据在内存中存放的位置。
内存访问模式分为保护模式和实模式,计算机在加电时,处于“实模式”,在计算机中有一个CR0 寄存器,又称为0 号控制寄存器,在这个寄存器中,最低位也即第0位,被称为PM(Protected Modle:保护模式)位,当它被清零的时候表示CPU在“实模式”下工作,当它被置位的时候,表示CPU在“保护模式”下工作。在计算机加电的时候,它是被清零的,所在这个时候的计算机,处于“实模式”。
3.1、实模式的内存访问
“实模式”下的内存访问通过段寄存器与偏移量构成,比如描述中常常出现的0x:0000:0x0001 就是一个实模式下的内存地址。分号前面的值表时段寄存器中的值,分号后
面的值表是偏移量,实际物理地址的形成如下图所示:
图4. 实模式物理地址形成过程
3.2、保护模式下的内存地址形成
当线性地址直接放在CPU的信号上就采用“段模式”,如果分页进行就是“页模式”。
逻辑地址、物理地址与性性地址,逻辑地址是程序使用的地址,物理地址就是物理内存存储位置。如果不使用分页机制,则线性地址就是物理地址,它与物理地址一一对应,32位CPU中,可以直接访问的物理地址大小为2的32次方,即4GB的大小,现在很少物理内存能有这么大,所以一般将其划分成许多页,进行必要的页面调度,当某些页在使用的时候另外一些页就没使用,这些没有在使用的页就用来载入4GB上的空间。
也就是说,把物理地址划分成若干个大小相等的页,然后将线性地址划分成与物理地址的页相等的大小的页若干个,多个线性地址的页映射一个物理地址的页。
图5.线性地址与物理地址映射模式(直接相连)
通过上图说明,当线性地址中的页5 需要被访问时,CPU通过地址映射机制将其转换物理地址,发现其对应物理地址中的页1。于是CPU 物理地址页1 的内容放到硬盘上的一个地方(虚拟内存),然后将线性地址中的页5 载入物理内存页1中。
线性地址与物理地址的映射方式除了上图表述的“直接相连”映射之外,还有“全相连映射”,“全相连映射”就是线性地址中的每一个页都可以映射到物理地址中的任何一个空闲的页中。
然而,当不使用分页机制的时候,线性地址就会被CPU当做物理地址来使用,线性地址会被直接放在CPU的地址信号线上,而程序使用的是逻辑地址,这时考虑更多是如何将线性地址与逻辑地址相映射。这时由操作系统与CPU硬件共同完成一种线性地址与逻辑地址之间的映射,我们把它叫做“段模式”,操作系统的任务是分配映射表(描述符表),而CPU硬件的任务是按着映射表进行映射。
有两种重要的描述符表,一种是“全局描述符表(GDT)”,另一种是“局部描述符表(LDT)”。
全局描述符表:全局描述符表是由全局描述符组成,全局描述符我们简单称为描述符,它是一种数据结构,因为这种数据结构存储在一段连续的内存之中,所以就成了表,表由表项组成,全局描述符表的表项就是由这些描述符组成,当然,因为这些描述符是放在全局描述符表中的,所以这些描述符也被叫做全局描述符。描述符由8个字节组成,下面是它的结构:
图6.描述符结构
TYPE: 表明此段的类型,4位中的最高位被置1的时候表示它是数据段,相应的以下的三位,从左到右依次为E、W、A,即数据段的TYPE为:1EWA。其中E表示向下增长位,置1时表示向下增长,W表示可写位,置1表示可写,A表示被访问位(如果CPU 访问了它,此位将会被置1);
S:为1 时表示其为代码或数据段,为0时表示为系统段;
DPL:表示物权级,从00~11,共0,1,2,3 四个特权级;
P:为0时表示此描术符无效,不能被使用;
AVL:留给系统程序随便用的;
D/B:为0的时候表示它是一个16位的段,为1时表示它是一个32位的段;
G:为0时,表示段限的单位是1字节,为1时表示段限的单位是4KB,并且段偏移量的最低12 位将不被检测是否在段限之中。
基址——段在物理内存中的地址
段限——段的大小限制,CPU计算段限的大小是通过以下公式得到:
段基址 + 段限值 * 段限单位 = 此段最大可访问地址
如果一个偏移地址大于了此段最大可访地址的话,CPU 就将产生一个错误中断,这样一来就可以访止一个程序非法访问另一个程序的内存空间,这对内存起到了保护作用,因此“保护模式”由此得名。
所以,如果段限值是0,那么此段最大可能访问地址就是段的基址,因此,当段限单位为
1字节时,此段的段大小就是1字节;当段限单位为4KB 时,因为CPU将不检测偏移量的最左12 位,而这12 位最大可能为0xFFF,因此,这时,此段的可访问范围就为4KB,所以:
(段限值 + 1)* 段限单位 = 此段大小
从程序使用的逻辑地址到线性地址的映射是通过“描述符”来完成的,而“描述符”又是放在描述符表中的,那么一个描述表中有许多描述符,到底选用哪一个描述符是由一个索引来决定,这个索引将指出是表中的第几个描述符,这个索引有一个专门的术语来描述,常常称它为“段选择子”。“段选择子”由2 个字节共16 位组成:
图7. 段选择子结构
RPL:指示出特权级,00~11,共0、1、2、3 四个特权级,与前述一样。
TI:为0时表明这是一个用于全局描述符表的选择子,为1时表明用于局部描述符表。
索引值:用来指示表中第几个描述符。索引值共有13位,因此,每张描述符表共可有2的13次方8K个表项,而一个表项(即描述符)占8个字节,因此一张描述符表最大可达64K。
注:如果将“段选择子”的最后3位置零,这整个段选择子其实就是一个描述符在描述符表中的偏移量!这里我们可以发现Intel 的工程师在设计的时候真的是非常精巧,如此的安排,可以使选取一个描述符的速度极大加快,因为将一个段选择子最后3 位清零后与描述符表的基址相加,就立即可以得到一个描述符的物理地址,通过这个地址就可以直接得到一个描述符。
描述符表的基址 描述符表在内存中的起始地址,也即表中第一个描述符所在的内存地址,系统中用两个特殊的寄存器来存放,一个用于存放全局描述符表的基址,称之为“全局描述符表寄存器(GDTR)”,另一个用来存放局部描述符表的基址,称之为“局部描述符表寄存器(LDTR)”,它们的结构如下图所示:
图8. 描述符表寄存器
其中表限为表的大小限制。
在保护模式下,以前实模式下的段寄存器还是有用的,不过它不在用来存放段的基址,而是用来存放“段选择子”,它的名子也变成了“段选择子寄存器”,在访问内存的时候,我们需要给出的是“段选择子”,而不是段基址了。
比如,我现在想使用全局描述符表中第二个表项,即其中的第二个“段描述符”,这个“段选择子”就需按如下的方式构成:
RPL:00,因为我们现在是在写操作系统,工作在0 特权级
TI:0,我们使用全局描述符
索引值:01,我们使用第二个全局描述符,第一个全局描述符编号为0,第二个为1,用二进制表示就是01。
因此,我们的“段选择子”为:0000 0000 0000 1000,也即 0x0008,因此,对于0x0008:0x0000 这样一个逻辑地址,在保护模式下就应看成是使用全局描述符段中第二个描述符所描述的段,偏移量为0的内存地址。这个逻辑地址的线性地址是怎样形成的呢?请看如下的图示:
图9. 逻辑地址段模式映射到线性地址过程