我们一打开计算机的时候,CPU会指向ROM区域的FFFF0地址开始执行,这段区域就是BIOS的指令。BIOS会将
磁盘的0磁道0扇区读入RAM的7C00的地方,并且将CS:IP寄存器指向这里那么我来看看BIOS加载了什么东西到RAM的7C00处
bootsect
0磁道0扇区的代码里面存放的是操作系统的bootsect代码,我们来阅读一下
# BOOTSEG = 0x07c0 INITSEG = 0x9000
// 这里设置ds段寄存器
mov ax,#BOOTSEG
mov ds,ax
// 这里设置了一个es寄存器
mov ax,#INITSEG
mov es,ax
// 重复256次
mov cx #256
// 设置段内偏移为0
sub si,si
sub di,di
// 移动一个字,重复256次就是移动512个字节
rep movw
jmpi go,INITSEG
通过上面的程序可以看出,这段代码其实就是把 07C00地址的512个指令移动到了90000的内存地址处,至于为什么要移动代码,我们之后会看到原因
go: mov ax,cs // cs=0x9000
mov ds,ax
mov es,ax
mov ss,ax
mov sp,#0xff00
load_setup:
mov dx,#0x0000
mov cx,#0x0002
mov bx,#0x0200
// 0x13 是读磁盘的中断, ah=0x02-读磁盘 al=扇区数量,SETUPLEN=4
// cl 开始扇区
mov ax,#0x0200+SETUPLEN int 0x13
jnc ok_load_setup
mov dx,#0x0000
mov ax,#0x0000
int 0x13
j load_setup
从上面的代码可以看出,操作系统读取了cl=2 从2号扇区开始读取4个扇区的数据是SETUP程序
setup模块
上面bootsect代码做了几件事情
- 把bootsect代码从内存的07c0移动到了90000上
- 把磁道的2到5扇区内容加载到内存 接下来我们就看看setup模块是在干什么
从上面的代码中我们可以看出,操作系统的代码被移动到了内存0地址开始,这也就解释了为什么bootsect代码一开始要从07c0的地方移动到90000以后,因为操作系统要挪动进来所以内存布局如图
在setup模块最后的时候,还执行了一段代码
MOV AX,#0X0001 MOV CR0,AX
JMPI 0,8
将 CR0的最后一位制成了1,CR0是一个寄存器,如果最后一位是1的话,表示CPU开始进入保护模式.从此寻址方式发生了改变
保护模式
原有计算机的寻址模式是 16位的段寄存器CS左移4位+16位的IP寄存器确定内存,这样的最大寻址范围只有2^20也就是1M的寻址空间。在现代计算机下1M内存根本不够用,于是又想出了另一种办法来扩大寻址范围。也就是引入了gdt表和idt表
gdt表
在保护模式下,原有的CS里面存放的不再是段地址,而是gdt表的下标位置,然后gdt表里面存放的是段的地址。通过cs去gdt中获取段地址再加上ip寄存器的地址获得真正的物理位置
知道了保护模式下的内存寻址方式是基于GDT表来实现的,那么这个GDT表的内容是什么时候装进去的呢?还是在setup模块里面对gdt表进行初始化
现在我们再来看 setup最后的那个汇编
JMPI 0,8表示的含义
CS通过gdt表找到8号指令为 07FF,0000,9A00,00C0然后再去GDT表项中翻译一下,获得真实地址,如下图
所以真实的段地址为0,然后 JMPI 0,8 的IP寄存器也是0 所以进入保护模式后,CPU要从内存为0的地方开始执行指令。
head 模块
在setup进入保护模式后,跳转到内存0地址开始执行指令,就来到了head.s模块。我们来看一些比较重要的地方
从上面的代码可以看到head在设置了页表之后,就跳到了
c语言写的main函数中,上面的0,0,0 就是main函数的参数
main函数
mem_init函数
我们知道了main函数就是一堆初始化,我们来看看linux对内存的初始化管理。 这个管理有点类似 Netty对内存管理的思路,是伙伴算法利用一个数组来标记内存的使用情况
void mem_init(long start_mem,long end_mem)
{
int i;
for(i = 0; i<PAGING_PAGES,i++){
// 把操作系统占用的内存标记已使用
mem_map[i] = USED;
}
i = MAP_NR(start_mem)
end_mem -= start_mem
// 右移12位表示减去 4K, 因为linux一页是 4K
end_mem >>= 12
while(end_mem -- > 0){
mem_map[i++] = 0;
}
}
这个函数的入参 end_mem就是在 setup模块把内存放到 90002的地址。