lab_1 清华大学ucore bootload启动ucore os(预备知识)
1.0实验内容:
lab1中包含一个bootloader和一个OS。这个bootloader可以切换到X86保护模式,能够读磁盘并加载ELF执行文件格式,并显示字符。而这lab1中的OS只是一个可以处理时钟中断和显示字符的幼儿园级别OS。bootload(引导装入)的翻译。
Lab1主要是关于操作系统如何启动,以及如何中断,函数调用栈相关的知识。
1.1 x86启动顺序
硬件启动后,怎么把操作系统放到内存中去运行。
1 BIOS启动过程
从BIOS到Bootload
CPU加电后,寄存器初始值为空。通过CS(16位的代码段寄存器)和EIP(指令指针寄存器)来决定启动的第一条指令的地址。
计算机加电后,CPU从物理地址0xFFFFFFF0(由初始化的CS:EIP确定,此时CS和IP的值分别是0xF000和0xFFF0))开始执行。在0xFFFFFFF0这里只是存放了一条跳转指令,通过跳转指令跳到BIOS例行程序起始点。
BIOS做完计算机硬件自检和初始化后,会选择一个启动设备(例如软盘、硬盘、光盘等),并且读取该设备的第一扇区的512字节(即主引导扇区或启动扇区)到内存一个特定的地址0x7c00处,然后CPU控制权会转移到那个地址继续执行。至此BIOS的初始化工作做完了,进一步的工作交给了ucore的bootloader。
2 bootloader启动过程
从Bootload(512字节)到OS
BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。bootloader完成的工作包括:
- 从实模式(16/20位寻址空间-1M)切换到保护模式(32位寻址空间-4G),启用分段机制。(为后续操作系统的执行做准备)
- 读磁盘中kernel代码(ELF执行文件格式的ucore操作系统到内存
- 显示字符串信息
- 把控制权交给ucore操作系统
3 段机制
段机制内容:
总结:你需要建立好一个GDT(全局段描述符表),这里面每一项是一个段描述符,然后我还要把相应的段寄存器CS DS等设置好对应的index,是的CS,DS等这些段寄存器能够指向全局描述符表GDT对应的项。这个项我们称为段描述符,这个描述符指出映射关系,从而可以在使能了保护机制后,使段机制能够正常的工作。
4 加载ELF格式的ucore OS kernel
ucore编译之后会生成ELF格式的执行程序。
总结:能识别出很重要的一些关键信息,然后把相应的代码段 数据段从我们的文件读到我们的内存中来。
1.2 C函数调用的实现
具体细节上怎么实现系统调用
其他注意事项:
参数和函数返回值可通过寄存器或位于内存中的栈来传递
不需要保存/恢复所有寄存器
1.3 gcc内联汇编
gcc是编译内核的一个编译器,有很方便的内联汇编。可以在C文件中嵌入汇编代码,就不用单独写一个汇编的文件。也有相应的规则。
Example:
asm代表内联汇编的关键字,然后把上一段汇编括起来,用引号表示一段字符串。%->%%
1.4 x86-32下的中断处理
一旦操作系统启动后,很重要的事情就是接管中断
1 x86中断源:
中断(异步中断、外部中断)
在操作系统中,有三种特殊的中断事件。由CPU外部设备引起的外部事件如I/O中断、时钟中断、控制台中断等是异步产生的(即产生的时刻不确定),与CPU的执行无关,我们称之为异步中断(asynchronous interrupt)也称外部中断,简称中断(interrupt)。
异常(同步中断、内部中断)
而把在CPU执行指令期间检测到不正常的或非法的条件(如除零错、地址访问越界)所引起的内部事件称作同步中断(synchronous interrupt),也称内部中断,简称异常(exception)。
软中断(陷入中断、trap)
把在程序中使用请求系统服务的系统调用而引发的事件,称作陷入中断(trap interrupt),也称软中断(soft interrupt),系统调用(system call)简称trap。
2 CPU与操作系统如何处理中断
每个中断或异常(中断向量、中断异常编号)与一个中断服务例程(Interrupt Service Routine,简称ISR)关联,其关联关系存储在中断描述符表(IDT)中。
IDT的起始地址和大小保存在 中断描述符表寄存器(IDTR)中。操作系统在IDT中设置好各种中断向量对应的中断描述符,留待CPU在产生中断后查询对应中断服务例程的起始地址。
处理中断的过程:
1.当CPU收到中断或者异常的事件时,它会暂停执行当前的程序或任务
2.通过查询IDT表 跳转 到负责处理这个信号的相关处理例程中
3.在完成对这个事件的处理后再跳回到刚才被打断的程序或任务中
IDT关联的建立
3 能够对中断向量表(中断描述符,简称IDT Interrupt Descriptor Table)进行初始化
中断产生后的堆栈栈变化
在不同特权级它的处理方式是不一样的,特权级是段描述符设定它到底处于哪个特权级。比如CS低两位是0表示运行于内核态,低两位是3表示运行于用户态。在内核态产生的中断依然在内核态,但是在用户态产生的中断也会跳到内核态里面去,这里面就会产生特权级的变化。对于这种特权级的变与不变,中断的保存与恢复也是不一样的。
下图显示了给出相同特权级和不同特权级情况下中断产生后的堆栈栈变化示意图:
对于同一特权级,意味着在内核态产生的中断依然在内核态。它的栈还是同一个栈,没有发生变化,知识在这个栈上面压了一些寄存器的内容,就是在被打断的那一刻寄存器的内容。第一个是Error Code,表示严重的异常莫不是每一个中断或者异常都会产生Error Code,第二个会压入EIP和CS,是当前被打断的那个地址(或者是当前被打断的下一个地址),下一个是EfLAGS是当前被打断的时候的标志性的内容。这三个是一旦产生中断的时候,硬件会压栈压进去。它压的是在同一个栈里面。
对于不同特权级,意味着中断产生的那一刻,我们的应用程序正在用户态执行,在用户态执行的时候,我们可以看到从用户态到内核态他们是用的不同的栈。ESP和SS是当时产生中断的时候在用户态里面的那个栈的地址,在执行完毕要恢复的时候也会发生变化,不会在内核态去执行。
完成中断服务例程之后,还要返回到被打断程序继续执行:这里面对于中断服务例程来说,它会通过一个iret指令来完成。对于通常的程序来说,他是通过ret和retf完成函数的返回。
对于没有改变特权级的方式我们可以看到,其实是把在同一个栈里面,把这个弹出,根据CS和EIP来跳转到被打断的那个地方继续执行,同时还要恢复他的Eflag的值,这是IRET弹出来的时候要干的事情。
但对于RET而言,它只是弹出了EIP,跳到当时调的下一条指令去执行,对于retf而言弹出CS和EIP,实行一种远程跳转的功能,从而确保被打断的用户态的程序能够正常地继续执行。(弹出的东西更多了)
4 系统调用(软中断、trap陷入)
用户程序通过系统调用访问OS的内核服务
如何实现?
需要指定中断
使用trap或者使用特殊指令(SYSENTER/SYSEXIT)