kernel/entry 用户态/内核态入口机制与原理详解
源码路径:rk3588/kernel-6.1/kernel/entry/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(4×Cortex-A76 + 4×Cortex-A55 big.LITTLE,ARM64)
该目录实现 Linux 内核 Generic Entry(通用入口/出口) 框架,统一管理从用户态进入内核(系统调用、中断、异常)以及返回用户态时的公共逻辑:RCU/上下文跟踪、lockdep、tracing、ptrace、seccomp、信号投递、调度等。
RK3588 说明:ARM64 未启用 CONFIG_GENERIC_ENTRY,而是在 arch/arm64/kernel/entry-common.c 和 syscall.c 中实现了语义等价的架构专用入口路径。本目录代码定义了通用框架的设计规范;理解本目录即理解 ARM64 入口逻辑的”蓝图”。
目录
一、源码目录结构
1.1 编译依赖(Makefile)
1 2 3 4 5 6 7
| KASAN_SANITIZE := n UBSAN_SANITIZE := n KCOV_INSTRUMENT := n
obj-$(CONFIG_GENERIC_ENTRY) += common.o syscall_user_dispatch.o obj-$(CONFIG_KVM_XFER_TO_GUEST_WORK) += kvm.o
|
| 配置项 |
说明 |
CONFIG_GENERIC_ENTRY |
启用通用入口框架(x86/s390/loongarch 等) |
CONFIG_KVM_XFER_TO_GUEST_WORK |
KVM 进入 guest 前的 pending work 处理 |
启用 GENERIC_ENTRY 的架构:x86、 s390、 loongarch 等(arch/*/Kconfig 中 select GENERIC_ENTRY)。
ARM64/RK3588:不 select GENERIC_ENTRY,使用 arch/arm64/kernel/entry-common.c。
1.2 源文件
| 文件 |
规模 |
功能 |
common.c |
~490 行 |
通用入口/出口核心:syscall、irq、NMI |
syscall_user_dispatch.c |
~109 行 |
Syscall User Dispatch(SUD)机制 |
kvm.c |
~49 行 |
KVM 进入 guest 前 pending work |
common.h |
~7 行 |
内部头文件 |
include/linux/entry-common.h |
~468 行 |
公共 API 与架构 hook 定义 |
二、整体架构
2.1 入口/出口分类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 用户态 (EL0) │ ┌───────────────┼───────────────┐ │ │ │ 系统调用 SVC 中断/异常 页错误等 │ │ │ ▼ ▼ ▼ enter_from_user irqentry_enter enter_from_user │ │ │ ▼ ▼ ▼ syscall 处理 IRQ handler fault 处理 │ │ │ ▼ ▼ ▼ exit_to_user irqentry_exit exit_to_user │ │ │ └───────────────┴───────────────┘ │ 用户态 (EL0)
|
2.2 每次入口/出口的统一职责
| 阶段 |
职责 |
| 进入内核 |
lockdep 标记 IRQ 关闭、RCU/context tracking 切换、tracing、kmsan |
| 内核处理 |
实际 syscall/IRQ/fault 逻辑 |
| 退出内核 |
处理 pending work(信号、调度、uprobe、livepatch)、RCU 切换回用户态、lockdep/tracing 恢复 |
2.3 设计原则
- noinstr 代码段:入口/出口关键路径标记
noinstr,避免 ftrace/KASAN 插桩导致递归
- instrumentation_begin/end:在安全点之后才允许插桩
- 架构 hook:
arch_enter_from_user_mode、arch_exit_to_user_mode 等 weak/inline 函数供架构扩展
三、核心入口/出口函数
3.1 用户态进入(common.c)
1 2 3 4 5 6 7
| void noinstr enter_from_user_mode(struct pt_regs *regs) { arch_enter_from_user_mode(regs); lockdep_hardirqs_off(); user_exit_irqoff(); trace_hardirqs_off_finish(); }
|
3.2 用户态退出
1 2 3 4 5 6 7 8
| void noinstr exit_to_user_mode(void) { trace_hardirqs_on_prepare(); lockdep_hardirqs_on_prepare(); user_enter_irqoff(); arch_exit_to_user_mode(); lockdep_hardirqs_on(); }
|
3.3 函数对照表
| 函数 |
场景 |
说明 |
enter_from_user_mode() |
通用用户态进入 |
建立 kernel 上下文状态 |
syscall_enter_from_user_mode() |
系统调用进入 |
enter + 开 IRQ + syscall work |
syscall_enter_from_user_mode_prepare() |
分步进入(仅 prepare) |
架构需中间插入逻辑时使用 |
syscall_enter_from_user_mode_work() |
分步进入(仅 work) |
ptrace/seccomp/tracepoint |
exit_to_user_mode() |
通用用户态退出 |
最终状态切换 |
syscall_exit_to_user_mode() |
系统调用退出 |
exit work + exit_to_user_mode |
syscall_exit_to_user_mode_work() |
分步退出 |
不含最终 exit_to_user_mode |
irqentry_enter_from_user_mode() |
中断从用户态进入 |
同 enter_from_user_mode |
irqentry_exit_to_user_mode() |
中断返回用户态 |
exit work + exit_to_user_mode |
irqentry_enter() |
通用中断进入 |
自动判断 user/kernel/idle |
irqentry_exit() |
通用中断退出 |
自动判断返回目标 |
irqentry_nmi_enter/exit() |
NMI 进入/退出 |
NMI 专用 lockdep/RCU |
四、系统调用入口路径
4.1 完整流程
1 2 3 4 5 6 7 8 9 10 11 12
| 架构 syscall 入口 (如 x86 syscall/sysenter) → syscall_enter_from_user_mode(regs, nr) ├── __enter_from_user_mode(regs) // RCU/lockdep/tracing ├── local_irq_enable() └── __syscall_enter_from_user_work() └── syscall_trace_enter(regs, nr, work) ├── [1] syscall_user_dispatch // SUD 过滤 ├── [2] ptrace_report_syscall_entry ├── [3] __secure_computing // seccomp ├── [4] trace_sys_enter // tracepoint └── [5] audit_syscall_entry // audit → 架构调用实际 syscall 处理函数
|
4.2 syscall_work 标志
1 2 3
| #define SYSCALL_WORK_ENTER (SECCOMP | TRACEPOINT | TRACE | EMU | AUDIT | USER_DISPATCH) #define SYSCALL_WORK_EXIT (TRACEPOINT | TRACE | AUDIT | USER_DISPATCH | EXIT_TRAP)
|
| 标志 |
功能 |
SYSCALL_WORK_SECCOMP |
seccomp BPF 过滤 |
SYSCALL_WORK_SYSCALL_TRACE |
ptrace 追踪 |
SYSCALL_WORK_SYSCALL_EMU |
ptrace syscall 模拟 |
SYSCALL_WORK_SYSCALL_TRACEPOINT |
ftrace sys_enter tracepoint |
SYSCALL_WORK_SYSCALL_AUDIT |
audit 审计 |
SYSCALL_WORK_SYSCALL_USER_DISPATCH |
Syscall User Dispatch |
SYSCALL_WORK_SYSCALL_EXIT_TRAP |
单步退出 trap |
4.3 跳过系统调用
syscall_trace_enter() 返回 -1 表示跳过 syscall 执行:
- ptrace 模拟(SYSCALL_EMU)
- seccomp 拒绝
- SUD 拦截
五、返回用户态路径
5.1 exit_to_user_mode_loop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| static unsigned long exit_to_user_mode_loop(struct pt_regs *regs, unsigned long ti_work) { while (ti_work & EXIT_TO_USER_MODE_WORK) { local_irq_enable_exit_to_user(ti_work);
if (ti_work & _TIF_NEED_RESCHED) schedule();
if (ti_work & _TIF_UPROBE) uprobe_notify_resume(regs);
if (ti_work & _TIF_PATCH_PENDING) klp_update_patch_state(current);
if (ti_work & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL)) arch_do_signal_or_restart(regs);
if (ti_work & _TIF_NOTIFY_RESUME) resume_user_mode_work(regs);
arch_exit_to_user_mode_work(regs, ti_work);
local_irq_disable_exit_to_user(); tick_nohz_user_enter_prepare(); ti_work = read_thread_flags(); } return ti_work; }
|
5.2 EXIT_TO_USER_MODE_WORK 标志
1 2 3 4
| #define EXIT_TO_USER_MODE_WORK \ (_TIF_SIGPENDING | _TIF_NOTIFY_RESUME | _TIF_UPROBE | \ _TIF_NEED_RESCHED | _TIF_PATCH_PENDING | _TIF_NOTIFY_SIGNAL | \ ARCH_EXIT_TO_USER_MODE_WORK)
|
5.3 系统调用退出流程
1 2 3 4 5 6 7 8 9 10 11
| syscall 处理完成 → syscall_exit_to_user_mode(regs) ├── syscall_exit_to_user_mode_prepare(regs) │ ├── rseq_syscall() │ └── syscall_exit_work() // audit/trace/ptrace exit ├── exit_to_user_mode_prepare(regs) │ ├── exit_to_user_mode_loop() // 处理所有 TIF pending work │ ├── arch_exit_to_user_mode_prepare() │ └── addr_limit_user_check() // 安全检查 └── __exit_to_user_mode() // RCU/lockdep/tracing 恢复 → 架构 restore context 并 ERET 返回用户态
|
六、中断/异常入口路径
6.1 irqentry_enter
统一的中断入口状态管理,自动判断来源:
1 2 3 4 5 6 7 8 9 10
| irqentry_state_t irqentry_enter(struct pt_regs *regs) { if (user_mode(regs)) irqentry_enter_from_user_mode(regs); else if (is_idle_task(current)) ct_irq_enter(); else rcu_irq_enter_check_tick(); }
|
6.2 irqentry_exit
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void irqentry_exit(struct pt_regs *regs, irqentry_state_t state) { if (user_mode(regs)) irqentry_exit_to_user_mode(regs); else if (!regs_irqs_disabled(regs)) { if (state.exit_rcu) ct_irq_exit(); irqentry_exit_cond_resched(); trace_hardirqs_on(); } else { if (state.exit_rcu) ct_irq_exit(); } }
|
6.3 NMI 入口/出口
1 2 3 4 5 6 7 8 9 10 11
| irqentry_nmi_enter(regs) → __nmi_enter() → lockdep_hardirq_enter() → ct_nmi_enter() → ftrace_nmi_enter()
irqentry_nmi_exit(regs, state) → ftrace_nmi_exit() → ct_nmi_exit() → lockdep_hardirq_exit() → __nmi_exit()
|
NMI 有独立的 lockdep/RCU 路径,因为 NMI 不可被普通中断打断。
七、Syscall User Dispatch
7.1 概述
syscall_user_dispatch.c 实现 Syscall User Dispatch(SUD),允许用户空间指定”允许直接发起 syscall 的代码区域”,区域外的 syscall 被拦截并发送 SIGSYS。
用途:Wine/Proton 等兼容层,让原生 Linux syscall 与转译层 syscall 共存。
7.2 工作流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| bool syscall_user_dispatch(struct pt_regs *regs) { if (PC 在允许区域内) return false;
if (selector == ALLOW) return false;
syscall_rollback(current, regs); trigger_sigsys(regs); return true; }
|
7.3 用户空间配置
通过 prctl 配置:
1 2 3 4
| set_syscall_user_dispatch(mode, offset, len, selector) → 设置 current->syscall_dispatch { offset, len, selector } → set_syscall_work(SYSCALL_USER_DISPATCH)
|
八、KVM 进入 Guest 模式
8.1 概述
kvm.c 在 KVM vCPU 进入 guest 模式前 处理 host pending work,类似 exit_to_user_mode_loop 但针对虚拟化场景。
8.2 流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int xfer_to_guest_mode_handle_work(struct kvm_vcpu *vcpu) { ti_work = read_thread_flags(); if (!(ti_work & XFER_TO_GUEST_MODE_WORK)) return 0;
do { if (ti_work & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL)) kvm_handle_signal_exit(vcpu);
if (ti_work & _TIF_NEED_RESCHED) schedule();
if (ti_work & _TIF_NOTIFY_RESUME) resume_user_mode_work(NULL);
ret = arch_xfer_to_guest_mode_handle_work(vcpu, ti_work); if (ret) return ret;
ti_work = read_thread_flags(); } while (ti_work & XFER_TO_GUEST_MODE_WORK || need_resched()); }
|
8.3 相关 API(entry-kvm.h)
| 函数 |
功能 |
xfer_to_guest_mode_handle_work() |
处理所有 pending work |
xfer_to_guest_mode_work_pending() |
快速检查是否有 pending work |
xfer_to_guest_mode_prepare() |
IRQ 关闭时的 NOHZ 准备 |
九、noinstr 与编译约束
9.1 为什么需要 noinstr
入口/出口代码在 中断关闭、RCU 特殊状态 下运行。若 ftrace/KASAN 插桩:
- ftrace 回调可能触发 page fault → 递归异常
- KASAN 检测可能分配内存 → 死锁
9.2 Makefile 编译约束
1 2 3 4
| KASAN_SANITIZE := n UBSAN_SANITIZE := n KCOV_INSTRUMENT := n CFLAGS_REMOVE_common.o = -fstack-protector
|
9.3 instrumentation 边界
1 2 3 4 5 6 7 8 9 10 11
| noinstr void syscall_enter_from_user_mode(...) { __enter_from_user_mode(regs);
instrumentation_begin(); local_irq_enable(); ret = __syscall_enter_from_user_work(regs, syscall); instrumentation_end();
return ret; }
|
十、RK3588/ARM64 对应实现
10.1 架构差异
| 方面 |
Generic Entry (kernel/entry) |
ARM64 (RK3588) |
| 配置 |
CONFIG_GENERIC_ENTRY=y |
未启用 |
| 入口文件 |
kernel/entry/common.c |
arch/arm64/kernel/entry-common.c |
| Syscall 处理 |
架构调用 generic 函数 |
arch/arm64/kernel/syscall.c |
| 返回用户态 TIF work |
exit_to_user_mode_loop() |
do_notify_resume() |
| SUD |
syscall_user_dispatch.c |
不支持(无 GENERIC_ENTRY) |
10.2 ARM64 系统调用路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| EL0 SVC 指令 → entry.S: el0_svc / el0_svc_compat → entry-common.c: el0_svc() ├── enter_from_user_mode() // 架构版:lockdep/RCU/tracing/MTE ├── local_daif_restore(DAIF_PROCCTX) // 开 IRQ └── do_el0_svc(regs) // syscall.c ├── fp_user_discard() // 清除 SVE/SME 状态 └── el0_svc_common() ├── syscall_trace_enter() // ptrace/seccomp/trace ├── invoke_syscall() // 调用 sys_* 函数 └── syscall_trace_exit() → exit_to_user_mode(regs) // entry-common.c ├── prepare_exit_to_user_mode() │ └── do_notify_resume() // 信号/调度/uprobe 等 └── __exit_to_user_mode() // RCU/lockdep/tracing 恢复 → entry.S: ret_to_user // 恢复寄存器并 ERET
|
10.3 ARM64 中断路径
1 2 3 4 5 6 7 8
| EL0/EL1 IRQ → entry.S: el0t_64_irq_handler / el1h_64_irq_handler → entry-common.c: enter_from_user_mode / enter_from_kernel_mode do_interrupt_handler() arm64_preempt_schedule_irq() // 内核态抢占 exit_to_user_mode / exit_to_kernel_mode → entry.S: 返回
|
10.4 ARM64 特有处理
| 特性 |
处理位置 |
| MTE (Memory Tagging) |
mte_check_tfsr_entry/exit, mte_disable_tco_entry |
| SVE/SME |
fp_user_discard() 在 syscall 入口 |
| BP hardening |
arm64_apply_bp_hardening() 在 instruction abort |
| DAIF 中断掩码 |
local_daif_mask/restore 替代 generic local_irq_* |
| Pseudo NMI |
CONFIG_ARM64_PSEUDO_NMI 区分 IRQ/FIQ |
十一、完整系统调用时序
以 用户态 write() 系统调用 为例(Generic Entry 路径):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| [用户态] app 执行 syscall 指令 (write) → CPU 切换到 EL1,保存 pt_regs
[架构入口 - entry.S] 保存寄存器到 pt_regs 调用 C 入口函数
[common.c - 进入] syscall_enter_from_user_mode(regs, __NR_write) → user_exit_irqoff() // RCU: CONTEXT_USER → CONTEXT_KERNEL → local_irq_enable() → seccomp 检查通过 → trace_sys_enter(write) // 可选 tracepoint
[内核 syscall 处理] ksys_write(fd, buf, count) → vfs_write() → 设备驱动 copy_from_user + 硬件写入
[common.c - 退出] syscall_exit_to_user_mode(regs) → audit_syscall_exit() → trace_sys_exit() → exit_to_user_mode_loop() → (如有信号) arch_do_signal_or_restart() → (如需调度) schedule() → exit_to_user_mode() → user_enter_irqoff() // RCU: CONTEXT_KERNEL → CONTEXT_USER → arch_exit_to_user_mode()
[架构出口 - entry.S] 恢复寄存器 ERET 返回 EL0
[用户态] write() 返回写入字节数
|
十二、总结
kernel/entry 是 Linux 内核 用户态/内核态边界管理 的通用框架:
- 统一入口 —
enter_from_user_mode / irqentry_enter 建立 kernel 上下文(RCU、lockdep、tracing)
- Syscall 拦截链 — SUD → ptrace → seccomp → tracepoint → audit 顺序处理
- 统一出口 —
exit_to_user_mode_loop 循环处理所有 pending work(信号、调度、uprobe、livepatch)
- noinstr 安全 — 关键路径禁止插桩,避免递归异常
- 架构 hook — 通过 weak/inline 函数让各架构扩展
- KVM 集成 — guest 模式进入前的 work 处理
- SUD — 用户态 syscall 区域过滤,服务兼容层
RK3588(ARM64)虽未编译本目录代码,但在 arch/arm64/kernel/entry-common.c 中实现了相同语义的入口/出口逻辑,是理解 ARM64 异常/系统调用/中断处理的关键参照。
附录:源文件清单
| 文件 |
行数 |
分类 |
common.c |
~490 |
通用入口/出口核心 |
syscall_user_dispatch.c |
~109 |
Syscall User Dispatch |
kvm.c |
~49 |
KVM guest 模式 work |
common.h |
~7 |
内部头文件 |
include/linux/entry-common.h |
~468 |
公共 API 定义 |
include/linux/entry-kvm.h |
~99 |
KVM 入口 API |
ARM64 对应文件(RK3588 实际使用):
| 文件 |
功能 |
arch/arm64/kernel/entry.S |
异常向量、上下文保存/恢复 |
arch/arm64/kernel/entry-common.c |
C 语言入口/出口逻辑 |
arch/arm64/kernel/syscall.c |
系统调用分发与 trace |
arch/arm64/kernel/signal.c |
do_notify_resume 信号/work 处理 |
正在加载留言…