x86_64 Linux:cpu_current_top_of_stack 与各类内核入口的栈顶约定

x86_64 Linux:cpu_current_top_of_stack 与各类内核入口的栈顶约定

本文档说明:系统调用 (syscall)、经 IDT 的用户态异常/中断(含 INT $0x80、外设 IRQ)、以及 sync_regs 在「当前线程内核栈顶 / pt_regs 落点」上是否共用同一套内核逻辑;并明确与 TSS.RSP0 / entry trampoline 的区别。
源码以 Linux 主线为参照,链接指向 torvalds/linux 上对应文件(行号会随版本漂移,以链接内行为准)。


1. 权威定义:线程栈上的 pt_regstask_top_of_stack

每个可运行任务有独立线程内核栈struct pt_regs 放在该栈的固定布局位置。相关宏在 arch/x86/include/asm/processor.h

这是TSS.sp0 无关的、纯软件对任务栈的约定。


2. 运行期缓存:cpu_current_top_of_stackcurrent_top_of_stack()

// 概念:per-CPU 上缓存「当前正在本 CPU 上跑的任务」的 task_top_of_stack
// 声明见 processor.h:cpu_current_top_of_stack

因此:current_top_of_stack() 的数值与 task_top_of_stack(current) 一致(在有效任务、正常切换路径下)。


3. syscall 快速路径:直接用 cpu_current_top_of_stackRSP

entry_SYSCALL_64swapgs、切内核 CR3 等之后,用:

movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp

%rsp 设到当前任务在 §1 中约定的那一格栈顶上,再向下压栈构造 pt_regs

这里不调用 current_top_of_stack() 的 C 内联函数,但读的是同一 per-CPU 变量、同一套 task_top_of_stack 语义


4. 经 idtentry + error_entry + sync_regscurrent_top_of_stack() 决定目的 pt_regs

sync_regs()

struct pt_regs *regs = (struct pt_regs *)current_top_of_stack() - 1;

current_top_of_stack() == (unsigned long)(task_pt_regs(current) + 1),故 current_top_of_stack() - 1 即为 task_pt_regs(current),也就是当前任务线程栈上那本 struct pt_regs 的指针

若入口帧当前在 entry trampoline 栈或 IST 栈上(eregs 指针与 regs 不同),则 *regs = *eregs 把帧拷到线程栈的标准槽位。

因此:异常 / INT $0x80 / 外设 IRQ,只要从用户态走上这条路径并最终进入 sync_regs目标栈顶语义与 syscall 一致:都是 task_pt_regs(current) 对应区域。

下文「同一套 top of stack 语义」指:线程内核栈布局里那一份 task_pt_regs(current) / task_top_of_stack(current) 约定,由 cpu_current_top_of_stack / current_top_of_stack() 表达;不包含 TSS.RSP0 指向的 entry trampoline 栈。


5. 与博文「三类事件」及各入口的对应关系

博文里区分 syscall经 IDT 的软/硬中断与异常 等;此处从栈顶约定把它们与 cpu_current_top_of_stack / sync_regs / TSS.RSP0 对齐说明。下列「是否用到同一套语义」均相对上文定义而言。

入口类型 是否用到「同一套 top of stack 语义」 说明
syscall 汇编 movq PER_CPU_VAR(cpu_current_top_of_stack), %rspentry_64.S entry_SYSCALL_64);与上下文切换里 raw_cpu_write(cpu_current_top_of_stack, task_top_of_stack(next))process_64.c)写入的值同源。不经 IDT,不使用 TSS.RSP0
用户态 IDTINT $0x80、外设 IRQ、多数 IST = 0 的异常) ✅(在走到 sync_regs 并完成对齐时) 硬件先从用户态切到 TSS.RSP0(entry trampoline 栈);idtentryerror_entrysync_regsregs = (struct pt_regs *)current_top_of_stack() - 1,即 task_pt_regs(current)。入口帧若在 trampoline 上与 regs 不一致,则 *regs = *eregs 拷到线程栈标准槽位(traps.c sync_regs)。
TSS.RSP0 / entry trampoline ❌(另一套 load_sp0((unsigned long)(cpu_entry_stack(cpu) + 1))cpu/common.c cpu_init / native_load_sp0)只表示** ring0 的第一段栈指针不是** task_top_of_stack(current),也不等同于 task_pt_regs 所在进程栈。
IST 异常 硬件:不同栈;软件对齐目标: task_pt_regs(current) 向量带 IST≠0 时,CPU 先把栈切到 IST 栈,与当前线程内核栈不同。sync_regs 注释写明可处理 IST 或 trampoline 上的 handler 帧:若 eregsregs,仍把 eregs 拷到 current_top_of_stack() - 1 所指 regs(线程栈上的标准 pt_regs),故软件侧「目的槽」仍是同一套 top-of-stack 语义;差别在硬件先落哪里
内核态被中断 / 异常 ⚠️ 不能与用户态 IDT 路径一概而论 error_entry 在内核态入口可能走用户态那条 jmp sync_regs(例如 paranoid、或帧本来就在内核栈上);是否再迁到「线程栈上的标准 task_pt_regs」取决于向量与 USER / PARANOID 等宏分支。不能与「用户态 IRQ / INT $0x80 + sync_regs」等同表述为同一套路径。

6. 与 TSS.RSP0 / entry trampoline 的并存关系(细读)


7. 术语与宏对照(速查)

概念 含义
task_pt_regs(task) 任务内核栈布局中 struct pt_regs*processor.h
task_top_of_stack(task) (unsigned long)(task_pt_regs(task) + 1),与 pt_regs 相邻的栈顶一端
cpu_current_top_of_stack(per-CPU) 运行中缓存 task_top_of_stack(current)
current_top_of_stack() 读该 per-CPU 变量;不是TSS.sp0
syscall 汇编加载 cpu_current_top_of_stack → %rsp,再压栈构帧
sync_regs() (struct pt_regs *)current_top_of_stack() - 1 作为线程栈上的目标 regs

8. 例外与边界(避免过度概括)


参考锚点(便于在树里 grep

cpu_current_top_of_stackcurrent_top_of_stacktask_top_of_stacktask_pt_regssync_regsentry_SYSCALL_64native_load_sp0 / load_sp0cpu_entry_stackidtentryerror_entryparanoideregs

My Github Page: https://github.com/liweinan

B站视频: https://space.bilibili.com/21947620

Powered by Jekyll and Theme by solid

If you have any question want to ask or find bugs regarding with my blog posts, please report it here:
https://github.com/liweinan/liweinan.github.io/issues