4. RISC-V架构基础

将CFS调度器从x86移植到RISC-V,首先需要理解RISC-V的核心架构基础。与x86的复杂指令集和环形特权级(Ring 0~3)不同,RISC-V采用精简指令集和清晰的特权级分层。本章将重点剖析RISC-V的特权级架构、控制状态寄存器(CSR)以及中断控制器(PLIC/CLINT),为后续调度器上下文切换、中断处理与时钟管理的移植工作奠定基础。

4.1 特权级架构:M/S/U三层模型

RISC-V定义了三个特权级,从高到低依次为机器模式(M Mode)、监管模式(S Mode)和用户模式(U Mode)。这种分层设计直接决定了操作系统内核与用户程序的隔离方式,以及调度器在哪个层级运行。

特权级 编码 名称 主要用途 x86对应
M 11 机器模式 (Machine Mode) 固件、BootROM、安全监控、异常处理最底层 Ring -1 (SMM)
S 01 监管模式 (Supervisor Mode) 操作系统内核、页表管理、中断处理 Ring 0
U 00 用户模式 (User Mode) 应用程序运行 Ring 3
移植关键点:CFS调度器作为Linux内核的核心组件,运行在S Mode。这意味着调度器相关的上下文切换、时钟中断处理、任务优先级操作都必须在S Mode下通过特定的CSR和指令完成。M Mode通常由OpenSBI或RustSBI等固件管理,对调度器透明。

下图展示了RISC-V的三级特权模型及其与操作系统各组件的对应关系:

RISC-V 特权级架构与操作系统映射 RISC-V 特权级架构与操作系统映射 M Mode (机器模式) — 编码 11 OpenSBI / RustSBI 固件 | 异常向量入口 | 物理内存保护 S Mode (监管模式) — 编码 01 Linux 内核 | CFS 调度器 | 页表管理 (SATP) | 中断处理 (STVEC) 上下文切换 | 时钟事件 (STIMECMP) | 系统调用入口 (ECALL) U Mode (用户模式) — 编码 00 用户进程 | 应用程序 | 受限指令集 说明:CFS调度器运行在S Mode,通过ECALL陷入M Mode处理,通过MRET/ERET返回。

4.2 CSR寄存器:调度器操作的核心接口

控制状态寄存器(CSR)是RISC-V架构中用于配置和控制处理器状态的关键寄存器。与x86的MSR类似,CSR通过专用的csrrw、csrrs、csrrc等指令进行读写。对于CFS调度器移植,以下CSR至关重要:

  • sstatus (Supervisor Status Register):控制全局中断使能、用户模式访问权限等。调度器在切换进程时需要保存和恢复该寄存器。
  • stvec (Supervisor Trap Vector Base Address Register):设置S Mode异常/中断入口地址。调度器初始化时需将其指向handle_arch_irq或do_IRQ。
  • sscratch (Supervisor Scratch Register):通常用于保存每个CPU的核心数据结构指针(如cpu_struct),在中断入口快速获取当前CPU信息。
  • sepc (Supervisor Exception Program Counter):保存发生异常或中断时的返回地址。上下文切换时需保存到任务栈中。
  • scause (Supervisor Cause Register):记录异常或中断的原因。调度器据此判断是时钟中断、IPI还是其他事件。
  • stimecmp (Supervisor Timer Compare Register):S Mode下的定时器比较寄存器,用于触发时钟中断。这是CFS调度器获取时间片和进行负载均衡的关键硬件资源。
CSR名称 地址 读写权限 调度器相关用途
sstatus 0x100 SRW 保存/恢复中断状态,控制S Mode全局中断
stvec 0x105 SRW 设置中断向量表基址,指向调度器中断入口
sscratch 0x140 SRW 保存每CPU数据结构指针,加速中断处理
sepc 0x141 SRW 保存异常返回地址,上下文切换时使用
scause 0x142 SR 判断中断类型(时钟/外部/软件)
stimecmp 0x14D SRW 设置时钟中断触发阈值,驱动调度器tick
移植提示:在x86中,时钟中断通过LAPIC的定时器实现;在RISC-V中,S Mode的时钟中断由stimecmp与硬件时间计数器time(通过rdtime伪指令读取)共同控制。调度器需要将stimecmp设置为当前时间加上时间片长度,以产生下一个tick中断。

下图展示了调度器在中断处理流程中与关键CSR的交互关系:

调度器中断处理与CSR交互流程 调度器中断处理与CSR交互流程 1. 时钟中断触发 time ≥ stimecmp 2. 读取 scause 判断中断类型 3. 保存上下文 保存 sepc / sstatus 4. 更新 stimecmp 设置下一个tick 5. CFS调度决策 pick_next_task 6. 恢复上下文 恢复 sepc / sstatus 说明:整个流程在S Mode下完成,不涉及M Mode。stimecmp的更新是调度器tick驱动的核心。 x86对应:LAPIC定时器 → stimecmp;IDT → stvec;CR2/CR3 → SATP。

4.3 中断控制器:PLIC与CLINT

RISC-V的中断系统由两个主要控制器组成:CLINT(Core Local Interruptor)和PLIC(Platform Level Interrupt Controller)。它们分别负责核内中断和平台级外设中断的管理。

4.3.1 CLINT:核内中断与定时器

CLINT是每个RISC-V处理器核内部的中断控制器,主要负责:

  • 软件中断(MSIP/SIP):用于核间通信(IPI),在SMP系统中唤醒其他CPU核心执行调度。
  • 定时器中断(MTIP/STIP):基于mtime和mtimecmp(M Mode)或stimecmp(S Mode)产生。CFS调度器依赖STIP获取周期性时钟中断。

在Linux内核中,CLINT的驱动通常由M Mode固件(如OpenSBI)封装,S Mode通过ECALL调用固件接口来设置定时器或发送IPI。调度器移植时,需要关注riscv_ipi_send()和riscv_timer_set()等底层函数的实现。

4.3.2 PLIC:平台级外设中断

PLIC负责管理所有平台级外设中断(如UART、网卡、磁盘控制器),并将中断路由到指定的CPU核。其核心特性包括:

  • 中断优先级:支持多级优先级配置,高优先级中断可抢占低优先级。
  • 中断使能:每个中断源可独立使能/禁用。
  • 中断阈值:只有优先级高于阈值的中断才会被转发到CPU。
  • 中断声明与完成:CPU读取claim寄存器获取中断号,处理完成后写入complete寄存器。
特性 CLINT PLIC
管理范围 核内中断(定时器、软件中断) 平台级外设中断
中断类型 本地中断,每个核独立 全局中断,可路由到任意核
调度器相关性 高(时钟tick、IPI调度) 中(外设I/O完成唤醒进程)
访问方式 MMIO + CSR(stimecmp) MMIO(memory-mapped I/O)
x86对应 LAPIC(本地APIC) IOAPIC(I/O APIC)
移植注意:在x86中,调度器可以直接操作LAPIC的定时器寄存器。在RISC-V中,S Mode不能直接写mtimecmp(M Mode寄存器),必须通过SBI(Supervisor Binary Interface)调用固件服务。这意味着调度器中的时钟初始化代码需要从直接写硬件寄存器改为调用sbi_set_timer()函数。

下图展示了CLINT与PLIC在RISC-V系统中的位置及其与CPU核的关系:

RISC-V 中断控制器架构:CLINT 与 PLIC RISC-V 中断控制器架构:CLINT 与 PLIC CPU Core 0 S Mode CLINT (本地) stimecmp / MSIP CPU Core 1 S Mode CLINT (本地) stimecmp / MSIP PLIC 平台级中断控制器 优先级 / 使能 / 阈值 中断路由 外设中断源 UART | 网卡 | 磁盘控制器 | GPIO | 定时器 说明:CLINT负责核内中断(定时器、IPI),PLIC负责外设中断。调度器主要与CLINT交互。

4.4 中断委托机制:M Mode到S Mode

RISC-V支持将某些中断和异常从M Mode委托到S Mode处理,从而减少固件介入,提升性能。对于调度器而言,监管外部中断(SEIP)、监管定时器中断(STIP)和监管软件中断(SSIP)通常被委托到S Mode。

委托通过以下CSR配置:

  • medeleg (Machine Exception Delegation Register):将异常委托给S Mode。
  • mideleg (Machine Interrupt Delegation Register):将中断委托给S Mode。

在调度器移植中,需要确保固件(OpenSBI)正确配置了这些委托寄存器,使得时钟中断(STIP)和IPI(SSIP)能够直接到达S Mode的stvec入口,而不经过M Mode的额外处理。

调度器初始化检查清单:
  1. 确认mideleg中STIP和SSIP位已置位(通常由OpenSBI在启动时完成)。
  2. 设置stvec指向内核中断入口函数(如handle_arch_irq)。
  3. 在S Mode下使能全局中断(设置sstatus.SIE位)。
  4. 通过SBI调用设置初始stimecmp值,启动第一个时钟tick。