02-微机原理与操作系统硬件基础-原理与本质.md 9.9 KB


tags: [source-summary] type: source source: "微机原理与操作系统硬件基础-原理与本质" author: "AI助手" date: 2026-09-19

created: 2026-09-19

微机原理与操作系统硬件基础:原理与本质

读完本文,你能理解

  1. CPU寄存器和上下文切换的关系
  2. 三总线为什么这样分工
  3. 中断控制器(GIC)怎么管理优先级
  4. MMU怎么实现虚拟地址翻译

1. 寄存器和上下文切换的关系

锚点:CPU寄存器 = CPU内部的高速存储。上下文切换 = 把当前任务的寄存器值保存到栈 + 从栈恢复下一个任务的寄存器值。SP和PC是切换的关键。

ARM Cortex-M的寄存器

寄存器 用途 为什么上下文切换要保存它
R0-R3 函数参数/返回值 调用者保存(caller-saved),ISR自动保存
R4-R11 通用变量 被调用者保存(callee-saved),上下文切换手动保存
SP(R13) 栈指针 切换的核心——保存到TCB,恢复时从TCB取出
LR(R14) 链接寄存器(返回地址) 中断返回时自动恢复
PC(R15) 程序计数器 中断返回时从栈恢复
xPSR 程序状态(N/Z/C/V标志) 保存运算状态,否则切换后标志位丢失

为什么只需要手动保存R4-R11

  • R0-R3/R12:进入PendSV中断时,硬件自动压入栈(中断响应机制)
  • LR/PC/xPSR:同上,硬件自动保存
  • R4-R11是callee-saved寄存器:编译器假设它们在整个函数调用链中保持不变,所以只有上下文切换代码需要保存/恢复

每种CPU模式有自己的寄存器

ARM Cortex-M有多种CPU模式(Handler mode、Thread mode),不同模式使用不同的SP:

  • MSP(Main Stack Pointer):ISR使用,系统启动时使用
  • PSP(Process Stack Pointer):任务使用,每个任务的栈通过PSP访问

    graph TD
    A[任务运行] --> B[中断发生]
    B --> C[切换到MSP<br/>保存任务寄存器到任务栈]
    C --> D[PendSV处理]
    D --> E[切换PSP到新任务栈]
    E --> F[恢复新任务寄存器]
    F --> G[新任务运行]
    

易错点

  • ❌ 上下文切换保存所有寄存器:只需要R4-R11(callee-saved),其余由硬件/ISR保存
  • ❌ 所有任务共用一个栈:每个任务有自己的栈,通过PSP指针访问
  • ❌ SP在任务运行时不变:SP会随函数调用/局部变量变化,但切换时保存的是调用前的值
  • ❌ PC可以直接修改:通过修改栈中保存的PC值来改变任务的恢复执行点

来源:ARM架构01-从零认识ARM


2. 三总线为什么这样分

锚点:三总线 = 地址总线(传地址)+ 数据总线(传数据)+ 控制总线(传控制信号)。分开是为了让多个设备能共享同一组线路而不冲突。

三总线各自的职责

总线 传什么 类比 宽度决定什么
地址总线 内存/设备地址 信封上的地址 能寻址多大空间(32位→4GB)
数据总线 实际数据 信封里的信 一次传多少数据(32位→一次4字节)
控制总线 读/写/中断信号 邮递员的手势 读操作、写操作、中断请求等

MMIO(内存映射I/O)

CPU用同一套地址总线访问内存和外设,但地址空间分成了两块:

  • 内存地址空间:0x00000000 - 0x7FFFFFFF → 访问RAM
  • 外设地址空间:0x40000000+ → 访问GPIO、UART、定时器等

这就是为什么驱动能控制硬件:向某个"外设地址"写入数据,实际是向硬件寄存器写入控制命令。*(volatile uint32_t *)0x40020014 = 0x01; 就是在操作GPIO的ODR寄存器。

为什么volatile关键字对驱动至关重要

  • volatile告诉编译器:这个变量的值可能被硬件改变,每次访问都要从内存重新读取
  • 如果不用volatile,编译器可能把值缓存到寄存器中,永远不读硬件的实际状态

易错点

  • ❌ 三总线是物理上独立的三组线:逻辑上分开,物理上可能复用(如ARM的AHB总线)
  • ❌ 地址总线宽度等于数据总线宽度:不一定,ARM Cortex-M是32位地址+32位数据,但8位MCU可能是16位地址+8位数据
  • ❌ 外设地址和内存地址完全隔离:不是,都是同一套地址线,只是地址范围划分不同
  • ❌ 驱动直接操作硬件寄存器:是通过MMIO,即向特定地址写入/读取数据

来源:组成原理/计算机系统概述


3. 中断控制器(GIC)怎么管理优先级

锚点:GIC(Generic Interrupt Controller)是ARM的中断管家——接收所有中断源的请求,按优先级排队,决定哪个中断能"插队"到哪个CPU。

GIC-400的中断分类

类型 全称 数量 来源 特点
SGI Software Generated Interrupt 16个 软件触发 核间通信用
PPI Private Peripheral Interrupt 16个 每个CPU私有 如本地定时器
SPI Shared Peripheral Interrupt 最多988个 外部设备共享 如UART、GPIO中断

优先级抢占机制

  • 每个中断源有独立的优先级编号(0最高,255最低)
  • 高优先级中断可以抢占低优先级中断——正在处理低优先级ISR时,高优先级中断到来,CPU暂停当前ISR,先处理高优先级
  • 处理完高优先级后,再回到低优先级ISR继续

    sequenceDiagram
    participant CPU as CPU
    participant ISR_L as 低优先级ISR
    participant ISR_H as 高优先级ISR
    
    CPU->>ISR_L: 开始执行低优先级ISR
    Note over ISR_L: 执行到一半...
    ISR_H->>CPU: 高优先级中断到来
    CPU->>ISR_H: 暂停低优先级,先处理高优先级
    ISR_H-->>CPU: 高优先级处理完毕
    CPU->>ISR_L: 恢复低优先级ISR继续执行
    ISR_L-->>CPU: 低优先级处理完毕
    

中断嵌套 vs 中断屏蔽

  • 中断嵌套:高优先级抢占低优先级 → 提高系统响应能力
  • 中断屏蔽:临时屏蔽所有/部分中断 → 保护临界区(共享数据不被破坏)

FreeRTOS用两种方式保护临界区:

  1. PRIMASK:完全关中断(最粗暴)
  2. BASEPRI:只关低于阈值的中断(精细控制,高优先级硬件紧急中断仍可响应)

易错点

  • ❌ 所有中断同时到达时全部处理:不是,按优先级排队,一次只处理一个
  • ❌ 中断优先级不能改变:可以通过GIC寄存器动态修改
  • ❌ ISR可以随意使用所有API:ISR中不能调用阻塞API,不能分配内存
  • ❌ 中断嵌套没有开销:有,每次嵌套需要额外保存/恢复上下文

来源:ARM架构01-从零认识ARM


4. MMU怎么实现虚拟地址翻译

锚点:MMU(Memory Management Unit)是CPU的"翻译官"——把进程使用的虚拟地址翻译成物理内存的真实地址。有了它,每个进程都以为自己独占整块内存。

为什么需要虚拟地址

没有MMU的世界:程序直接访问物理地址 → 进程A访问0x20000000,进程B也访问0x20000000 → 数据互相覆盖

有了MMU:进程A的0x20000000可能映射到物理0x30000000,进程B的0x20000000可能映射到物理0x50000000 → 互不干扰

两级页表结构

ARM Cortex-M使用两级页表(为了节省内存):

graph LR
    VA["虚拟地址<br/>32位"] --> L1["一级页表<br/>[31:20] 12位<br/>索引4096个条目"]
    VA --> L2["二级页表<br/>[19:12] 8位<br/>索引256个条目"]
    VA --> Offset["页内偏移<br/>[11:0] 12位<br/>4KB页大小"]
    L1 --> PT["页表基地址<br/>TTBR0/TTBR1"]
    L2 --> PA["物理地址"]
    Offset --> PA
  • 一级页表(L1):每个条目指向一个二级页表,或标记为"段映射"直接映射1MB
  • 二级页表(L2):每个条目映射一个4KB的页
  • TTBR0:进程切换时加载新进程的L1页表地址 → 每个进程有自己的地址空间

TLB缓存

页表翻译需要多次内存访问(L1→L2→物理页),太慢。TLB(Translation Lookaside Buffer)缓存最近使用的翻译结果:

  • TLB命中:直接用缓存的物理地址(1个时钟周期)
  • TLB未命中:查页表(可能几十个时钟周期),然后存入TLB

用MMU做进程隔离和COW

  • 进程隔离:每个进程的页表不同,A进程的虚拟地址映射到物理页X,B进程的虚拟地址映射到物理页Y,互不干扰
  • COW(写时复制):fork时父子进程的页表映射到同一批物理页,标记为只读;写入时触发page fault→内核复制该页→更新页表

FreeRTOS为什么没有MMU

FreeRTOS面向没有MMU的MCU(如STM32),所有任务运行在同一个地址空间。这简化了设计,但意味着:

  • 一个任务的野指针可能破坏其他任务的数据
  • 没有进程隔离
  • 没有COW、虚拟内存等高级特性

易错点

  • ❌ 虚拟地址就是物理地址:不是,经过MMU翻译后才是
  • ❌ 每次访问内存都要查两级页表:有TLB缓存,大部分命中
  • ❌ FreeRTOS也能用MMU:FreeRTOS设计目标是无MMU的MCU,用MMU的是Linux
  • ❌ 页表常驻内存不需要管理:内核需要在进程创建/销毁时分配/释放页表

来源:Linux内核04-进程调度与中断管理、ARM架构01


总结:操作系统需要的硬件支持

硬件 操作系统用它做什么 没有它会怎样
寄存器 上下文切换(保存/恢复执行现场) 无法实现多任务
三总线 CPU访问内存和外设 无法和外界通信
时钟 调度器的心跳、定时器 无法做时间片轮转
中断 事件驱动、任务唤醒 只能轮询,效率极低
中断控制器 优先级管理、中断嵌套 多设备中断冲突
MMU 进程隔离、虚拟内存、COW 无进程保护,一个bug影响全局