kernel/entry 用户态/内核态入口机制与原理详解

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.csyscall.c 中实现了语义等价的架构专用入口路径。本目录代码定义了通用框架的设计规范;理解本目录即理解 ARM64 入口逻辑的”蓝图”。


目录


一、源码目录结构

1.1 编译依赖(Makefile)

1
2
3
4
5
6
7
# 禁止 sanitizer 污染 noinstr 代码
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/*/Kconfigselect 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:在安全点之后才允许插桩
  • 架构 hookarch_enter_from_user_modearch_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); // 架构 hook
lockdep_hardirqs_off();
user_exit_irqoff(); // RCU/context tracking: USER → KERNEL
trace_hardirqs_off_finish(); // ftrace: 标记 IRQ off
}

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(); // RCU/context tracking: KERNEL → USER
arch_exit_to_user_mode(); // 架构 hook(推测执行缓解等)
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
// include/linux/entry-common.h
#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); // uprobe

if (ti_work & _TIF_PATCH_PENDING)
klp_update_patch_state(current); // livepatch

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); // rseq 等

arch_exit_to_user_mode_work(regs, ti_work); // 架构 TIF 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(); // idle 任务特殊 RCU 处理
else
rcu_irq_enter_check_tick(); // 内核态 RCU tick 检查
// lockdep + tracing
}

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); // → 用户态:完整 exit work
else if (!regs_irqs_disabled(regs)) {
if (state.exit_rcu)
ct_irq_exit(); // idle RCU 恢复
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)
{
// 1. 检查 PC 是否在允许的 [offset, offset+len) 区域内
if (PC 在允许区域内)
return false; // 正常执行 syscall

// 2. 如有 selector,读取用户态字节决定 ALLOW/BLOCK
if (selector == ALLOW)
return false;

// 3. 拦截:回滚 syscall 并发送 SIGSYS
syscall_rollback(current, regs);
trigger_sigsys(regs); // si_code = SYS_USER_DISPATCH
return true;
}

7.3 用户空间配置

通过 prctl 配置:

1
2
3
4
// prctl(PR_SYS_DISPATCH_*, ...)
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.cKVM 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;

// 循环处理 pending work
do {
if (ti_work & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
kvm_handle_signal_exit(vcpu); // 信号 → 退出 guest

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          # 整个目录禁用 KASAN
UBSAN_SANITIZE := n # 禁用 UBSAN
KCOV_INSTRUMENT := n # 禁用 KCOV
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); // noinstr 区域

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 内核 用户态/内核态边界管理 的通用框架:

  1. 统一入口enter_from_user_mode / irqentry_enter 建立 kernel 上下文(RCU、lockdep、tracing)
  2. Syscall 拦截链 — SUD → ptrace → seccomp → tracepoint → audit 顺序处理
  3. 统一出口exit_to_user_mode_loop 循环处理所有 pending work(信号、调度、uprobe、livepatch)
  4. noinstr 安全 — 关键路径禁止插桩,避免递归异常
  5. 架构 hook — 通过 weak/inline 函数让各架构扩展
  6. KVM 集成 — guest 模式进入前的 work 处理
  7. 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 处理

文章互动

阅读 --

留言

0 条留言

正在加载留言…