ARM64 与 GICv3 的角色
RT 的主体在通用代码里,架构只需要满足几条前提。这一章回答:ARM64 为 RT 提供了什么、在 arch/arm64 里到底改了哪几行、GICv3 在 RT 内核里扮演什么角色,以及哪些延迟来自硬件和固件、RT 无能为力。
8.1RT 与架构的关系:一行 Kconfig 背后的前提
6.12 为 ARM64 打开 RT 的提交 d8fccd9ca5f9 "arm64: Allow to enable PREEMPT_RT." 只改了一行:
select ARCH_SUPPORTS_RT
整个 arch/arm64 目录里,字面出现 PREEMPT_RT 的地方只有 3 个文件、11 行(grep -rn PREEMPT_RT arch/arm64 核验):
| 文件 | 行 | 内容 |
|---|---|---|
arch/arm64/kernel/fpsimd.c | 228, 243, 1897, 1907, 1913, 1919, 1947, 1949 | FPSIMD 上下文保护与 kernel-mode NEON(8.5 节) |
arch/arm64/kernel/irq.c | 78 | do_softirq_own_stack() 在 RT 下不编译(8.6 节) |
arch/arm64/kernel/traps.c | 177–178 | oops 信息里打印 " PREEMPT_RT" |
改动这么少,是因为 RT 对架构的要求本来就不多,而 ARM64 在过去几年已经逐项满足(提交说明原话:"arm64 has all the required architecture related changes, that have been identified over time")。这些前提可以归为五类:
8.2异常模型与入口:中断从哪里进内核
ARM64 的异常级别:EL0 用户态、EL1 内核、EL2 hypervisor(KVM/VHE 下内核本身就跑在 EL2)、EL3 安全监控(TF-A 固件)。中断以异常的形式到达,CPU 根据来源 EL、栈指针选择、执行态跳到 VBAR_EL1 向量表里 16 个入口之一。每个入口 128 字节(.align 7),所以偏移可以直接从顺序算出:
gic_handle_irq(),但返回前的抢占处理不同。汇编部分 entry_handler 宏(entry.S:572)只做三件事:kernel_entry 保存寄存器形成 pt_regs,bl el1h_64_irq_handler 进 C,返回后 b ret_to_kernel / b ret_to_user。C 入口:
static __always_inline void __el1_irq(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
enter_from_kernel_mode(regs); /* RCU / lockdep / context tracking */
irq_enter_rcu(); /* preempt_count += HARDIRQ_OFFSET */
do_interrupt_handler(regs, handler); /* 切 IRQ 栈,调 gic_handle_irq */
irq_exit_rcu(); /* -= HARDIRQ_OFFSET;软中断(RT 下只唤醒) */
arm64_preempt_schedule_irq(); /* ← 内核态抢占点 */
exit_to_kernel_mode(regs);
}
static void noinstr el1_interrupt(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
write_sysreg(DAIF_PROCCTX_NOIRQ, daif); /* D、A 打开,I、F 保持屏蔽 */
if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && !interrupts_enabled(regs))
__el1_pnmi(regs, handler); /* 被打断时 IRQ 是关的 → 只能是 pseudo-NMI */
else
__el1_irq(regs, handler);
}
static void noinstr el0_interrupt(struct pt_regs *regs,
void (*handler)(struct pt_regs *))
{
enter_from_user_mode(regs);
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
if (regs->pc & BIT(55))
arm64_apply_bp_hardening(); /* Spectre-v2 缓解 */
irq_enter_rcu();
do_interrupt_handler(regs, handler);
irq_exit_rcu();
exit_to_user_mode(regs); /* ← 返回用户态前检查 TIF_NEED_RESCHED */
}
x86、RISC-V、LoongArch 用 kernel/entry/common.c 的通用 entry 框架;6.12 的 ARM64 还用自己的一套(arch/arm64/Kconfig 没有 select GENERIC_ENTRY)。所以 RT 相关的抢占逻辑要到 entry-common.c 里看,名字也不同:通用代码的 irqentry_exit_cond_resched() 在这里对应 arm64_preempt_schedule_irq()。
8.3抢占点:内核态的"三道门"与用户态的出口
内核态:arm64_preempt_schedule_irq()
static void __sched arm64_preempt_schedule_irq(void)
{
if (!need_irq_preemption()) /* RT 下 = IS_ENABLED(CONFIG_PREEMPTION) = 真 */
return;
/* thread_info::preempt_count 同时包含 count 和 need_resched */
if (READ_ONCE(current_thread_info()->preempt_count) != 0)
return; /* 门 1:count≠0 或 不需要调度 */
/* 用 PMR 屏蔽时,DAIF 里有位被置 = 刚处理过 NMI */
if (system_uses_irq_prio_masking() && read_sysreg(daif))
return; /* 门 2:NMI 上下文绝不调度 */
/* 抢占会把 PSTATE 副本留在栈上,cpufeature 启用前不允许 */
if (system_capabilities_finalized())
preempt_schedule_irq(); /* 门 3 */
}
RT 内核里 PREEMPT_DYNAMIC 不可用(kernel/Kconfig.preempt:98),所以 need_irq_preemption() 走 #else 分支,恒为真,没有 static key 开销。
"门 1"为什么一次比较就够:preempt_count 的打包
union {
u64 preempt_count; /* 0 => preemptible, <0 => bug */
struct {
#ifdef CONFIG_CPU_BIG_ENDIAN
u32 need_resched;
u32 count;
#else
u32 count;
u32 need_resched;
#endif
} preempt;
};
#define PREEMPT_NEED_RESCHED BIT(32)
#define PREEMPT_ENABLED (PREEMPT_NEED_RESCHED)
static inline void set_preempt_need_resched(void)
{
current_thread_info()->preempt.need_resched = 0; /* 取反存储:0 表示"需要" */
}
...
static inline bool should_resched(int preempt_offset) /* :77 */
{
u64 pc = READ_ONCE(current_thread_info()->preempt_count);
return pc == preempt_offset;
}
need_resched 取反存储,于是"抢占计数为 0 且需要调度"等价于整个 64 位字为 0。preempt_enable() 的检查、中断返回的检查都只需一次 64 位 load 加一次比较。RT 内核里抢占点比非 RT 密集得多(每次 spin_unlock()、local_bh_enable()、rcu_read_unlock() 都可能是),这个设计直接受益。
跨 CPU 唤醒时,远端只能设 TIF_NEED_RESCHED(thread_info 的 flags),再发 IPI;本 CPU 收到后由 scheduler_ipi() 调 preempt_fold_need_resched() 把它"折叠"进 preempt.need_resched(include/linux/sched.h:1976),中断返回时门 1 才能看到。
用户态出口:exit_to_user_mode()
static void do_notify_resume(struct pt_regs *regs, unsigned long thread_flags)
{
do {
local_irq_enable();
if (thread_flags & _TIF_NEED_RESCHED)
schedule();
...
if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
do_signal(regs);
...
if (thread_flags & _TIF_FOREIGN_FPSTATE)
fpsimd_restore_current_state();
local_irq_disable();
thread_flags = read_thread_flags();
} while (thread_flags & _TIF_WORK_MASK);
}
从用户态被打断的情况与 RT 无关——任何抢占模型下,返回用户态都是安全的调度点。RT 的价值体现在内核态:一个正在执行系统调用、持有 spinlock_t 的低优先级任务,在 RT 下也能在 __el1_irq 返回时被抢占。
- 用户态代码(FIFO 40 的任务)PSTATE.I = 0,正在 EL0 运行
- VBAR_EL1 + 0x480 → el0t_64_irqentry.S:531kernel_entry 0:保存 x0–x30、sp_el0、ELR/SPSR 到 pt_regs
- el0t_64_irq_handler() → __el0_irq_handler_common()entry-common.c:945 / 940
- el0_interrupt()entry-common.c:923enter_from_user_mode;DAIF_PROCCTX_NOIRQ
- do_interrupt_handler() → call_on_irq_stack()entry-common.c:304 · entry.S:876
- gic_handle_irq() → … → handle_fasteoi_irq()irq-gic-v3.c:964 · chip.c:687mask + primary 唤醒 irq/45(FIFO 50)
- try_to_wake_up() → wakeup_preempt() → resched_curr()50 > 40:本 CPU,set_tsk_need_resched + set_preempt_need_resched
- irq_exit_rcu()kernel/softirq.c:672
- exit_to_user_mode() → exit_to_user_mode_prepare()entry-common.c:176 / 161flags 含 _TIF_NEED_RESCHED
- do_notify_resume() → schedule()entry-common.c:130 · sched/core.c:6812
- __schedule(SM_NONE) → context_switch() → switch_to()sched/core.c:6613 / 5296切到 irq/45 线程,处理完它睡回去,再切回本任务
- …… irq/45 运行完毕,schedule() 返回 ……
- local_daif_mask() → ret_to_user → kernel_exit 0 → ERETentry.S:612
- 用户态代码继续
8.4上下文切换:cpu_switch_to 只存 13 个寄存器
RT 内核切换更频繁(每个线程化中断至少两次),切换本身的开销值得看一眼。通用层 context_switch() → switch_to() 宏 → ARM64 的 __switch_to()(arch/arm64/kernel/process.c:577)先处理"惰性"状态:
fpsimd_thread_switch(next):保存当前的 FPSIMD/SVE 状态(用户态或内核态 NEON),不立即恢复下一任务的用户态 FPSIMD,只设TIF_FOREIGN_FPSTATE,等它返回用户态时在do_notify_resume()里恢复。tls_thread_switch()(TPIDR_EL0)、entry_task_switch()、ssbs_thread_switch()、mte_thread_switch()等。
然后是真正的切换:
SYM_FUNC_START(cpu_switch_to)
save_and_disable_daif x11
mov x10, #THREAD_CPU_CONTEXT
add x8, x0, x10
mov x9, sp
stp x19, x20, [x8], #16 // store callee-saved registers
stp x21, x22, [x8], #16
stp x23, x24, [x8], #16
stp x25, x26, [x8], #16
stp x27, x28, [x8], #16
stp x29, x9, [x8], #16
str lr, [x8]
add x8, x1, x10
ldp x19, x20, [x8], #16 // restore callee-saved registers
...
ldr lr, [x8]
mov sp, x9
msr sp_el0, x1 // sp_el0 = current(task_struct 指针)
ptrauth_keys_install_kernel x1, x8, x9, x10
scs_save x0
scs_load_current
restore_irq x11
ret
SYM_FUNC_END(cpu_switch_to)
只有 callee-saved 的 x19–x29、sp、lr 需要保存——因为 cpu_switch_to 是一次普通函数调用,caller-saved 寄存器已由编译器处理。sp_el0 在内核态被复用为 current 指针,这也是 current 在 ARM64 上只需一条 mrs 的原因。页表切换(TTBR0)在更早的 switch_mm 里完成,内核线程之间切换(比如 irq 线程 ↔ ksoftirqd)不切换地址空间,开销更小。
8.5FPSIMD:ARM64 唯一需要 RT 专用分支的地方
ARM64 的 FPSIMD/SVE 寄存器是惰性管理的:CPU 上"当前装着谁的 FP 状态"记在 per-CPU 变量 fpsimd_last_state 里。修改这些 per-CPU 状态时必须保证不被打断去做别的 FP 操作。非 RT 下用 local_bh_disable() 同时达到两个目的——既挡软中断(软中断里可能用 kernel-mode NEON 做加密/校验和),也顺带关了抢占。
preempt_count += SOFTIRQ_DISABLE_OFFSET
→ 挡住软中断 ✓
→ 不可抢占 ✓(白嫖的副作用)
拿 per-CPU softirq_ctrl.lock(睡眠锁)
→ 挡住同 CPU 的软中断处理 ✓
→ 仍然可抢占 ✗
另一个任务可以上来改 FP 状态
* On RT kernels local_bh_disable() is not sufficient because it only
* serializes soft interrupt related sections via a local lock, but stays
* preemptible. Disabling preemption is the right choice here as bottom
* half processing is always in thread context on RT kernels so it
* implicitly prevents bottom half processing as well.
*/
static void get_cpu_fpsimd_context(void)
{
if (!IS_ENABLED(CONFIG_PREEMPT_RT))
local_bh_disable();
else
preempt_disable();
}
RT 下软中断永远在线程里跑,关抢占就自动挡住了它们,所以一个 preempt_disable() 同时满足两个条件。
kernel-mode NEON 在 6.12 中是可抢占的
容易误解的一点:kernel_neon_begin() / kernel_neon_end() 之间并不是不可抢占区。从 6.8 起 ARM64 引入 TIF_KERNEL_FPSTATE:kernel_neon_begin() 只在保存用户态 FP 状态的那一小段里持有 get_cpu_fpsimd_context(),然后设置该标志并放开;之后任务被抢占时,fpsimd_thread_switch() 会把它的内核态 NEON 寄存器存起来,切回时再恢复(fpsimd.c:1565)。
void kernel_neon_begin(void)
{
...
get_cpu_fpsimd_context();
if (test_thread_flag(TIF_KERNEL_FPSTATE)) {
/* 嵌套使用:只可能是软中断打断了任务级 kernel NEON —— RT 下不存在 */
BUG_ON(IS_ENABLED(CONFIG_PREEMPT_RT) || !in_serving_softirq());
fpsimd_save_kernel_state(current);
} else {
fpsimd_save_user_state();
/* RT:软中断在独立线程里,不会打断任务级 kernel NEON,
* 所以总是可以设标志,让内核态 NEON 状态随上下文切换保存 */
if (IS_ENABLED(CONFIG_PREEMPT_RT) || !in_serving_softirq())
set_thread_flag(TIF_KERNEL_FPSTATE);
}
fpsimd_flush_cpu_state();
put_cpu_fpsimd_context(); /* ← 返回时已经重新可抢占 */
}
非 RT 与 RT 的根本区别可以总结为两句:
非 RT:软中断可以打断任务(会发生 NEON 嵌套),但持 bh 的任务不可抢占
RT :软中断是线程,不会打断任务(NEON 嵌套不存在),但任务处处可抢占
所以 RT 下"嵌套 kernel NEON"被 BUG_ON 直接判为不可能,kernel_neon_end()(fpsimd.c:1939)也只需清标志。may_use_simd() 在硬中断、关中断、NMI 中返回假(arch/arm64/include/asm/simd.h:24),这些上下文里只能走标量回退路径。
8.6IRQ 栈与软中断栈
ARM64 每个 CPU 有一个独立 IRQ 栈(IRQ_STACK_SIZE = THREAD_SIZE,无 KASAN 时 16 KiB,arch/arm64/include/asm/memory.h:144)。do_interrupt_handler() 发现自己还在任务栈上时,用 call_on_irq_stack() 切过去。非 RT 下软中断也借这个栈:
#ifndef CONFIG_PREEMPT_RT
static void ____do_softirq(struct pt_regs *regs)
{
__do_softirq();
}
void do_softirq_own_stack(void)
{
call_on_irq_stack(NULL, ____do_softirq);
}
#endif
RT 下软中断在 ksoftirqd 或 irq 线程里、用线程自己的内核栈执行,这个函数没有调用者,于是整段不编译(引入它的提交是 8eb858c44b98 "arm64: run softirqs on the per-CPU IRQ stack")。结果是 RT 下 IRQ 栈上只会出现真正的硬中断帧,栈深度更可预测。
8.7GICv3 在 RT 内核里:优先级、PMR 与"故意不嵌套"
Linux 只用三个优先级
GICv3 支持最多 256 级优先级和抢占嵌套(ICC_BPR1_EL1 分组、ICC_RPR_EL1 运行优先级)。Linux 一个都不用来做设备之间的分级:
#define GICV3_PRIO_UNMASKED 0xe0 /* PMR 设为它:IRQ 和 NMI 都放行 */
#define GICV3_PRIO_IRQ 0xc0 /* 所有普通中断统一用这个 */
#define GICV3_PRIO_NMI 0x80 /* pseudo-NMI 专用 */
#define GICV3_PRIO_PSR_I_SET (1 << 4)
为什么故意不用硬件嵌套?这和 RT 的理念完全一致:
- 栈深不可预测:嵌套中断叠在同一个固定大小的 IRQ 栈上。
- 最坏延迟不可分析:低优先级中断的延迟 = 所有更高优先级中断的总和,且组合爆炸。
- 第二套调度器:GIC 优先级是硬件里的一套静态优先级,与调度器的任务优先级无法统一。RT 的选择正好相反——把中断变成线程,让唯一的调度器来排序。中断线程化之后,"网卡比串口重要"用
chrt表达,而不是用GICD_IPRIORITYR。
pseudo-NMI:PMR 代替 PSTATE.I
ARM64 没有架构级 NMI(ARMv8.8 的 FEAT_NMI 在 6.12 中尚未被 Linux 用于此目的)。打开 CONFIG_ARM64_PSEUDO_NMI 并在命令行加 irqchip.gicv3_pseudo_nmi=1(arch/arm64/kernel/cpufeature.c:1097)后,local_irq_disable() 不再置 PSTATE.I,而是写 ICC_PMR_EL1:
static __always_inline void __daif_local_irq_disable(void)
{
barrier();
asm volatile("msr daifset, #3"); /* 置 I 和 F */
barrier();
}
static __always_inline void __pmr_local_irq_disable(void) /* :59 */
{
...
barrier();
write_sysreg_s(GIC_PRIO_IRQOFF, SYS_ICC_PMR_EL1); /* 0xc0:只放行 NMI */
barrier();
}
代价与 RT 相关的有三点:
- 开关中断变贵:写 PMR 后在某些实现上需要
pmr_sync()(DSB),比msr daifset慢。RT 内核里显式关中断的地方少了很多(spin_lock_irqsave不再关中断),这一代价被部分抵消。 - 中断入口更复杂:
kernel_entry在 IRQ 入口把 PMR 存进pt_regs,并改成GIC_PRIO_IRQON | GIC_PRIO_PSR_I_SET(entry.S:319);__gic_handle_irq_from_irqson()读 IAR 后查ICC_RPR_EL1判断是不是 NMI(irq-gic-v3.c:904),再决定走 NMI 还是 IRQ 路径。 - NMI 上下文不可调度:
arm64_preempt_schedule_irq()的"门 2"就是为它准备的。NMI handler(perf 采样、hardlockup 检测)的执行时间是 RT 内核里另一个连关中断都挡不住的延迟来源;对延迟极敏感的系统,通常不打开 pseudo-NMI,或者只在调试时打开。
GIC 中断状态机
stateDiagram-v2
[*] --> Inactive
Inactive --> Pending: 设备触发 / 写 SGI1R / ITS 翻译
Pending --> Active: CPU 读 ICC_IAR1_EL1 (ack)
Active --> ActivePending: 电平仍有效 或 边沿再次到来
ActivePending --> Pending: deactivate
Active --> Inactive: deactivate (EOImode0 写 EOIR, EOImode1 写 DIR)
Pending --> Inactive: 设备撤销电平
note right of Active
EOImode1 下写 ICC_EOIR1_EL1 只做 priority drop,
状态仍是 Active,直到写 ICC_DIR_EL1
end note
8.8不会被线程化的 ARM64 中断
IPI 是 SGI,用 request_percpu_irq 注册
enum ipi_msg_type {
IPI_RESCHEDULE,
IPI_CALL_FUNC,
IPI_CPU_STOP,
IPI_CPU_STOP_NMI,
IPI_TIMER,
IPI_IRQ_WORK,
...
};
这些 IPI 在 set_smp_ipi_range() 里用 request_percpu_irq()(或可作为 NMI 的用 request_percpu_nmi())注册(smp.c:1083)。SGI 走 handle_percpu_devid_irq,带 IRQ_NOTHREAD,永远在硬中断里执行。其中对 RT 最关键的是 IPI_RESCHEDULE:远端唤醒一个 RT 任务时,arch_smp_send_reschedule()(smp.c:1099)→ gic_ipi_send_mask() → gic_send_sgi() 写 ICC_SGI1R_EL1(irq-gic-v3.c:1413),目标 CPU 进入中断后 do_handle_IPI() 调 scheduler_ipi(),在中断返回时完成抢占。
sequenceDiagram participant C0 as CPU0 (唤醒者) participant GIC as GICv3 Redistributor participant C3 as CPU3 (运行 FIFO 10 任务) C0->>C0: try_to_wake_up(RT 任务, 亲和 CPU3) C0->>C0: ttwu_queue / activate, resched_curr(rq3) C0->>C0: 设 TIF_NEED_RESCHED, smp_send_reschedule(3) C0->>GIC: msr ICC_SGI1R_EL1 (SGI IPI_RESCHEDULE, 目标 CPU3) GIC->>C3: SGI 送达 C3->>C3: el1h_64_irq 或 el0t_64_irq C3->>C3: gic_handle_irq, handle_percpu_devid_irq C3->>C3: ipi_handler, do_handle_IPI, scheduler_ipi Note over C3: preempt_fold_need_resched 写 preempt.need_resched=0 C3->>C3: 中断返回: arm64_preempt_schedule_irq 或 do_notify_resume C3->>C3: __schedule, cpu_switch_to 到 RT 任务
preempt_enable();RT 的意义就是让这种区间尽可能短。arch timer 是 PPI
ARM 通用定时器(CNTP/CNTV)通过 PPI 送达(常见是虚拟定时器 INTID 27、非安全物理定时器 30,由 DT/ACPI 描述),驱动用 request_percpu_irq(ppi, arch_timer_handler_virt, ...) 注册(drivers/clocksource/arm_arch_timer.c:1251)。它的 handler 调用 evt->event_handler,高精度模式下就是 hrtimer_interrupt()——在 RT 下只直接执行标了 HRTIMER_MODE_*_HARD 的定时器(包括 RT 任务的 clock_nanosleep 唤醒和 tick),其余推给 HRTIMER_SOFTIRQ(第 6 章)。cyclictest 测到的"中断延迟"正是这条 PPI 路径。
ITS / LPI 与 MSI
PCIe 设备的 MSI/MSI-X 经 ITS 翻译为 LPI(INTID ≥ 8192),走 handle_fasteoi_irq,可以线程化。LPI 的 mask/unmask 不是写 GICD_ICENABLER,而是改内存中的 LPI 配置表并通过 ITS 命令(INV)使其生效,同样有不可忽略的成本。多队列网卡每个队列一个 MSI-X 向量、一个 irq/N-xxx 线程,可以分别绑核、分别设优先级——这是在 RT 系统里隔离网络负载的基本手段。ITS 驱动内部的锁都是 raw_spinlock_t(drivers/irqchip/irq-gic-v3-its.c:100 起),因为它们在硬中断和 irqchip 回调里被使用。
KVM
ARM64 KVM 的虚拟定时器仿真必须及时把中断注入 vCPU,因此三处 hrtimer 都显式用 HRTIMER_MODE_ABS_HARD(arch/arm64/kvm/arch_timer.c:215, 993, 1021);vGIC 的 irq_lock、ap_list_lock 是 raw_spinlock_t(include/kvm/arm_vgic.h:119, 336)。没有 _HARD,RT 下这些定时器会被推到软中断线程,客户机时钟中断的抖动就取决于宿主机 ksoftirqd 的调度。
8.9RT 管不了的 ARM64 硬件与固件延迟
RT 能控制的只是"Linux 内核什么时候愿意让出 CPU"。下面这些发生在内核视野之外或硬件内部,PREEMPT_RT 不改变它们:
| 来源 | 机制 | 缓解手段 |
|---|---|---|
| EL3 固件(TF-A) | PSCI 调用(CPU_ON/OFF、CPU_SUSPEND)、SMC 服务、SoC 私有 SMC;EL3 运行时 Linux 完全停顿 | 审查固件;避免运行期频繁 SMC |
| SDEI | 固件直接向 Linux 投递事件(类似 NMI),绕过 GIC 屏蔽 | RT 系统上不启用或确认用途 |
| cpuidle 深睡退出 | WFI 很快,但 PSCI 深度状态(核心/簇掉电)的退出延迟可达数十到上百微秒量级 | 限制 idle 深度(/dev/cpu_dma_latency、cpuidle.off=1)。6.12 的 cpuidle-psci 在 RT 下把簇级(domain)idle 状态限制在系统休眠时才用(drivers/cpuidle/cpuidle-psci.c:243–248、cpuidle-psci-domain.c:70–76) |
| DVFS | 调频调压期间的切换窗口;低频下所有路径整体变慢 | performance governor,固定频率 |
| TLB / 缓存维护广播 | TLBI ...IS + DSB ISH 需要所有核响应;munmap、页迁移、KSM 等都会触发 | RT 进程预先 mlockall,避免运行期大规模映射变更;隔离核 |
| 缓存与总线争用 | 共享 L3/DSU、内存控制器、其他 master 的 DMA | 核隔离、缓存划分(MPAM,取决于 SoC 与内核支持) |
| 虚拟化 | vCPU 被宿主机抢占、stage-2 缺页 | 宿主机也跑 RT、vCPU 钉核 |
rtla timerlat 报告的"IRQ latency"包含从定时器到期到 arch timer handler 开始执行的全部时间:GIC 送达、关中断窗口、EL3/SDEI 打断、idle 退出。如果这个数字已经很大,问题多半在硬件、固件或残余的关中断区;如果 IRQ latency 小而"Thread latency"大,问题在调度路径——这才是 RT 补丁负责的部分。详见 第 10 章。
8.10本章小结
- ARM64 为 RT 只改了 3 个文件 11 行;真正的前提是:中断返回内核态时有抢占点(
arm64_preempt_schedule_irq)、返回用户态时检查重调度、上下文切换廉价、FPSIMD 的 per-CPU 状态不再依赖local_bh_disable关抢占、软中断不再借用 IRQ 栈。 preempt_count与need_resched打包成一个 64 位字,让 RT 下密集的抢占检查只需一次比较。- GICv3 在 Linux 中只用三个优先级、故意不嵌套;RT 把中断间的分级交给调度器(线程优先级),而不是 GIC。
- IPI(SGI)、arch timer(PPI)、pseudo-NMI 始终在硬中断/NMI 上下文执行,构成 RT 的延迟下限;固件、idle、DVFS、TLB 广播等硬件因素在 RT 视野之外,需要系统层面配置。