跳至主要內容

操作系统 - CPU管理

LincZero大约 6 分钟

操作系统 - CPU管理

启动流程

细节不同系统不同

物理存储

  • 磁盘
    • Bootloader (且在磁盘的第一个主引导扇区,且512MB。自己写系统的话首先就会先接触这个东西)
    • OS
  • 主板内存
    • BIOS

流程

  1. CPU执行BIOS指令:(1) 自检,(2) 加载 BootLoader 的内存
  2. CPU执行BootLoader指令:将OS和数据加载到内存
  3. CPU执行OS指令:(1) 开始操作系统初始化工作,(2) 创建各种应用,(3) 直至启动完成

操作系统、CPU、内存三者 关系:CPU不断从内存中取出操作系统/应用的指令执行

内核态 vs 用户态

vs

运行的东西

  • 软件
    • 用户态:应用程序、用户接口程序
    • 内核态:操作系统
  • 硬件

权限

  • 用户态:只能执行一部分机器指令 (mov、add、sub、push、ret ……)
  • 内核态:完全访问所有的硬件 (in、out、……等特权指令,能控制计算机、直接操作硬件)

定义

  • 用户态:应用程序在运行时CPU所属的状态,此时CPU为低级别,不能直接访问某些机器指令,或不能直接访问I/O
  • 内核态:操作系统在运行CPU所属的状态,此时CPU为高级别,可以运行任何指令(包括特权指令、直接访问I/O)

系统调用 - 用户态和内核态的切换

用户态**陷入(trap)**内核态

int add (int a, int b) {
    return a + b;
}

int main () {
    int c = add(1, 2);
    printf("%d", c);
    return 0;
}

调用链:(从上往下)

  1. 用户态部分
    1. 应用程序部分
      1. main()
    2. 用户接口程序 (库函数 glibc)
      1. printf()
      2. ……
      3. 用户态write()
  2. 内核态部分
    1. 内核态write()
    2. ……
    3. sys_write()
    4. out

这里面

  • 用户态调用用户态函数有栈
  • 内核态调用内核态函数,也有个内核栈
  • 问题在于 —— 如何从用户态**陷入(trap)**内核态。方法:
    • linux 32位操作系统:80中断
    • linux 64位操作系统:syscall 汇编指令
80软中断

write()会使用 ENTER_KERNEL,产生中断的指令,int $0x80

然后CPU会指向:系统调用中断服务程序 entry_INT80_32

……

系统调用号,用来查 系统调用表 (sys_call_table)

……

  1. 用户态的寄存器保存到 pt_regs 中
  2. 在 sys_call_table 中根据调用号找到对应的函数
  3. 执行函数实现,将返回值写入 pt_regs 的 ax 位置
  4. 通过指令 iret 根据 pt_regs 恢复用户态程序

64位过程差不多

syscall 汇编指令

特殊模块寄存器 (Model Specific Registers, MSR)

……

……

内核态回用户态

CPU 和 IO 设备交互

操作系统如何和外设交互,两种方式:

  • 汇编指令:in、out、mov 等 (通常都是特权指令)
  • 中断机制

方式一:汇编方式

分层架构与总线

image-20240228220139063

设备控制器

  • 设备控制器
    • 命令寄存器
    • 数据寄存器
    • 状态寄存器
    • 接口控制电路

image-20240228220150918

轮询 / 忙等待

image-20240228220621158

端口映射IO 和 内存映射IO

Out指令,如:out 0x03B0 EAX,这里的0x03B0是数据寄存器

  • CPU怎么知道将数据给哪个控制器的寄存器?

    • 方式一:端口映射IO。操作系统为每个控制器中的寄存器设置唯一的端口号

      方式二:内存映射IO。把IO设备的各个寄存器都编址,看成 “内存地址”

      两种方式都有,各位有优缺点。

      Windows可以这样查看:设备管理器 > 选中设备右击属性 > 资源 标签栏 > 资源类型,会看到 “内存范围” 和 “IO范围”,分别是内存映射IO和端口映射IO

方式二:中断机制 - 键盘原理

为什么需要中断机制:在上面的基础上,不忙等待,而是充分利用CPU时间,提高CPU利用率

例如CPU通知打印机打印,并去做其他事了,那CPU怎么知道打印机设备完成了?

键盘原理

  1. 键盘:输入字符 -> 键盘编码器
  2. 键盘编码器:知道按了什么,上报数据给键盘控制器
  3. 键盘控制器:解码保存数据到数据寄存器
  4. 中断控制器:由中断控制器发起中断请求 (InterruptRequest),将对应的键盘中断号发给CPU
    • Windows可以这样查看:设备管理器 > 选中键盘右击属性 > 资源 > IRQ (InterruptRequestQuest)
  5. CPU:维护一张中断向量表 (map<中断号, 中断服务程序内存基地址>),然后CPU找到对应的地址中,对应的是 “键盘中断服务程序”
    • 向量表在系统初始化时就有了
  6. 中断服务程序:具体流程
    • (1) 保存之前程序的状态
    • (2) IN EAX 0x03FA (将这个地址写到寄存器中)
    • (3) OUT 0x06B1 EAX (将寄存器内容输出到显卡)
    • (4) 恢复之前的程序状态

Q:中断后,CPU寄存器状态存在哪?rip、rsp、状态寄存器等,放在内存中

中断机制如何提高CPU利用率

(步骤序号根据打印机中断服务程序来)

  1. CPU指向应用程序1:该程序为打印字符串

  2. CPU指向内核态:处理out命令进行打印,而后打印机需要10ms来处理,但CPU不等待

  3. CPU指向应用程序2:处理其他东西 (打印机正在打印的同时)

    打印机完成后,直到发出中断信号给中断控制器,中断控制器再告诉CPU

    CPU去中断向量表去找,根据中断号找到 “中断服务程序内存基地址”

  4. CPU指向打印机中断服务程序:继续处理打印相关的事项,从步骤二开始循环流程,直到中断服务程序的所有事件处理完成

  5. CPU指向回应用程序1

DMA再加速 (DMA+中断)

前面还是比较慢,打印1个字符中断一次,原因在于CPU参与的移动比较多

可以使用 DMA (DIrect Memory Access,直接内存访问) 机制减少CPU开销。

DMA是什么:

DMA控制器是设备上配套的,如打印机有打印机的DMA控制器,磁盘有磁盘的DMA控制器。

DMA内容:

  • 数据源地址
  • 数据目的地址
  • 数据长度

新流程

  1. CPU设置DMA控制器,设置完后,其他事情都交给DMA控制器。CPU不再参与移动,DMA参与移动
  2. DMA负责持续从内存中读取字符,并交给打印机,将所有输出完成后,才再来通知CPU返回打印机中断服务程序

这种方式,不会再那么频繁去用CPU