自己动手写操作系统-计算机通电之后的操作
以前每次开机,先是看到一个黑屏幕上显示一些字符,然后会进入windows经典的启动界面。对于要编写操作系统来说,这种表面的认识是不够的,得需要知道一下通电后,执行的入口是哪里,启动的流程是如何跑起来的。
先说结论:通电后,CPU的cs:ip寄存器被强制初始化为0xF000:0xFFF0。
接下来,为了理解CPU的cs:ip寄存器被初始化后的效果,需要学习一些准备知识:
- 什么是分段访问
- 什么是cs:ip寄存器
- 什么是实模式
- 实模式下的内存布局是如何的
- 0xF000:0xFFF0地址有什么特别意义
- cs:ip寄存器初始化为0xF000:0xFFF0后续操作是什么
什么是分段访问
8086的地址线有20位,也就是说8086可以寻址的范围是00000-FFFFF,1MB的内存。但是8086的寄存只有16位,直接使用一个寄存器只能寻址64KB的内存。
为了能访问到1MB的内存,8086引入了段的概念,把内存区域划分为一个一个的段:
用一个寄存器,存放段的起始位置,称为段基址,另外一个寄存器存放偏移量,称为段内偏移。起始位置+偏移量=最终的地址。这里需要注意的是,起始位置必须是20位的,否则两个16位的数字相加,区间为FFFF+FFFF=1FFFE,也是无法寻址20位地址的空间的。8086想的方法是存放段基址的寄存器左移4位实现20位,然后再加上偏移量。
所以最终物理地址的计算公式为:段基址*16+段内偏移
什么是cs:ip寄存器
大家知道CPU可以执行机器码,这些机器码包含了操作码和操作数或操作寄存器。
机器码是我们的程序编译后生成的,保存在持久化设备中,比如软盘,硬盘。但是CPU不会直接与硬盘交互,只会与内存交互,所以有一个过程,会把程序加载到内存中,然后CPU来读取,来执行。问题是CPU如何知道自己从内存的哪个位置读取机器码呢?答案就是cs:ip寄存器。
cs是代码段寄存器,存放代码段段基址,ip是指令寄存器,存放段内偏移。cs:ip永远保存下一条要被执行的机器码的地址。
只要给CPU一个cs:ip后,CPU取出机器码执行,同时会对ip加上机器码的长度,执行完当前机器码后,会根据cs:ip再次取出机器码,周而复始,直到关机断电。
我们无法直接操作ip寄存器,但是可以通过jmp等跳转相关指令修改cs:ip寄存器的值,实现分支等逻辑。
什么是实模式
8086中只有一种模式,80286中出现了保护模式,同时把8086的模式称为实模式。
实模式的特点是:
- 实模式使用20位地址线,可以寻址1MB内存。
- 实模式下寻址的地址如果超过20位,则会卷回到低地址,也就是取模的效果。
- 在实模式下,所有的段都是可以读、写和执行的。
8086以及之后的CPU启动后进入的都是实模式。操作系统需要在实模式下引导系统启动。所以即使实模式是历史久远的落后模式,我们依然要学习如何在实模式下编程。
实模式下的内存布局是如何的
实模式下可以寻址1MB的内存,不过这个内存不是完全对应插在主机内存插槽上的内存。这个物理的内存称为DRAM。这1MB的区域只有一部分是对应到DRAM上的,其他的地址对应到BIOS的ROM,内卡的ROM。具体见下表:
起始 | 结束 | 大小 | 用途 |
---|---|---|---|
FFFF0 | FFFFF | 16B | BIOS入口地址,此地址也属于BIOS代码,同样属于顶部的640KB字节。只是为了强调其入口地址才单独贴出来。此处16字节的内容是跳转指令jmp f000:e05b |
F0000 | FFFEF | 64KB-16B | 系统BIOS范围是F0000~FFFFF共640KB,为说明入口地址,将最上面的16字节从此处去掉了,所以此处终止地址是0XFFFEF |
C8000 | EFFFF | 160KB | 映射硬件适配器的ROM或内存映射式I/O |
C0000 | C7FFF | 32KB | 显示适配器BIOS |
B8000 | BFFFF | 32KB | 用于文本模式显示适配器 |
B0000 | B7FFF | 32KB | 用于黑白显示适配器 |
A0000 | AFFFF | 64KB | 用于彩色显示适配器 |
9FC00 | 9FFFF | 1KB | EBDA(Extended BIOS Data Area)扩展BIOS数据区 |
7E00 | 9FBFF | 622080B约608KB | 可用区域 |
7C00 | 7DFF | 512B | MBR被BIOS加载到此处,共512字节 |
500 | 7BFF | 30464B约30KB | 可用区域 |
400 | 4FF | 256B | BIOS Data Area(BIOS数据区) |
000 | 3FF | 1KB | Interrupt Vector Table(中断向量表) |
其中0~0x9FFFF对应到DRAM。
BIOS中也有只读存储器ROM,保存BIOS的程序,F0000~FFFFF这64KB空间对应的是BIOS ROM。
0xF000:0xFFF0地址有什么特别意义
前文提到,CPU在加电后,会强制初始化cs:ip寄存器为0xF000:0xFFF0,按分段寻址的公式计算:0xF000*16+0xFFF0=0xFFFF0。
这个地址对应的是什么数据呢?我们看下上面的实模式1MB内存布局的表格,可以看到F0000~FFFFF是BIOS程序的地址,而0xFFFF0则是指向最后16字节的地址。
也就是说,CPU启动后,会强制初始化cs:ip寄存器为0xF000:0xFFF0,是为了取到这个地址的执行去执行,而这个地址是BIOS程序的一个部分,也就是说,CPU启动后,就开始执行BIOS的自带的程序。
所以可以推论,CPU启动后,就开始执行BIOS程序,这是电脑设计者的目的,同时他们规定了1MB的最后16字节为BIOS程序的入口,基于这种规定,CPU才会做出强制初始化cs:ip寄存器为0xF000:0xFFF0的行为。
那这个0xFFFF0地址对应的指令具体是啥呢?
Bochs学习-安装配置篇有提到bochs启动后会自动暂停,那个时候就是CPU一条指令还未执行的时候,我们来看看:
1 | ➜ /Users/mazhibin/project/system/bochs-conf-file > bochs -q |
可以看到,bochs显示了CPU接下来要执行的指令,也就是cs:ip寄存器指向的内存区域中存放的指令,地址是f000:fff0,这也印证了CPU在启动时,cs:ip的值会被设置为0xF000:0xFFF0。指令内容是jmp far f000:e05b
,一个跳转指令,跳到了BIOS程序的入口,接下来BIOS程序开始检测内存,显卡的外设信息,初始化硬件,并在0x000~0x3FF内存区域中建立中断向量表。
BIOS程序执行完后,就会开始从可能的启动设备中启动操作系统,这个后面来继续学习。
总结
- x86CPU启动后进入实模式
- 实模式下使用20位的地址线,可以寻址1MB内存
- 实模式下使用分段的方式访问1MB的空间,方法是两个寄存器,一个保存段基址,一个保存段内偏移,然后通过公式:段基址*16+段内偏移,得到20位的地址
- CPU通电后,cs:ip寄存器会被强制更新为0xF000:0xFFF0,这个地址指向BOIS程序的入口
- 所以CPU通电后,就会开始执行BOIS的程序,检测并初始化硬件,最后BIOS程序尝试从可能的启动设备中启动操作系统。