在系统加电(我们按下电源开关)后,开始初始化他的寄存器,主要是cs和eip(基于x86架构),然后在ROM中找到一个叫BIOS(Basic Input Output System),加载到RAM中然后开始执行他,他在进行完设备的自检和初始化之后,就根据他自己内部的“我该去哪个设备启动加载程序”表,将其中第一个设备的主引导扇区加载到内存中来,也就是将系统的控制权转交给了这个程序,然后他在正确的引用了bootloader,也就是我们常说的“引导程序”,这个引导程序就会在内存的I/O区域中读入硬盘(os的位置)扇区的信息,得到操作系统内核的ELF文件,加载到内存中来,然后将控制权转交给OS,就大功告成了。
ROM-BIOS:意思是只读存储器基本输入输出系统。是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、系统设置信息、开机上电自检程序和系统启动自举程序。 形象地说,BIOS应该是连接软件程序与硬件设备的一座"桥梁",负责解决硬件的即时要求。一块主板性能优越与否,很大程度上就取决于BIOS程序的管理功能是否合理、先进。主板上的BIOS芯片或许是主板上唯一贴有标签的芯片,一般它是一块32针的双列直插式的集成电路,上面印有"BIOS"字样。586以前的BIOS多为可重写EPROM芯片,上面的标签起着保护BIOS内容的作用(紫外线照射会使EPROM内容丢失),不能随便撕下。586以后的ROM BIOS多采用EEPROM(电可擦写只读ROM),通过跳线开关和系统配带的驱动程序盘,可以对EEPROM进行重写,方便地实现BIOS升级。常见的BIOS芯片有Award、AMI、Phoenix、MR等,在芯片上都能见到厂商的标记。
BIOS的主要作用有以下几方面: 首先是自检及初始化程序:计算机电源接通后,系统将有一个对内部各个设备进行检查的过程,这是由一个通常称之为POST(Power On Self Test/上电自检)的程序来完成,这也是BIOS程序的一个功能。 完整的自检包括了对CPU、640K基本内存、1M以上的扩展内存、ROM、主板、CMOS存贮器、串并口、显示卡、软硬盘子系统及键盘的测试。 在自检过程中若发现问题,系统将给出提示信息或鸣笛警告。如果没有任何问题,完成自检后BIOS将按照系统CMOS设置中的启动顺序搜寻软、硬盘驱动器及CDROM、网络服务器等有效的启动驱动器 , 读入操作系统引导记录,然后将系统控制权交给引导记录,由引导记录完成系统的启动,你就可以放心地使用你的宝贝了。其次是硬件中断处理:计算机开机的时候,BIOS会告诉CPU等硬件设备的中断号,当你操作时输入了使用某个硬件的命令后,它就会根据中断号使用相应的硬件来完成命令的工作,最后根据其中断号跳会原来的状态。再有就是程序服务请求:从BIOS的定义可以知道它总是和计算机的输入输出设备打交道,它通过最特定的数据端口发出指令,发送或接收各类外部设备的数据,从而实现软件应用程序对硬件的操作。
内存(主存):它是可以随时读写,而且速度很快随机存储器。作为操作系统或其他正在运行中的程序的临时数据存储媒介。
在bootloader接手BIOS的工作后,当前的PC系统处于实模式(16位模式)运行状态,在这种状态下软件可访问的物理内存空间不能超过1MB(0X00000~0XFFFFF),且无法发挥Intel 80386以上级别的32位CPU的4GB内存管理能力。
1.实模式:
将整个物理内存看成分段的区域,程序代码和数据位于不同区域,操作系统和用户程序并没有区别对待,而且每一个指针都是指向实际的物理地址。这 样,用户程序的一个指针如果指向了操作系统区域或其他用户程序区域,并修改了内容,那么其后果就很可能是灾难性的。通过修改A20地址线可以完成从实模式 到保护模式的转换。
开机时系统会以实模式进入,此时可访问的内存只有1M大小,这时的内存分配情况如下所示(此时由bios主导这一M内存的使用情况):
0x 0 0 0 0 0
|
| 10x64K=640K; 基本内存
|
0x 9 F F F F
0x A 0 0 0 0
|
| 2x64K=128K; 作为显存使用
| 0xa0000-0xb0000 EGA/VGA/XGA/XVGA图形视频缓冲区
| 0xb0000-0xb8000 Mono text video buffer
| 0xb8000-0xc0000 CGA/EGA+ chroma text video buffer
|
0x B F F F F
0x C 0 0 0 0
|
| 4x64K=256K; 由bios使用,地址如何利用由其自己决定
|
0x F F F F F
---------------------------------------------------------
而通常情况下,bios对属于自己的地址空间的划分方式如下:
0x C 0 0 0 0
|
| 0.5X64k=32k; 显卡bios使用
|
0x C 7 F F F
0x C 8 0 0 0
|
| 0.25x64K=16K IDE控制器bios使用
|
0x C B F F F
.
.
0x F 0 0 0 0
|
| 1x64K=64K; 系统bios使用
|
0x F F F F F
也就是说:C0000H~FFFFFH则被保留给BIOS使用,其中系统BIOS一般占用了最后的64KB或更多一点的空间,显卡BIOS一般在C0000H~C7FFFH处,IDE控制器的BIOS在C8000H~CBFFFH处。
----------------------------------------------------------
基本内存的分配方式如下(由bios分配):
0x 0 0 0 0 0
|
| 1K 中断向量表 每一项占领4字节 共256项
|
0x 0 0 3 F F
0x 0 0 4 0 0
|
| 256字节 bios数据区
|
0x 0 0 4 F F
0x 0 0 5 0 0
|
| 自由内存区 但0x07C00-0x07DFF (512字节)为引导程序加载区
|
0x 9 F F F F
2.bootloader模式切换:
要将实模式转换到保护模式下,首先要做的就是打开A20Gate,实现实模式向保护模式的转换过程:
激活A20地址线的流程为:
1.关闭中断;
2.等待8042 Input buffer为空;
3.发送禁止键盘操作命令;
4.等待8042 Input buffer为空;
5.发送读取8042 Output Port命令;
6.等待8042 Output buffer有数据;
7.读取8042 Output buffer,并保存得到的字节;
8.等待8042 Input buffer为空;
9.发送Write 8042 Output Port命令到8042 Input buffer;
10.等待8042 Input buffer为空;
11.将从8042 Output Port得到的字节的第2位置1(或清0),然后写入8042 Input buffer;
12.等待,直到8042 Input buffer为空为止;
13.发送允许键盘操作命令到8042 Input buffer;
14. 打开中断。
3.保护模式:
只有在保护模式下,80386的全部32根地址线有效,可寻址高达4G字节的线性地址空间和物理地址空间,可访问64TB(有2^14个段,每个段 最大空间为2^32字节)的逻辑地址空间,可采用分段存储管理机制和分页存储管理机制。这不仅为存储共享和保护提供了硬件支持,而且为实现虚拟存储提供了 硬件支持。通过提供4个特权级和完善的特权检查机制,既能实现资源共享又能保证代码数据的安全及任务的隔离。
3.1 分段存储管理机制
只有在保护模式下才能使用分段存储管理机制。分段机制将内存划分成以起始地址和长度限制这两个二维参数表示的内存块,这些内存块就称之为段 (Segment)。分段机涉及4个关键内容:逻辑地址、段描述符(描述段的属性)、段描述符表(包含多个段描述符的“数组”)、段选择子(段寄存器,用于定位段描述符 表中表项的索引)。转换逻辑地址(Logical Address,应用程序员看到的地址)到物理地址(Physical Address, 实际的物理内存地址)分以下两步:
(1)分段地址转换:CPU把逻辑地址(由段选择子selector和段偏移offset组成)中的段选择子的内容作为段描述符表的索引,找到表中对应的段描述 符,然后把段描述符中保存的段基址加上段偏移值,形成线性地址(Linear Address)。如果不启动分页存储管理机制,则线性地址等于物理地址。
(2)分页地址转换,这一步中把线性地址转换为物理地址。
线性地址空间由一维的线性地址构成,线性地址空间和物理地址空间对等。线性地址32位长,线性地址空间容量为4G字节。分段地址转换的基本过程如下图:
3.2 段描述符
在分段存储管理机制的保护模式下,每个段由如下三个参数进行定义:段基地址(Base Address)、段界限(Limit)和段属性(Attributes)。在ucore中的kern/mm/mmu.h中的struct segdesc 数据结构中有具体的定义。
- 段基地址:规定线性地址空间中段的起始地址。在80386保护模式下,段基地址长32位。因为基地址长度与寻址地址的长度相同,所以任何一个段都可以从32位线性地址空间中的任何一个字节开始,而不象实方式下规定的边界必须被16整除。
- 段界限:规定段的大小。在80386保护模式下,段界限用20位表示,而且段界限可以是以字节为单位或以4K字节为单位。
- 段属性:确定段的各种性质。
- 段属性中的粒度位(Granularity),用符号G标记。G=0表示段界限以字节位位单位,20位的界限可表示的范围是1字节至1M字节,增量为1字节;G=1表示段界限以4K字节为单位,于是20位的界限可表示的范围是4K
- 段属性中的粒度位(Granularity),用符号G标记。G=0表示段界限以字节位位单位,20位的界限可表示的范围是1字节至1M字节,增量为1字节;G=1表示段界限以4K字节为单位,于是20位的界限可表示的范围是4K字节至4G字节,增量为4K字节。
- 类型(TYPE):用于区别不同类型的描述符。可表示所描述的段是代码段还是数据段,所描述的段是否可读/写/执行,段的扩展方向等。
- 描述符特权级(Descriptor Privilege Level)(DPL):用来实现保护机制。
- 段存在位(Segment-Present bit):如果这一位为0,则此描述符为非法的,不能被用来实现地址转换。如果一个非法描述符被加载进一个段寄存器,处理器会立即产生异常。图5-4显示 了当存在位为0时,描述符的格式。操作系统可以任意的使用被标识为可用(AVAILABLE)的位。
- 已访问位(Accessed bit):当处理器访问该段(当一个指向该段描述符的选择子被加载进一个段寄存器)时,将自动设置访问位。操作系统可清除该位。
上述参数通过段描述符来表示,段描述符的结构如下图所示:
全局描述符表
全局描述符表的是一个保存多个段描述符的“数组”,其起始地址保存在全局描述符表寄存器GDTR中。GDTR长48位,其中高32位为基地址,低16位为 段界限。由于GDT 不能有GDT本身之内的描述符进行描述定义,所以处理器采用GDTR为GDT这一特殊的系统段。注意,全部描述符表中第一个段描述符设定为空段描述符。 GDTR中的段界限以字节为单位。对于含有N个描述符的描述符表的段界限通常可设为8*N-1。在ucore中的boot/bootasm.S中的gdt 地址处和kern/mm/pmm.c中的全局变量数组gdt[]分别有基于汇编语言和C语言的全局描述符表的具体实现。
选择子
线性地址部分的选择子是用来选择哪个描述符表和在该表中索引一个描述符的。选择子可以做为指针变量的一部分,从而对应用程序员是可见的,但是一般是由连接加载器来设置的。选择子的格式如下图所示:
图3 段选择子结构
- 索引(Index):在描述符表中从8192个描述符中选择一个描述符。处理器自动将这个索引值乘以8(描述符的长度),再加上描述符表的基址来索引描述符表,从而选出一个合适的描述符。
- 表指示位(Table Indicator,TI):选择应该访问哪一个描述符表。0代表应该访问全局描述符表(GDT),1代表应该访问局部描述符表(LDT)。
- 请求特权级(Requested Privilege Level,RPL):保护机制,在后续试验中会进一步讲解。
全局描述符表的第一项是不能被CPU使用,所以当一个段选择子的索引(Index)部分和表指示位(Table Indicator)都为0的时(即段选择子指向全局描述符表的第一项时),可以当做一个空的选择子(见mmu.h中的SEG_NULL)。当一个段寄存 器被加载一个空选择子时,处理器并不会产生一个异常。但是,当用一个空选择子去访问内存时,则会产生异常。
3.3 保护模式下的特权级
在保护模式下,特权级总共有4个,编号从0(最高特权)到3(最低特权)。有3种主要的资源受到保护:内存,I/O端口以及执行特殊机器指令的能 力。在任一时刻,x86 CPU都是在一个特定的特权级下运行的,从而决定了代码可以做什么,不可以做什么。这些特权级经常被称为为保护环(protection ring),最内的环(ring 0)对应于最高特权0,最外面的环(ring 3)一般给应用程序使用,对应最低特权3。在ucore中,CPU只用到其中的2个特权级:0(内核态)和3(用户态)。
有大约15条机器指令被CPU限制只能在内核态执行,这些机器指令如果被用户模式的程序所使用,就会颠覆保护模式的保护机制并引起混乱,所以它们被 保留给操作系统内核使用。如果企图在ring 0以外运行这些指令,就会导致一个一般保护异常(general-protection exception)。对内存和I/O端口的访问也受类似的特权级限制。
数据段选择子的整个内容可由程序直接加载到各个段寄存器(如SS或DS等)当中。这些内容里包含了请求特权级(Requested Privilege Level,简称RPL)字段。然而,代码段寄存器(CS)的内容不能由装载指令(如MOV)直接设置,而只能被那些会改变程序执行顺序的指令(如 JMP、INT、CALL)间接地设置。而且CS拥有一个由CPU维护的当前特权级字段(Current Privilege Level,简称CPL)。二者结构如下图所示:
代码段寄存器中的CPL字段(2位)的值总是等于CPU的当前特权级,所以只要看一眼CS中的CPL,你就可以知道此刻的特权级了。
CPU会在两个关键点上保护内存:当一个段选择符被加载时,以及,当通过线性地址访问一个内存页时。因此,保护也反映在内存地址转换的过程之中,既包括分段又包括分页。当一个数据段选择符被加载时,就会发生下述的检测过程:
因为越高的数值代表越低的特权,上图中的MAX()用于选择CPL和RPL中特权最低的一个,并与描述符特权级(Descriptor Privilege Level,简称DPL)比较。如果DPL的值大于等于它,那么这个访问可正常进行了。RPL背后的设计思想是:允许内核代码加载特权较低的段。比如,你 可以使用RPL=3的段描述符来确保给定的操作所使用的段可以在用户模式中访问。但堆栈段寄存器是个例外,它要求CPL,RPL和DPL这3个值必须完全 一致,才可以被加载。CPL、RPL和DPL描述:
- CPL:当前特权级(Current Privilege Level) 保存在CS段寄存器(选择子)的最低两位,CPL就是当前活动代码段的特权级,并且它定义了当前所执行程序的特权级别)
- DPL:描述符特权(Descriptor Privilege Level) 存储在段描述符中的权限位,用于描述对应段所属的特权等级,也就是段本身真正的特权级。
- RPL:请求特权级RPL(Request Privilege Level) RPL保存在选择子的最低两位。RPL说明的是进程对段访问的请求权限,意思是当前进程想要的请求权限。RPL的值由程序员自己来自由的设置,并不一定 RPL>=CPL,但是当RPL<CPL时,实际起作用的就是CPL了,因为访问时的特权检查是判断:max(RPL,CPL)& lt;=DPL是否成立,所以RPL可以看成是每次访问时的附加限制,RPL=0时附加限制最小,RPL=3时附加限制最大。
参考文献:
1.操作系统引导--从实模式到保护模式 链接:https://blog.csdn.net/judyge/article/details/51854446
2.BIOS 内存分布图 链接:https://blog.csdn.net/deng_sai/article/details/50103269
3.BIOS加载MBR到内存地址0x7C00的由来 链接:https://blog.csdn.net/witch_soya/article/details/23124291
4.实模式和保护模式 链接:http://www.cnblogs.com/immortal-worm/p/5867418.html