信号的内核数据结构与接口全景
这是 signals-kernel(信号内核总纲)的拆分篇之三,专讲"信号在内核里用什么数据结构表示、有哪些接口"。配套拆分篇:分类 · 传递路径 · 性能。
传递路径篇从流程角度把信号一次投递走完了。本节回到"积木"本身——把那条链路涉及的所有数据结构、所有内核接口逐一定义、逐字段注释、逐函数说明。先看懂积木长什么样,再回头看流程就通了。
数据结构关系全景

一句话:进程级一对 sighand_struct+signal_struct、每线程一对 pending+blocked、RT 信号分配 sigqueue 排队、投递时在用户栈上临时构建 rt_sigframe。
struct sighand_struct —— 信号处理动作表(进程共享)
sighand_struct 是进程级唯一的信号处理动作表,所有线程共享同一份实例。
字段说明:
| 字段 | 类型 | 含义 |
|---|---|---|
count | refcount_t | 引用计数。CLONE_SIGHAND(线程创建)时 +1,task 退出时 -1,归零后 kfree |
action[_NSIG] | struct k_sigaction | 64 个信号的处置动作数组,按信号编号索引(1~64)。全进程共享——所有线程看到同一套 handler |
siglock | spinlock_t | 保护所有信号操作的自旋锁(send_signal、do_sigaction、get_signal),是信号序列化的根,不能长持 |
signalfd_wqh | wait_queue_head_t | signalfd 等待队列,用于 poll()/select() 等待。新信号到达时 wake_up |
rcu | struct rcu_head | RCU 延迟释放。信号处理路径可能在 RCU 读锁下访问 sighand,free 时需走 call_rcu |
// include/linux/sched/signal.h (简化, 核心字段)
struct sighand_struct {
refcount_t count; // 引用计数
struct k_sigaction action[_NSIG]; // 动作数组, _NSIG=64
spinlock_t siglock; // 保护全部信号状态的锁
wait_queue_head_t signalfd_wqh; // signalfd 等待队列
struct rcu_head rcu; // RCU 延迟释放
};锁的语义:
siglock保护sighand_struct.action[]、signal_struct.shared_pending、以及当前线程的private pending和blocked。几乎所有信号函数都先spin_lock_irq(&sighand->siglock)——这是信号被序列化的原因。
struct signal_struct —— 线程组共享信号状态(进程共享)
signal_struct 是线程组共享的信号状态容器。各线程通过 task_struct->signal 指针指向同一份实例。
字段说明:
| 字段 | 类型 | 含义 |
|---|---|---|
sigcnt | refcount_t | 引用计数。线程创建 +1,退出 -1,归零释放 |
shared_pending | struct sigpending | ★ 进程级未决信号集。kill(pid) 发来的信号落在这里,complete_signal() 从中取信号分发给某条线程 |
group_exit_code | int | 组退出码。收到 SIGKILL 时设置,记录退出原因(信号编号的低 8 位) |
group_stop_count | int | 正在执行组停止的线程数 |
flags | unsigned int | SIGNAL_STOP_STOPPED 等状态标志 |
rlim[RLIM_NLIMITS] | struct rlimit | 进程级资源限制(RLIMIT_CPU、RLIMIT_FSIZE…)。SIGXCPU / SIGXFSZ 的触发依赖这里 |
utime, stime | u64 | 进程累计用户/内核 CPU 时间。SIGPROF(ITIMER_PROF)和 SIGVTALRM(ITIMER_VIRTUAL)依赖这里判断是否超时 |
cutime, cstime | u64 | 已回收子进程的累计时间 |
posix_timers | struct hlist_head | POSIX 定时器链表。timer_create() 创建的定时器挂在这里,超时时由 alarm 软中断 → posix_timer_fn → send_sigqueue 投递信号 |
thread_head | struct list_head | 线程组链表头。complete_signal() 遍历它来找到合适的投递目标 |
// include/linux/sched/signal.h (简化, 核心字段)
struct signal_struct {
refcount_t sigcnt; // 引用计数
struct sigpending shared_pending; // 发给整个进程的未决信号
int group_exit_code; // 组退出码
int group_stop_count; // 组停止中的线程数
unsigned int flags; // 状态标志
struct rlimit rlim[RLIM_NLIMITS]; // 资源限制
u64 utime, stime; // 累计 CPU 时间
u64 cutime, cstime; // 已回收子进程时间
struct hlist_head posix_timers; // POSIX 定时器链表
struct list_head thread_head; // 线程组链表头
};struct sigpending —— 未决信号集(每线程私有 + 进程共享各一份)
sigpending 是未决信号的容器,出现在两个地方:每线程的 task->pending 和进程级的 signal->shared_pending。
字段说明:
struct list_head list:sigqueue 链表头。RT 信号的排队节点链在这里。遍历 pending 时先遍历 list(RT 信号,可能多个),再检查 signal 位掩码(普通信号,每种只记一次)。sigset_t signal:位掩码,_NSIG=64 个 bit,每位对应一个信号号。sigaddset 置位标记"有未决",sigdelset 清除"已投递"。普通信号只靠位掩码,不分配 sigqueue;RT 信号位掩码 + list 中排队。
// include/linux/signal.h
struct sigpending {
struct list_head list; // sigqueue 链表头
sigset_t signal; // 未决信号位掩码
};struct sigqueue —— RT 信号的排队节点
每个 RT 信号实例都分配一个 sigqueue 并链入 pending.list 尾部。来的 N 次各分配一个,依次投递——这就是 RT 信号"排队"的实现。
字段说明:
struct list_head list:链表节点,挂入 sigpending.list。kernel_siginfo_t info:信号详细信息——谁发出的(si_pid / si_uid)、什么原因(si_code: SI_USER / SI_QUEUE / SI_TKILL / ...)、附带数据(si_int / si_ptr,sigqueue() 传来的)。int flags:标志位。SIGQUEUE_PREALLOC 表示预分配的 sigqueue(per-task 缓存,不分配新内存);用户态 sigqueue() 发送时从 kmem_cache 分配。struct user_struct *user:指向发送进程的 user_struct,用于审计和资源统计。每个 user 有挂起信号数限制(RLIMIT_SIGPENDING)。
// include/linux/signal_types.h (简化)
struct sigqueue {
struct list_head list; // pending.list 的节点
kernel_siginfo_t info; // siginfo_t 的内容
int flags; // 标志
struct user_struct *user; // 发送方 user 资源追踪
};struct k_sigaction —— 内核视角的信号处置配置
k_sigaction 是内核存储信号处置动作的结构,存于 sighand_struct.action[] 数组中。每个信号号对应一个独立的 k_sigaction——SIGINT(2) 和 SIGTERM(15) 可以注册完全不同的 handler。
k_sigaction vs 用户态 struct sigaction —— 核心区别
用户态 (glibc) 内核态
═══════════════ ═════════
┌─────────────────────┐ ┌─────────────────────────────┐
│ struct sigaction │ │ struct k_sigaction │
│ .sa_handler │────→│ .sa ← 原样内嵌 │
│ .sa_mask │ │ (struct sigaction) │
│ .sa_flags │ │ │
│ .sa_restorer │ │ .flags ← 内核独有 │
│ │ │ SA_IMMUTABLE │
│ (用户传递进来的) │ │ SA_UNSUPPORTED │
│ │ │ │
│ ★ 按信号号独立存储 │ │ ★ 按信号号索引 │
│ (每个信号一个) │ │ action[sig-1] │
│ │ │ ★ 全进程共享一份 │
│ │ │ (所有线程同一套 handler) │
└─────────────────────┘ └─────────────────────────────┘| 维度 | 用户态 struct sigaction | 内核 struct k_sigaction |
|---|---|---|
| 定义位置 | glibc (bits/sigaction.h),架构相关 | include/linux/signal_types.h,架构无关 |
| 嵌套关系 | 独立结构体 | 内嵌 struct sigaction sa + 内核专属 flags |
| sa_handler 含义 | 用户态函数指针 | 原样存储,内核不做任何验证——只是拿来拷贝进 pt_regs->ip |
| sa_flags | 用户设置的值 | 包含用户 sa_flags,内核额外读写其中部分位(如自动设置 SA_IMMUTABLE) |
| sa_restorer | 已废弃,glibc 不再设置,填 NULL | 同样废弃,setup_rt_frame 用 VDSO 地址替代 |
| flags | 无此字段 | 内核私有标志:SA_IMMUTABLE(SIGKILL/SIGSTOP 不可改)、SA_UNSUPPORTED |
| 生命周期 | 栈/堆上临时,sigaction() 调用后即释放 | sighand_struct.action[] 中,随进程存在,直到被覆盖或进程退出 |
| 共享性 | 调用线程私有 | 进程全局——所有线程共享同一份 action 数组 |
| 索引方式 | 按信号号传给 sigaction() | action[sig-1],按信号号索引 |
| SA_IMMUTABLE | 用户看不到、设不了 | 内核通过 force_sig() 给 SIGKILL/SIGSTOP 自动设置 |
sigaction() 系统调用 → 内核做了什么
// kernel/signal.c: do_sigaction()
int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
{
struct k_sigaction *k = ¤t->sighand->action[sig - 1];
// ① 如果用户想获取当前处置,直接拷贝出去
if (oact)
*oact = *k;
if (act) {
// ② ★ SIGKILL 和 SIGSTOP 禁止修改
if (sig == SIGKILL || sig == SIGSTOP)
return -EINVAL;
// ③ ★ 已有 SA_IMMUTABLE 标志的也不能改
if (k->flags & SA_IMMUTABLE)
return -EINVAL;
// ④ 拷贝用户传入的 sigaction 到内核结构
*k = *act;
// ⑤ 如果 handler 是 SIG_IGN 且信号默认动作为 Stop/Cont
// 则清除 pending 中该信号,防止"忽略但已 pending"的僵局
if (sig_handler_ignored(sig_handler(current, sig), sig)) {
sigdelset(¤t->pending.signal, sig);
// ...
}
}
return 0;
}关键洞察:内核几乎不做验证,只是原样拷贝用户传来的
struct sigaction。sa_handler指向的地址是否有效、是否可以执行——内核不管。如果用户传了一个垃圾地址,第一次信号投递时iretq跳过去,CPU 会立刻触发 SIGSEGV(又一个同步信号)。
flags 字段的细节
// include/linux/signal.h
#define SA_IMMUTABLE 0x00800000U // 处置不可修改 (SIGKILL/SIGSTOP)
#define SA_UNSUPPORTED 0x00000400U // 架构不支持该信号的某些特性SA_IMMUTABLE:内核通过force_sig()设置 SIGKILL(9) 和 SIGSTOP(19) 的初始 handler 时加上此标志。此后do_sigaction()拒绝任何修改。注意这不是用户态sa_flags里的位(0x00800000 在用户态SA_命名空间中未分配),所以用户看不到、设不了。SA_UNSUPPORTED与sa_flags中的SA_UNSUPPORTED(0x00000400)共享同一个位——如果架构不支持某信号特性,该位可以同时出现在sa_flags和flags中。- 用户态
sa_flags子集:SA_SIGINFO、SA_RESTART、SA_NODEFER、SA_ONSTACK、SA_RESETHAND等标志位只在k_sigaction.sa.sa_flags中,不在k_sigaction.flags中。内核读取sa_flags来决定如何投递信号(是否传 siginfo、是否用备用栈、是否自动屏蔽等)。
struct rt_sigframe —— x86-64 用户栈上的信号帧
rt_sigframe 是内核在用户栈上构建的临时结构,handler 执行完毕后 sigreturn 从这里恢复全部寄存器。
字段说明:
char __user *pretcode(★关键):信号返回地址。setup_rt_frame 将__vdso_rt_sigreturn的地址写在这里——handler 执行 ret 时 CPU 从此处 pop 出返回地址跳转,自动进入 VDSO → sys_rt_sigreturn,无需用户显式调用 sigreturn。struct ucontext uc:包含完整上下文。其中 uc_mcontext 保存完整的 CPU 寄存器快照(sigcontext,含 R8~R15、RDI、RSI、RBP、RBX、RDX、RAX、RCX、RSP、RIP、EFLAGS 等 23+ 个寄存器);uc_sigmask 是 handler 执行期间的 blocked mask;uc_link 指向上一个 ucontext(嵌套信号链);uc_stack 记录 SA_ONSTACK 时的备用栈信息。siginfo_t info:信号详情(si_signo、si_code、si_pid、附带数据),handler 的第二个参数指向这里。struct _fpstate fpstate:浮点/SIMD 状态(FPU、MMX、SSE、AVX),默认 layout 为 fxregs_state + xstate_header。
// arch/x86/include/asm/sigframe.h (简化 x86-64)
struct rt_sigframe {
char __user *pretcode; // 返回地址 → VDSO sigreturn
struct ucontext uc; // 用户上下文 (含寄存器)
siginfo_t info; // 信号详情
struct _fpstate fpstate; // FPU/SIMD 状态
};
// uc_mcontext 内部 (sigcontext, x86-64):
// struct sigcontext {
// __u64 r8, r9, r10, r11, r12, r13, r14, r15; // 被调用者保存
// __u64 rdi, rsi, rbp, rbx, rdx, rax, rcx, rsp; // 调用约定寄存器
// __u64 rip; // ★ 被中断的指令地址
// __u64 eflags; // CPU 标志寄存器
// __u64 cs, gs, fs, ss; // 段选择子
// __u64 err, trapno, oldmask, cr2;// 异常上下文
// struct _fpstate *fpstate; // 指向浮点状态
// __u64 reserved1[8];
// };为什么定义在用户栈上:
uc_mcontext保存的是信号到来那一刻的全部寄存器——这是 sigreturn 能"恢复现场"的物质基础。内核不保存"旧 pt_regs"的指针,而是把它完整拷贝到用户栈上。恢复时再从用户栈拷贝回pt_regs。
struct ksignal —— 信号投递的临时上下文
ksignal 是 get_signal() → handle_signal() 之间的临时传递结构,在 get_signal 内部栈上分配。
字段说明:
struct k_sigaction *ka:指向 sighand_struct.action[sig-1] 中的处置动作。get_signal() 找到目标信号后,把对应的 k_sigaction 指针放在这里,handle_signal() 用这个来调用 setup_rt_frame。kernel_siginfo_t info:待投递信号的详细信息(si_signo、si_code、si_pid 等)。来源是 sigqueue.info 拷贝或由 get_signal 填充。int sig:信号编号(1~64),等于 info.si_signo。
// include/linux/signal_types.h (简化)
struct ksignal {
struct k_sigaction *ka; // 信号处置 (handler / flags / mask)
kernel_siginfo_t info; // 信号详情
int sig; // 信号号
};sigset_t —— 信号集位掩码
sigset_t 是 64 位的位掩码(x86-64 上用一个 u64 搞定),每位对应一个信号号。_NSIG_WORDS=1 表示一个 unsigned long 即可覆盖全部 64 个信号。
操作 sigset_t 必须使用标准宏,不可直接位运算(不同平台 sizeof 可能不同):sigemptyset(全清零)、sigfillset(全置一)、sigaddset(置位)、sigdelset(清除)、sigismember(测试)。
// include/uapi/asm-generic/signal.h (简化)
typedef struct {
unsigned long sig[_NSIG_WORDS]; // 位掩码, x86-64 上一个 u64
} sigset_t;
// 操作宏:
// sigemptyset(set) → set = 0
// sigfillset(set) → set = ~0UL
// sigaddset(set, sig) → set |= (1UL << (sig-1))
// sigdelset(set, sig) → set &= ~(1UL << (sig-1))
// sigismember(set, sig)→ (set >> (sig-1)) & 1
// 用法示例:
// sigset_t mask;
// sigemptyset(&mask);
// sigaddset(&mask, SIGINT); // 把 SIGINT(2) 加入
// sigprocmask(SIG_BLOCK, &mask, NULL); // 屏蔽 SIGINT核心接口一览
发送侧 —— 让信号"出发"
四个用户态发送入口(kill/pthread_kill/tgkill/sigqueue)落到内核后分别对应四个 syscall:
| syscall | 目标粒度 | 调用链 | 说明 |
|---|---|---|---|
sys_kill(pid, sig) | 进程(tgid) | kill_something_info → kill_pid_info → group_send_sig_info → do_send_sig_info → send_signal(SEND_SIG_PRIV) | pid>0 发 tgid==pid;pid==0 发同 PGID;pid==-1 发所有有权进程;pid<-1 发 PGID==-pid |
sys_tkill(tid, sig) | 单线程 | 直接操作目标线程 private pending | 略过选线程步骤 |
sys_tgkill(tgid, tid, sig) | 组内特定线程 | 同 tkill + 校验 tid∈tgid | 防 race(线程退出后 tid 复用) |
sys_rt_sigqueueinfo(pid, sig, uinfo) | 进程 + 附加数据 | send_signal 带完整 siginfo | 用户态 sigqueue(pid, sig, value);value 包装成 sigqueue 节点挂入 pending.list,handler 通过 info->si_value 拿到 |
返回约定:0 成功;-1 失败(ESRCH 无此进程 / EPERM 无权限 / EINVAL 信号无效)。
发送侧的内核内部函数
send_signal —— 信号投递的核心入口(四条路径归一)
- 参数:
sig为信号编号,info为信号详情,t为目标 task_struct,type为投递类型。- PIDTYPE_PID → 发给特定线程(private pending)
- PIDTYPE_TGID → 发给整个进程(shared pending)
- PIDTYPE_PGID → 发给进程组
- PIDTYPE_SID → 发给会话
- 返回:0 成功,-1 失败(权限拒绝/信号忽略/不能发 SIGKILL 给 init)。
- 内部调用 __send_signal():
- 权限检查(prepare_signal)
- 准备 struct sigqueue(RT 信号分配,普通信号用预分配 per-task 节点)
sigaddset(&pending->signal, sig)置位 pending- RT 信号:
list_add_tail(&q->list, &pending->list)链入队列 - 如果
!sigismember(&t->blocked, sig)→complete_signal(sig, t, type)
int send_signal(int sig, struct kernel_siginfo *info,
struct task_struct *t, enum pid_type type);complete_signal —— 选目标线程 + 设 TIF_SIGPENDING + 唤醒
- 参数:
sig为信号编号,t为目标 task_struct,type为投递类型。 - 核心逻辑:
- 如果 type == PIDTYPE_PID(发给特定线程):直接对 t 操作(设 TIF_SIGPENDING,必要时唤醒)。
- 否则(发给整个进程):遍历 signal->thread_head 链表,找
!(sig blocked) && 线程可唤醒的目标;全部被屏蔽则留在 shared_pending 等解除屏蔽。通过 __wants_signal() 检查:!(sig blocked) && (在线程组中) && !(exiting)。
- 选中目标后调用 signal_wake_up() 唤醒。
void complete_signal(int sig, struct task_struct *t, enum pid_type type);signal_wake_up —— 唤醒接收线程
- 参数:
t为目标 task_struct,resume表示是否强行设置 TIF_SIGPENDING(即使线程在 exit 中)。 - 动作:
set_tsk_thread_flag(t, TIF_SIGPENDING)设标志位。wake_up_state(t, TASK_INTERRUPTIBLE)唤醒睡眠中的线程。- 如果线程在内核态运行(正在执行 syscall),不做任何事——TIF_SIGPENDING 会在返回用户态时被 exit_to_user_mode_loop 检查到。
void signal_wake_up(struct task_struct *t, int resume);注册/配置侧 —— 设置信号的处置方式
sys_rt_sigaction —— 设置/查询信号处置
- 参数:
sig为信号编号(不能是 SIGKILL/SIGSTOP),act为新处置(NULL=只查询),oact为旧处置输出(NULL=不关心),sigsetsize通常是 sizeof(sigset_t)。 - 调用链:
sys_rt_sigaction → do_sigaction - do_sigaction() 关键行为:校验 sig 范围且不是 SIGKILL/SIGSTOP →
spin_lock_irq(&siglock)→ 拷入新 action(sighand->action[sig-1])→ 若设为 SIG_IGN 且该信号有 pending → 清理 pending →spin_unlock_irq。 - 返回:0 成功,-1 失败(EINVAL: 信号号无效 / EFAULT: 用户指针无效)
int sys_rt_sigaction(int sig,
const struct sigaction __user *act,
struct sigaction __user *oact,
size_t sigsetsize);sys_rt_sigprocmask —— 检查/修改当前线程的信号屏蔽字
- 参数:
how为操作类型(SIG_BLOCK 加入屏蔽 / SIG_UNBLOCK 解除屏蔽 / SIG_SETMASK 直接覆盖)。set为新屏蔽字(NULL=只查询),oset为旧屏蔽字输出(NULL=不关心)。 - 调用链:
sys_rt_sigprocmask → sigprocmask → __set_current_blocked - __set_current_blocked 关键行为:
spin_lock_irq(&siglock)→current->blocked = newset→recalc_sigpending()检查是否有被解除屏蔽的未决信号 → 有则signal_wake_up(current, 0)→ 解锁。 - sigprocmask 是线程级操作——只改 current->blocked。多线程下应使用 pthread_sigmask,它与 sigprocmask 在 Linux 上走同一条系统调用 sys_rt_sigprocmask。
int sys_rt_sigprocmask(int how, sigset_t __user *set,
sigset_t __user *oset, size_t sigsetsize);投递/检查侧 —— 返回用户态时的信号处理
exit_to_user_mode_loop —— 内核→用户态的出口(架构无关层)
- 调用栈:
syscall_exit_to_user_mode / interrupt_exit_to_user_mode → exit_to_user_mode_loop(regs, ti_work) - 关键逻辑(简化):
while (ti_work & _TIF_WORK_MASK) {
local_irq_enable();
if (ti_work & _TIF_NEED_RESCHED) schedule(); // 先调度
if (ti_work & _TIF_SIGPENDING) // ★ 再处理信号
arch_do_signal_or_restart(regs);
if (ti_work & _TIF_NOTIFY_RESUME) ... // 最后通知
}- 这是信号被处理的唯一入口——无论 syscall/中断/异常返回,最终都走到这个循环。
void exit_to_user_mode_loop(struct pt_regs *regs, unsigned long ti_work);arch_do_signal_or_restart / do_signal
- 调用 get_signal() 从 pending 中取出一个可投递的信号。
- 根据返回的 ksig 决定:handle_signal / 默认动作 / 重启系统调用。
void arch_do_signal_or_restart(struct pt_regs *regs, bool has_signal);get_signal —— 从 pending 中取出下一个待处理信号
- 参数:
ksig为输出参数,填充取到的信号和处置。 - 返回:true = 找到可投递信号,false = 没有(已全部处理或仍在屏蔽中)。
- 核心逻辑:
spin_lock_irq(&sighand->siglock)- 检查是否有 group_stop(SIGSTOP/SIGTSTP 等)
- dequeue_signal 遍历 pending: a. 先遍历 current->pending.list(RT 信号队列) b. 再遍历 shared_pending.list c. 再检查 pending.signal 位掩码(普通信号) d. 对每个候选检查:不是 SIG_IGN、不被 blocked、不在退出中
- 找到后:
ksig->ka = &sighand->action[sig-1] spin_unlock_irq(&sighand->siglock)
- 动作分支:SIG_DFL+终止 → do_group_exit / SIG_DFL+停止 → do_signal_stop / SIG_IGN → continue / 有handler → return true
bool get_signal(struct ksignal *ksig);处理侧 —— handler 跳转的准备
handle_signal —— 架设"跳板":在用户栈伪造 sigframe,改写 pt_regs
handler 本身在用户态执行,但内核必须提前往用户栈写入 sigframe 来架设跳板。setup_rt_frame 通过 put_user() / __put_user() 直接写用户态内存——如果写入失败(栈不可写或空间不够)则 force_sigsegv。
- 参数:
ksig为信号+处置信息,regs为当前 pt_regs(将被修改)。 - 核心逻辑:
- 检查 SA_SIGINFO → 决定用 setup_rt_frame(有 siginfo)还是 setup_frame(无)。
- 调用 setup_rt_frame(ksig, regs):
- get_sigframe() 在用户栈计算 rt_sigframe 位置:正常栈
(regs->sp - 128) & ~0x3F(16 字节对齐,留出红色区);若设置了SA_ONSTACK+sigaltstack(),则用备用栈altstack.ss_sp + altstack.ss_size - sizeof(*frame) __put_user()将 regs 拷贝到 frame->uc.uc_mcontext(★内核写用户栈)copy_siginfo_to_user()将 ksig->info 拷贝到 frame->info(★内核写用户栈)put_user(vdso_addr, &frame->pretcode)设置返回地址 = VDSO sigreturn(★内核写用户栈)- ★ 改写 regs:
regs->ip = handler地址,regs->sp = frame,regs->di = sig(参数1),regs->si = &frame->info(参数2),regs->dx = &frame->uc(参数3)
- get_sigframe() 在用户栈计算 rt_sigframe 位置:正常栈
- iretq/sysret 返回用户态,CPU 自然进入 handler。
- 如果 setup_rt_frame 失败(用户栈不可写/空间不够)→ force_sigsegv(sig)。
void handle_signal(struct ksignal *ksig, struct pt_regs *regs);恢复侧 —— handler 返回后的现场恢复
sys_rt_sigreturn —— 从用户栈 sigframe 恢复完整 pt_regs
- 触发:handler 执行 ret → 跳到 VDSO
__vdso_rt_sigreturn→mov $SYS_rt_sigreturn, %rax→ syscall。 - 核心逻辑(arch/x86/kernel/signal.c):
- 从
current->regs->sp定位到用户栈上的 rt_sigframe。 - restore_sigcontext():从 frame->uc.uc_mcontext 读取所有寄存器值,逐一写回 current->pt_regs(regs->ip = sigcontext->rip, regs->sp = sigcontext->rsp, ...,全部 23+ 个寄存器)。
- restore_altstack():如果 SA_ONSTACK,恢复原备用栈的状态。
- __set_current_blocked():恢复信号屏蔽字为 sigframe.uc.uc_sigmask。
- iretq/sysret 返回——此时 pt_regs 已完全恢复为信号到来前的值。
- 从
- 这是内核唯一能"撤销" setup_rt_frame 修改的地方——没有 sigreturn,handler 永远回不到原始代码。
- 安全机制:内核不信任用户栈上的 sigframe 数据,恢复前有严格校验(uc_flags 必须为 0、段寄存器合法、浮点状态格式正确),任何一项不通过 → 直接 SIGSEGV。
asmlinkage long sys_rt_sigreturn(void);一句话总结:信号的内核世界就四块积木——进程级共享的
sighand_struct(处置动作表)和signal_struct(进程级未决集)、每线程私有的pending+blocked、RT 信号排队用的sigqueue,再加上投递时临时构建在用户栈上的rt_sigframe;所有系统调用最终都汇入 send_signal → complete_signal → exit_to_user_mode_loop → get_signal → handle_signal → sys_rt_sigreturn 这一条流水线。