计算机操作系统学习之旅
本部分内容是本人学习了李述铜老师的操作系统课程之后想巩固知识点的笔记分享,目前还没有和老师沟通能不能放,明天就去沟通。
一、计算机启动简介
- 计算机上电之后,CPU是处于16位运行的实模式,没有打开分页机制,也没有特权级,只有1MB内存可用。
- CPU会跳转到BIOS的入口地址(0xFFFF0)处开始执行,BIOS会进行硬件的自检,保存一些配置数据到特定的地址处。
- 根据配置的顺序,加载引导代码到内存中执行。将硬盘的第一个扇区读取到0x7C00处并跳转到此处执行。
- 引导代码会对操作系统的初始环境进行配置,并加载操作系统到内存中。
- 跳转到操作系统执行
二、实模式以及内核寄存器简单介绍
- CPU启动时,会自动进入实模式。早期的8086芯片的工作模式就是这种,这种模式没有任何保护机制,只能运行16位代码。有点类似现在的嵌入式系统的工作模式。
- X86架构的芯片包含很多的寄存器,比如EAX EBX ECX EDX ESP EBP等通用寄存器。这些通用寄存器有些是有特殊用途的。比如EAX寄存器会被乘法和除法指令自动使用,ECX寄存器会被LOOP指令用作循环计数器,而ESP寄存器通常用来寻址栈上的数据,很少用于普通数据运算和传输。EBP在一些高级语言中被用来引用栈上的函数参数和局部变量,一般也是不用于普通运算,称之为扩展帧指针寄存器。
- 在进入实模式后,EAX EBX ECX EDX只能使用低16位,即AX BX CX DX寄存器。此外还有CS DS SS ES FS GS等段寄存器,用于指向一段内存区域。在访问特定地址时,需要使用段地址加上偏移(段地址 << 4 + 偏移)的方法去寻址,我学习的操作系统使用的是一个段地址都为0只使用偏移地址去寻址的平坦模型。
- 在实模式中只能访问1MB以下的内存,想要访问高于1MB的内存则需要进入保护模式,现在先不赘述。这1MB的内存其中0到0x000003FF内为BIOS的中断向量表信息,0x00000400到0x000004FF部分为BIOS的数据区,0x00000500到0x0007FFFF的内存为自由使用区域,可以用来写一些我们自己的代码,0x00080000到0x000FFFFF也是一些我们不能使用的部分。
三、引导代码
- 这部分代码处于磁盘的第0个扇区内部,BIOS会根据其最后两个字节(1FE, 1FF)是否为0x55和0xaa判断此扇区是否包含有效的引导代码。如果是的话,将自动从磁盘的第0个扇区加载引导代码到0x7C00处执行。
- 在0x7C00处将执行我们的boot程序,利用boot程序去加载loader程序(loader将会在后面提出),boot程序只有512字节,因为计算机启动后只能加载一个扇区到内存中运行,能干的事情比较少,所以利用boot去加载loader然后利用loader去完成内存检测和内核加载。
我们程序使用的是平坦模型即段寄存器值为零。
_start: //此处的地址为 0x7c00
mov $0, %ax //将立即数0传递给ax寄存器
mov %ax, %ds //利用ax寄存器将各段寄存器设置为0
mov %ax, %ss
mov %ax, %es
mov %ax, %fs
mov %ax, %gs //将各个段寄存器值修改为 0
mov $_start, %esp //将boot的栈顶指针定义在 0x7c00处,用来存放一些boot程序的一些数据
jmp boot_entry //跳转到boot程序去执行
在跳转boot_entry程序之前,还要先从磁盘读取一段loader程序到内存中,本文将loader程序的开始地址定位0x8000处。
read_loader:
mov $0x8000, %bx // the start addr
mov $0x2, %ah
mov $0x2, %cx // start from two
mov $64, %al // 32kb
mov $0x0080, %dx
int $0x13 // read
jc read_loader
这段代码是放在上面jmp boot_entry指令前面的,这样在进入boot程序后就可以操纵loader的程序了。
在boot程序中将跳转到loader的开始地址处。
#define LOADER_START_ADDR 0x8000 //定义loader的入口地址
void boot_entry(void) {
((void (*)(void))LOADER_START_ADDR)(); //将0x8000将转成函数指针并去运行它
}
//接下来将跳转到loader
_start: //上面代码将跳转到此处
jmp loader_entry //跳转到loader_entry函数去运行
//#########################
//由于目前是运行在实模式下的,所以程序按照16位的逻辑执行
__asm__(".code16gcc");
void loader_entry (void) {
//在loader函数中可以去完成内存检测和内核加载。
//会在后面的学习之路中展示
}
总结
综上,实现的boot只完成了非常简单的功能,只是简单的加载了loader模块,后续将丰富boot的功能。