内核态发起用户态程序执行的标准路径:call_usermodehelper() → 工作队列 → __call_usermodehelper() → kernel_execve() → 系统调用 sys_execve()。本文剖析调用链、关键数据结构、执行上下文切换及遗留疑问。
call_usermodehelper()
├─ call_usermodehelper_setup() // 初始化 subprocess_info (含 work_struct)
└─ call_usermodehelper_exec() // 加入 khelper_wq 工作队列,等待完成
↓
__call_usermodehelper() // 工作队列处理函数
└─ kernel_execve() // arch/x86/kernel/entry_64.S 汇编实现
↓
sys_execve() // 最终执行用户程序
struct subprocess_info {
struct work_struct work; // 必须是第一个字段,便于 container_of
const char *path; // 可执行文件路径
char **argv; // 参数数组
char **envp; // 环境变量
int wait; // 是否等待完成
// ... 回调函数指针等
};
技巧:work_struct 作为首字段,工作队列执行时可通过 container_of(work, subprocess_info, work) 反推完整上下文。
ENTRY(kernel_execve)
CFI_STARTPROC
FAKE_STACK_FRAME $0
SAVE_ALL // 保存内核寄存器
movq %rsp, %rcx // 传递参数指针
call sys_execve // 调用系统调用入口
movq %rax, RAX(%rsp) // 保存返回值
RESTORE_REST
testq %rax, %rax
je int_ret_from_sys_call
RESTORE_ARGS
UNFAKE_STACK_FRAME
ret
CFI_ENDPROC
END(kernel_execve)
核心动作:构造伪栈帧 → 保存上下文 → 调用 sys_execve → 恢复上下文返回。
推测:运行在用户空间。理由:sys_execve 是标准系统调用,用户态调用时创建的进程自然在用户空间;内核态调用同一入口,内核必会在 sys_execve 内部完成「内核栈 → 用户栈」切换、地址空间切换(mm_struct)、特权级降级(iret/sysret)。
待验证证据:需追踪 sys_execve → do_execve → exec_binprm → start_thread 中的 regs->cs = __USER_CS; regs->ss = __USER_DS 等上下文切换代码。
内核调用用户程序采用「工作队列异步化 + kernel_execve 汇编桩 + sys_execve 复用」设计,既复用了成熟的 execve 逻辑,又解决了原子上下文无法阻塞的约束。后续将深入 do_execve 验证用户空间切换细节。