03 启动、中断、异常和系统调用

3.1 BIOS

内存ROM部分中一段地址存放BIOS固件,通电后CPU中的寄存器的初始状态会从BIOS中取指令。

为了启动其他程序,BIOS包括:

  • 基本输入输出的程序:完成从磁盘读数据、键盘中读用户输入

  • 系统设置信息:从硬盘、网络还是光盘或USB启动(从哪里加载操作系统)

  • 开机后自检程序

  • 系统自启动程序

BIOS会从硬盘中读取512字节的引导扇区(存放加载程序),把加载程序放到内存指定位置。CPU再跳转到加载程序,把控制权交给加载程序。加载程序再加载操作系统,并且把控制权交给操作系统。

  • BIOS

    • 将加载程序从磁盘的引导扇区加载到0x7c00,然后跳转到0x7c00

  • 加载程序(bootloader)

    • 将操纵系统的代码和数据从硬盘加载到内存中

    • 跳到操作系统的起始地址

BIOS不会直接加载操作系统,因为:

  • 磁盘上是有各种文件系统的,BIOS不需要认识。文件系统识别的功能是加载程序来进行的。

BIOS只能工作在实模式下。

3.2 系统启动流程

BIOS

BIOS其实也不是直接加载加载程序,而是先加载MBR。

BIOS初始化过程

  1. 硬件自检POST

  2. 检查系统中内存和显卡等关键部件的存在和工作状态

  3. 查找并执行显卡等接口卡BIOS,进行设备初始化

  4. 执行系统BIOS,进行系统检测

  5. 按BIOS指定的顺序从外部读入(可能是从磁盘、USB、光驱等)主引导记录

MBR

主引导记录(Master Boot Record, MBR),是计算机开机后访问硬盘时所必须要读取的首个扇区。

主引导记录格式(共512字节):

  • 启动代码:446字节

    • 检查分区表正确性

    • 加载并跳转到磁盘上的引导程序

  • 硬盘分区表:64字节

  • 结束标志:2字节,内容就是55AA。标志这是一个合法的主引导记录。

主引导记录选择要启动的分区,再加载活动分区的引导记录。

活动分区的引导记录

然后活动分区的引导记录再去加载bootloader

加载程序(bootloader)

加载程序的执行步骤:

  • 从文件系统中读取启动配置信息

  • 可选的操作系统内核列表和加载启动参数(是在什么模式下启动系统)

  • 依据配置加载内核

3.3 中断、异常和系统调用

这时三种内核与外部交流的方法。为什么需要中断、异常和系统调用

  • 在计算机系统中,内核是被信任的第三方。只有内核可以执行特权指令。

  • 中断:是为了外设连接计算机

  • 异常:是为了处理应用程序执行时的以外

  • 系统调用:提供内核服务的接口

中断(hardware interrupt)

  • 来自硬件设备的处理请求

异常(exception)

  • 非法指令或者其他原因导致当前指令执行失败后的处理请求

系统调用(system call)

  • 应用程序主动向操作系统发出的服务请求

三者比较

  • 源头

    • 中断:外设

    • 异常:应用程序意想不到的行为

    • 系统调用:应用程序请求操作系统提供服务

  • 响应方式

    • 中断:异步

    • 异常:同步

    • 系统调用:异步或同步

  • 处理机制

    • 中断:持续,对用户应用程序透明

    • 异常:杀死或重新执行

    • 系统调用:等待和持续

中断处理机制

硬件处理

  • 在CPU初始化时设置中断使能标志(中断使能之前CPU不会接受中断)

  • 设置中断标志

  • 依据中断向量调用响应的中断服务例程

软件

  • 现场保存

  • 中断服务处理(服务例程来做)

  • 清除中断标记(服务例程来做)

  • 现场恢复

中断嵌套

  • 硬件中断服务例程可以被新的中断打断

    • 不同硬件中断源可能在硬件中断处理时出现。这时根据优先级来先后执行

    • 硬件中断服务例程中也可以临时禁止中断请求

    • 中断请求会保持到CPU做出响应

  • 异常服务例程可能被中断打断:如处理异常时需要磁盘IO

  • 异常服务例程可能嵌套:如异常服务例程执行时缺页,引发一个新的异常

3.4 系统调用

标准C库的例子:调用标准函数printf()会触发系统调用write()

三种最常用的API:

  • Win32 API

  • POSIX API,用于Unix、Linux、MacOS等

  • Java API,用于Java虚拟机

系统调用的实现

每个系统调用都有一个系统调用编号。

系统调用和函数调用的不同

  • 系统调用:INT和IRET指令。有堆栈切换、特权级转换

  • 函数调用:CALL和RET指令。没有堆栈切换

开销

中断、异常和系统调用需要切换到内核态等一系列开销