BIOS的启动原理
0xFFFF0
CPU 硬件逻辑设计为家电瞬间强行将 CS 的值置为 0xFFFF0, IP 的值置为 0x0000。这样 CS:IP 就指向 0xFFFF0 这个地址位置,这个是一个纯硬件完成的动作。
如果此时这个位置有可执行代码,计算机将从这里的代码开始,沿着后续程序一直执行下去。
BIOS 程序的入口点恰好是 0xFFFF0,也就是说 BIOS 程序的第一条指令就被设置在这个地址上。
中断向量表和中断服务程序
BIOS 程序被固化在 ROM 中。BIOS 启动后,会开始检查显卡、内存等等信息,然后最重要的一点是还会建立 中断向量表和中断服务程序。
中断 INT (INTerrupt): 中断就是中途打断一件正在进行中的事情。其最初的含义是:外在的事件打断正在执行的程序,转而执行这个事件的特定程序,处理结束后,回到被打断的程序继续执行。
中断对操作系统来说意义重大。
开始加载系统内核
BIOS 向 CPU 发送一个中断 int 19h ,CPU 接收到这个中断后,会立即在中断向量表中找到 int 19h 中断向量在内存中所在的准确位置,接下来中断向量把 CPU 指向 0x0E6F2, 这个位置就是 int 0x19h 相对应的中断服务程序的入口地址,通过这段程序会把硬盘的第一个扇区中的程序(512B)加载到内存中的指定位置。
这个就是系统的引导程序,其作用就是陆续把硬盘中的操作系统分批次的载入内存,这个引导程序称为 bootsect。
BIOS 为了能和操作系统协同工作,现行的方法就是“两头约定” 和“定位识别”
bootsect 还要做的一个很重要的事情就是内存规划,因为 bootsect 是用汇编写的,为了防止程序的相互覆盖,它必须自己解决内存分配的问题,而不像高级语言,编译器都已经帮我们做好了。
bootsect 分为三阶段,此处略过。
第三阶段完成后,操作系统内核即被加载如内核
至此 BIOS 就完成了它的任务
到这里只有,虽然大部分任务都完成了,但是并没有完全由操作系统接手,此时需要关中断 ,将CPU的标志寄存器(EFLAGS)中的中断允许标志(IF)置0。这意味着,这接下来的执行过程中,无论是否发生中断,系统都不在响应此中断。直到取而代之的是由操作系统自身提供的中断服务程序。
关于为什么要关中断,可以这样理解,在 BIOS 中断即将退出舞台的时候,新的中断尚未建立完成,如果此时,用户敲击键盘,则会引发中断,而操作系统又无法去找到中断服务程序,则面临系统崩溃的风险。
内核登场
设置中断描述符表和全局描述符表
这其中还有一些列的任务要做,此处略过。
软中断
在操作系统中,为了对内核进行保护,是不允许用户进程直接访问内核的,但处理具体事务又需要内核代码的支持。比如:读文件和写文件。为了解决这个矛盾,系统的设计者提供了这样一个方案——系统调用软中断,即提供一套系统服务接口,用户进程只要想和内核打交道,就调用这套接口程序。之后,就会立即引发软中断,后面的事情就不需要用户程序负责了,而是通过另一条执行路线—由 CPU 对这个信号进行响应,通过中断描述符表找到系统调用端口,进而调用到具体的系统调用函数,来处理事务。可见,所有的系统调用函数,其实也是中断服务程序,更准确的说就是软中断服务程序。
GDT
全局描述符表,当初始化完成如硬盘、外设设备等环境后,并且建立好针对每一个设备的中断描述符表和中断服务程序后。
这个时候就可以 开中断 了。
开中断
现在,系统所有中断服务程序都已经和中断描述符表正常挂接,这意味着中断服务体系已经构建完毕,系统可以在保护模式下处理中断信号了。所以要将中断开启。
这时,由前面创建的第一个进程0就即将在此环境下正式开始工作
操作系统为进程0创建进程1做准备
在 Linux 0.11 中,除进程0外,所有进程都是由一个已有进程在用户态下完成创建的。为了遵守这个规则,在进程0正式创建进程1之前,要将进程0由内核态转变为用户态。通过模拟中断返回动作,实现进程0的特权级从内核态转变为用户态。
用户态、内核态及这两种状态切换的方式介绍:
CPU 中为了保护代码和数据设计了多种特权级,目的是为了防止低特权级的代码直接跳转执行高特权级代码或访问高特权级数据,避免引发灾难性后果。
用户态时特权级值为3
内核态时特权级为0
之后,还有很多工作去创建进程1。
系统切换到进程1执行
进程1继承了进程0全部能力,进一步构建环境,使进程能够依托系统与外设“以文件形式”进行数据交互。
从根本上将,都要依靠文件系统的支持,所以进程1是通过加载的根文件系统来达到这个目的的。
当进程1完成了它自己的工作后,通过时钟中断,或者进程切换,进程0再继续执行其他初始化操作。
参考: Linux 内核设计艺术