目录
(代码:linux 6.3.1,架构:arm64)
One look is worth a thousand words. —— Tess Flanders
相关链接:
阅读本文前,让我们一起思考几个问题:
1)任务一般什么时候会响应并处理信号?
2)对于SIGKILL的信号,其处理流程是怎样的?
3)若信号没有注册任何处理函数,任务接收到这类信号后的处理逻辑是什么?
4)若任务上同时有多个待处理的信号,其执行流程是怎样的?
5)任务执行系统调用时,被信号打断后,该如何处理?
6)信号处理函数,能否改变程序原本的执行轨迹?什么场景下会这么干?
一、信号处理的时机
在linux中,信号的处理时机:任务从内核态返回用户态前夕。不论任务是由于系统调用、data_abort同步异常、中断等任何哪种方式陷入的内核,在其返回用户态前夕,内核代码都会检查当前任务上是否存在待处理的信号!
// 任何从EL0陷入EL1的方式, 在处理完后, 都会调用exit_to_user_mode, 该函数中会去检查是否有信号需要处理!
entry_handler
el0t_64_sync_handler / el0t_64_irq_handler / el0t_64_fiq_handler / el0t_64_error_handler
...
exit_to_user_mode(regs) //<<<<<< 其中会判断是否有pending信号需要处理
.if \el == 0
b ret_to_user
在exit_to_user_mode中,会检查当前任务的很多待处理的事项,其中有一项就是:是否存在pending的信号需要处理。
/* 返回用户态前夕 */
exit_to_user_mode(struct pt_regs *regs) {
prepare_exit_to_user_mode(struct pt_regs *regs = regs) {
local_daif_mask()
flags = read_thread_flags();
if (unlikely(flags & _TIF_WORK_MASK))
do_notify_resume(struct pt_regs *regs = regs, unsigned long thread_flags = flags)
{
do {
local_daif_restore(DAIF_PROCCTX)
if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
do_signal(regs)
{
### Detail see below
}//do_signal
local_daif_mask()
thread_flags = read_thread_flags()
} while (thread_flags & _TIF_WORK_MASK);
}//do_notify_resume
}//prepare_exit_to_user_mode
__exit_to_user_mode()
}//exit_to_user_mode
二、信号处理实现原理
内核中,信号处理的流程比较复杂,所以接下来会将其拆解成 4 部分,各自详细介绍。
2.1)整体流程
信号在内核中的处理流程如下:
1)判断当前任务是否处于syscall系统调用中;
2)若处于的话,说明当前任务的系统调用被信号打断了,可能需要执行syscall restart的动作,设置对应的寄存器(这块后面章节会详细介绍);
3)获取pending的信号,可能是线程私有的pending信号、也可能是线程共享的pending信号;(下面会详细分析)
4)若pending的信号,其注册时不支持syscall restart的话,恢复原本的task上下文中的pc,并且设置系统调用返回值(即:x[0])为-EINTR,代表系统调用被信号打断的了,需要用户态进行处理;
5)handle_signal中主要做的事情是:设置任务返回用户态,执行信号处理函数时,所需要的用户栈、寄存器等内容(下面会详细分析);
6)当为本次获取到的pending信号做好准备工作后,接着尝试获取任务上其他pending信号,直到任务不再置位TIF_SIGPENDING;
7)若任务上没有可以处理的pending信号,则从do_signal返回,最终返回用户态去执行信号处理函数;
注意:在do_signal中,是会循环调用get_signal、handle_signal,这就说明如果当任务上有多个可处理的pending信号,在内核的do_signal中,会为它们一一分配好对应的用户栈和上下文,这样任务返回用户态后,会依次执行所有准备好的pending信号!(细节见第4章)
2.2)获取信号
get_signal 的处理流程如下:
1)判断当前进程是否有置位SIGNAL_GROUP_EXIT;
2)若置位了SIGNAL_GROUP_EXIT,且当前pending信号是需要执行coredump流程的信号的话,则调用do_coredump执行coredump流程(具体实现在Linux coredump原理 图文详解一文中有详细介绍)
3)若不是需要coredump的信号,则调用do_exit 执行线程退出流程;
4)尝试获取pending信号,依次查找synchronous signal、线程私有信号pending队列(task->pending)、线程共享信号pending队列(task->signal->pending),找到一个pending信号即可;
5)判断当前任务是否处于gdb调试状态,若是的话,调用ptrace_signal,给父进程ptracer发送SIGCHLD,并唤醒父进程,随后将自身挂起(详细内容可参考:ptrace系列文章);
6)获取pending信号,在内核中记录的k_sigaction对象;
7)若信号的处理函数为SIG_IGN,则代表该信号当前进程不做处理直接忽略,则返回第1步重新尝试获取其他pending信号;
8)若信号的处理函数为SIG_DFL,且是SIG_STOP类型的信号,则将所有其他线程都唤醒,去执行sig_stop的处理,随后当前线程也调用schedule调度挂起,直到之后的SIGCONT会将其唤醒;
9)若信号的处理函数为SIG_DFL,且不属于SIG_STOP,则默认这类信号的处理与SIGKILL信号的处理一致,将当前进程杀死。于是会给进程置位SIGNAL_GROUP_EXIT,并给每个线程的私有pending信号队列置位SIGKILL,并唤醒其他线程,最后自身调用do_exit 执行线程退出流程;
10)若获取的pending信号,注册了非SIG_DFL、SIG_IGN的其他处理函数,则将k_sigaction对象设置到入参“ksig->ka”中,并返回signr;
【get_signal针对一些特殊信号处理】
1)对于需要coredump的信号,不会返回到用户态了,而是在内核中调用do_coredump,走coredump流程;
2)对于STOP类型的信号,会将其他线程置位SIG_STOP并唤醒它们去立刻挂起;
3)对于处理函数为SIG_DFL的信号,其行为与SIGKILL一致,都是将整个进程杀死,走退出流程;
【获取pending信号的优先级】
从上述流程可以看到,get_signal的时候,会按照一定顺序去拿去pending信号,顺序如下:
synchronous信号 ——> 线程私有pending信号 ——> 线程共享pending信号
其中,synchronous信号,需要在其它信号之前被传递和处理,以应对严重的运行时错误,如:SIGSEGV, SIGILL 等
2.3)handle signal
handle_signal 的处理流程如下:
1)设置用户态信号处理时的用户栈空间;
2)首先将当前task在内核中记录的“用户态上下文”,保存到用户栈中,这是为了用户态信号处理函数执行完毕并返回到内核后,重新恢复原本task需要返回的上下文!
3)既然原本的task上下文已经备份好了,那么就可以重新设置task的用户态上下文了,主要涉及几个寄存器:x[0]、sp、x29、pc、x30。这里的x30寄存器十分重要,当用户态的信号处理函数执行完毕后,需要再次陷入内核,这里就是借助x30执行的函数去完成这个动作的(后面会详细介绍);
4)若注册的信号处理函数需要siginfo信息,则将siginfo的信息也设置到用户栈中,并设置好x[1]、x[2]这两个用作信号处理函数入参的寄存器;
5)若在setup_rt_frame设置用户栈的期间发生了错误,例如用户栈溢出等问题,就认定当前进程异常了,无法继续正常执行,所以给当前进程发送SIGSEGV信号,让其走coredump流程;
6)设置任务在用户态执行信号处理函数时的信号屏蔽掩码,由:“当前待处理的信号”、以及“当前信号的处理函数注册时指定的屏蔽信号集” 组成;
7)若进程中,还存在pending的进程信号(即:线程共享pending队列中的信号),且这些共享的pending信号当前任务由于信号屏蔽掩码的原因无法处理的话,就尝试唤醒进程中其他可以处理这些shared pending信号的线程去做信号处理。这么做也是为了保证信号尽快得到处理!
以上都做完后,当任务从内核态恢复上下文,并eret返回到用户态后,就会跳转到信号处理函数去执行了。
2.4)信号返回
到这里,我们知道了内核是如何让task返回用户态执行信号处理函数的了,但有一个问题还没有解释清楚:用户态sig_handler执行完后,程序如何跑回原本的代码执行流程中?
这就是信号返回(sigreturn)所需要负责的事情了!
在介绍内核sigreturn的实现之前,先看一下C库中注册信号处理函数时的一些行为,这里以Musl-C为例:
int __libc_sigaction(int sig, const struct sigaction *restrict sa, struct sigaction *restrict old) {
struct k_sigaction ksa, ksa_old;
ksa.handler = sa->sa_handler
ksa.flags = sa->sa_flags
ksa.flags |= SA_RESTORER
ksa.restorer = (sa->sa_flags & SA_SIGINFO) ? __restore_rt : __restore
memcpy(&ksa.mask, &sa->sa_mask, _NSIG/8)
int r = __syscall(SYS_rt_sigaction, sig, sa?&ksa:0, old?&ksa_old:0, _NSIG/8)
if (old && !r) {
old->sa_handler = ksa_old.handler
old->sa_flags = ksa_old.flags
memcpy(&old->sa_mask, &ksa_old.mask, _NSIG/8)
}
return __syscall_ret(r)
}
这里,Musl-C将 ksa.restorer 的地址,设置为了 __restore_rt 或 __restore。ksa.restorer的值,在do_signal中,就会被设置到task上下文中的x[30]中,也就是用户态信号处理函数执行完毕后,执行ret的函数返回地址!
那么我们来看一下__restore_rt 、__restore 的实现是什么:
.global __restore
.hidden __restore
.type __restore,%function
__restore:
.global __restore_rt
.hidden __restore_rt
.type __restore_rt,%function
__restore_rt:
mov x8,#139 // SYS_rt_sigreturn
svc 0
可以看到,__restore_rt 、__restore 的实现是一样的,都是调用 svc 触发系统调用陷入内核,同时,系统调用号设置为 139,即:SYS_rt_sigreturn;
在内核中,针对rt_sigreturn的实现如下:
内核中,rt_sigreturn做的主要事情如下:
1)恢复原本的task信号屏蔽掩码;
2)从用户栈中,恢复task原本的用户态上下文;
当rt_sigreturn执行完毕,并返回时,就是返回到信号处理前,task原本的用户态返回地址去执行!这样一来,用户态正常执行的代码就能无感的照常执行原本的代码逻辑了!
整个一套流程如下图所示:
三、系统调用被信号打断后的restart处理
在linux中,不是所有的系统调用都能在内核中执行完毕后会立刻返回的,对于一些可能会将当前任务挂起阻塞的系统调用(如:futex、wait 等),在挂起期间,若收到了一个信号被唤醒,那么在内核中会从挂起的地方唤醒继续执行,但是可能此时的系统调用并未执行成功(或者叫执行完毕),那么在从内核的系统调用路径层层返回后,最终同样会在返回用户态前夕检查是否有pending信号需要处理;这种情况就是所谓的被信号打断的syscall调用。
那么有什么办法,可以让程序自动的再次触发一次系统调用,使得用户对本次被信号打断的系统调用行为无感呐?
答案是有的!对于一些允许被信号打断后restart的系统调用而言,其会设置一些特定的返回值,如:ERESTARTNOHAND、ERESTART_RESTARTBLOCK、ERESTARTSYS;
当打断系统调用的信号,在注册期间也指定了SA_RESTART这个标志位的话,就好办了。说明这种情况下,内核可以手动的修改task上下文中的寄存器,让代码在处理完信号后,再次触发一次系统调用。其实现原理,就是在do_signal中,针对被打断的syscall场景,修改task寄存器上下文中的pc、x[0]。
其中,pc修改为“pc-4”、x[0]修改为orig_x0;
这里解释下,为什么仅仅修改PC、x[0],就能达到 restart syscall 的效果。在arm64中,执行svc指令后,会触发同步异常,陷入内核,同时PC会被CPU自动加4,执行svc的下一条指令,那么只要我们将PC减4,就能使得task处理完信号返回用户态执行的代码,再次指向svc 0,这样就能再一次的触发系统调用陷入内核了。
当然,仅仅修改pc是不够的,还需要配合修改x[0],细心的小伙伴可能会问:“那又为什么只需要修改x[0]呐?” 哈哈哈哈,那是因为其他x[1]~x[30]的内容,都有保存在task的寄存器上下文中,而唯独x[0],是会在内核系统调用执行期间,被修改为系统调用的返回值的。因此,这里需要将x[0]恢复成系统调用首次陷入时缓存在task中的orig_x0;
四、任务上有多个待处理的pending信号
思考2个问题:
1)do_notify_resume中,为什么需要 do-while (thread_flags & _TIF_WORK_MASK) 去调用 do_signal ?
do_notify_resume(struct pt_regs *regs = regs, unsigned long thread_flags = flags)
{
do {
local_daif_restore(DAIF_PROCCTX)
if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
do_signal(regs) // <<<<<<<<<<
local_daif_mask()
thread_flags = read_thread_flags()
} while (thread_flags & _TIF_WORK_MASK);
}//do_notify_resume
因为,此时此刻,任务上可能存在不止一个pending的信号,所以需要一直调用do_signal去获取pending信号,并准备好用户栈的布局、以及相应的寄存器上下文,直到task上不再置位TIF_SIGPENDING为止!
2)对于存在多个pending信号的场景,用户栈的构造布局又是怎样的?
有点写不动了。。。直接上图!
五、用户态信号处理函数修改程序的运行轨迹
在handle_signal的代码中,setup_sigframe函数会将pc、sp设置到一个用户态信号处理函数能够访问到的数据结构uc_mcontext中,同时在sigreturn时,内核也是从uc_mcontext中恢复原本任务的用户态返回地址的!
setup_sigframe(&user, regs, set) {
struct rt_sigframe __user *sf = user->sigframe
__put_user_error(regs->sp, &sf->uc.uc_mcontext.sp, err) // <<<<<<<<<<
__put_user_error(regs->pc, &sf->uc.uc_mcontext.pc, err) // <<<<<<<<<<
__put_user_error(regs->pstate, &sf->uc.uc_mcontext.pstate, err)
__put_user_error(current->thread.fault_address, &sf->uc.uc_mcontext.fault_address, err)
err |= __copy_to_user(&sf->uc.uc_sigmask, set, sizeof(*set))
return err
}
那么问题来了,如果用户态的信号处理函数,修改了uc_mcontext中的pc,不就会导致任务处理完信号sigreturn陷入内核后,最终返回的用户态地址被篡改了么!
的确,在Musl-C的pthread_cancel中,就这么干了,其中会修改uc_mcontext->pc,使得task处理完信号后,返回的用户态执行地址,被篡改到了其他地方。代码实现如下:
1)pthread_cancel 实现
int pthread_cancel(pthread_t t) {
static int init
if (!init) {
init_cancellation();
init = 1;
}
a_store(&t->cancel, 1);
if (t == pthread_self()) {
if (t->canceldisable == PTHREAD_CANCEL_ENABLE && t->cancelasync)
pthread_exit(PTHREAD_CANCELED);
return 0;
}
return pthread_kill(t, SIGCANCEL) // <<<< 发送信号给目标线程
}
2)init_cancellation 函数
static void init_cancellation()
{
struct sigaction sa = {
.sa_flags = SA_SIGINFO | SA_ONSTACK,
.sa_sigaction = cancel_handler
};
memset(&sa.sa_mask, -1, _NSIG/8);
__libc_sigaction(SIGCANCEL, &sa, 0);
}
这个函数主要就是为当前进程注册一个SIGCANCEL信号的处理函数:cancel_handler;
3)cancel_handler 函数
static void cancel_handler(int sig, siginfo_t *si, void *ctx)
{
pthread_t self = __pthread_self();
ucontext_t *uc = ctx;
uintptr_t pc = uc->uc_mcontext.MC_PC;
a_barrier();
if (!self->cancel || self->canceldisable == PTHREAD_CANCEL_DISABLE) return;
_sigaddset(&uc->uc_sigmask, SIGCANCEL);
if (self->cancelasync) {
pthread_sigmask(SIG_SETMASK, &uc->uc_sigmask, 0);
__cancel();
}
if (pc >= (uintptr_t)__cp_begin && pc < (uintptr_t)__cp_end) {
uc->uc_mcontext.MC_PC = (uintptr_t)__cp_cancel; // <<<<<<< 修改了PC!
#ifdef CANCEL_GOT
uc->uc_mcontext.MC_GOT = CANCEL_GOT;
#endif
return;
}
__syscall(SYS_tkill, self->tid, SIGCANCEL);
}
我们看到,在cancel_handler中,会将uc->uc_mcontext.MC_PC,修改为__cp_cancel!
4)__cp_cancel 函数
__cp_cancel:
b __cancel
long __cancel()
{
pthread_t self = __pthread_self();
if (self->canceldisable == PTHREAD_CANCEL_ENABLE || self->cancelasync)
pthread_exit(PTHREAD_CANCELED); // <<<<<<< 调用pthread_exit退出
self->canceldisable = PTHREAD_CANCEL_DISABLE;
return -ECANCELED;
}
最终,__cp_cancel会调用pthread_exit执行线程退出流程!
Musl-C中,在SIGCANCEL信号处理函数中修改了uc_mcontext->pc的目的,是为了让被pthread_cancel杀死的线程,在处理完pthread_cancel发送的SIGCANCEL信号后,自己在用户态调用pthread_exit,执行线程退出流程!
六、代码实现
1、do_signal实现
do_signal(struct pt_regs *regs) {
unsigned long continue_addr = 0, restart_addr = 0
struct ksignal ksig
bool syscall = in_syscall(regs) {
return regs->syscallno != NO_SYSCALL
}
/* 1) 判断当前任务是否正在执行系统调用, 若是的话说明本次系统调用被信号打断了 */
if (syscall) {
/* 2) 若处于系统调用中, 则记录返回用户态的指令地址、原本触发系统调用的指令地址、原本的系统调用返回值 */
continue_addr = regs->pc
restart_addr = continue_addr - (compat_thumb_mode(regs) ? 2 : 4)
retval = regs->regs[0]
forget_syscall(regs) {
regs->syscallno = NO_SYSCALL
}
/* 3) 若处于系统调用中, 先修改“task上下文”中的regs[0]、pc, 为syscall restart作准备 */
switch (retval) {
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
case -ERESTART_RESTARTBLOCK:
regs->regs[0] = regs->orig_x0
regs->pc = restart_addr
break
}//switch (retval)
}//if (syscall)
/* 4) 获取一个需要处理的pending信号, 并将其k_sigaction记录到入参ksig中; 对于SIGSTOP、fatal类型的信号, 做一些相应处理 */
has_pending_signal = get_signal(&ksig)
{
struct sighand_struct *sighand = current->sighand
struct signal_struct *signal = current->signal
if (!task_sigpending(current))
return false
/* can't return to user-mode if freezing() == T */
try_to_freeze()
relock:
spin_lock_irq(&sighand->siglock)
/* 4.1) 若是SIGSTOP/SIGCONT类型的信号, 则发送SIGCHLD信号给父进程, 并唤醒父进程 */
if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
if (signal->flags & SIGNAL_CLD_CONTINUED)
why = CLD_CONTINUED
else
why = CLD_STOPPED
signal->flags &= ~SIGNAL_CLD_MASK
spin_unlock_irq(&sighand->siglock)
do_notify_parent_cldstop(current, false, why)
goto relock
}
for (;;) {
struct k_sigaction *ka
enum pid_type type
/* 4.2) 若当前进程需要走退出流程, 则清除当前任务私有pending信号队列中的SIGKILL位 */
if ((signal->flags & SIGNAL_GROUP_EXIT) || signal->group_exec_task) {
ksig->info.si_signo = signr = SIGKILL
sigdelset(¤t->pending.signal, SIGKILL)
recalc_sigpending() {
if (!recalc_sigpending_tsk(current) && !freezing(current))
clear_thread_flag(TIF_SIGPENDING)
}//recalc_sigpending
goto fatal
}
if (unlikely(current->jobctl & JOBCTL_STOP_PENDING) && do_signal_stop(0))
goto relock
type = PIDTYPE_PID
/* 4.3) 获取当前task的私有pending信号队列中的synchronous信号(若存在) */
signr = dequeue_synchronous_signal(kernel_siginfo_t *info = &ksig->info)
if (!signr)
/* 4.4) 若不存在synchronous信号, 则尝试获取私有pending信号队列中的普通信号、或还没有则尝试获取线程共享的pending信号队列中的信号 */
signr = dequeue_signal(current, ¤t->blocked, &ksig->info, &type)
{
*type = PIDTYPE_PID
signr = __dequeue_signal(&tsk->pending, mask, info, &resched_timer)
if (!signr) {
*type = PIDTYPE_TGID
signr = __dequeue_signal(&tsk->signal->shared_pending, mask, info, &resched_timer)
}
/* 重新计算当前任务上是否还有pending待处理的信号 */
recalc_sigpending()
return signr
}//dequeue_signal
/* 4.5) 若当前任务处于ptraced状态、且pending信号不是SIGKILL, 则调用ptrace_signal将当前任务挂起并通知父进程 */
if (unlikely(current->ptrace) && (signr != SIGKILL) && !(sighand->action[signr -1].sa.sa_flags & SA_IMMUTABLE)) {
signr = ptrace_signal(signr, &ksig->info, type)
if (!signr)
continue
}
ka = &sighand->action[signr-1]
if (ka->sa.sa_handler == SIG_IGN)
continue
/* 4.6) 若待处理的信号, 有注册信号处理函数, 则将信号对应的k_sigaction对象记录到入参ksig中, 并从get_signal函数返回 */
if (ka->sa.sa_handler != SIG_DFL) {
ksig->ka = *ka
if (ka->sa.sa_flags & SA_ONESHOT)
ka->sa.sa_handler = SIG_DFL
break
}
if (sig_kernel_ignore(signr))
continue
/* 4.7) 若是SIGSTOP类型的信号, 则唤醒所有其他线程去同样执行信号处理并挂起, 然后当前task也将自身挂起调度其他任务运行 */
if (sig_kernel_stop(signr)) {
if (likely(do_signal_stop(ksig->info.si_signo))) { // <<<<< 这里面会调用schedule()挂起!待之后的SIGCONT唤醒当前task
goto relock
}
continue
}
fatal:
spin_unlock_irq(&sighand->siglock)
current->flags |= PF_SIGNALED
/* 4.8) 若信号属于需要产生coredump的信号类型, 则执行coredump流程 */
if (sig_kernel_coredump(signr)) {
do_coredump(&ksig->info)
}
/* 4.9) 若信号不需要产生coredump, 仅仅是Death signal, 则唤醒给进行中每个线程置位SIGKILL pending位, 并唤醒所有其他线程, 最后走do_exit退出流程 */
do_group_exit(ksig->info.si_signo) {
struct signal_struct *sig = current->signal
if (sig->flags & SIGNAL_GROUP_EXIT)
exit_code = sig->group_exit_code
else if (sig->group_exec_task)
exit_code = 0
else {
struct sighand_struct *const sighand = current->sighand
spin_lock_irq(&sighand->siglock)
if (sig->flags & SIGNAL_GROUP_EXIT)
exit_code = sig->group_exit_code
else if (sig->group_exec_task)
exit_code = 0
else {
sig->group_exit_code = exit_code
sig->flags = SIGNAL_GROUP_EXIT
zap_other_threads(struct task_struct *p = current) {
struct task_struct *t = p
while_each_thread(p, t) {
task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK)
if (t->exit_state) /* Don't bother with already dead threads */
continue
sigaddset(&t->pending.signal, SIGKILL)
signal_wake_up(t, 1)
}
}//zap_other_threads
}
}
do_exit(exit_code)
}//do_group_exit
}//for (;;)
spin_unlock_irq(&sighand->siglock)
out:
ksig->sig = signr
return ksig->sig > 0
}//get_signal
if (has_pending_signal) {
/* 5) 若本次信号处理位于系统调用中, 且信号注册时不支持syscall被打断后restart的话, 则恢复原本的用户态返回地址, 并设置返回系统调用的范湖一直为-EINTR */
if (regs->pc == restart_addr && (retval == -ERESTARTNOHAND || retval == -ERESTART_RESTARTBLOCK ||
(retval == -ERESTARTSYS &&!(ksig.ka.sa.sa_flags & SA_RESTART))))
{
syscall_set_return_value(current, regs, -EINTR, 0)
regs->pc = continue_addr
}
/* 6) 设置用户态sig_handler执行时需要的栈空间内容、上下文(pc、lr、x0、x1、x2、sp), 并在用户栈中备份task原本返回用户态的上下文 */
handle_signal(&ksig, regs)
{
sigset_t *oldset = sigmask_to_save()
int usig = ksig->sig
/* 6.1) 设置用户态sig_handler函数执行时的栈空间内容, 并保存当前task的用户态上下文到用户栈中
* Set up the stack frame */
ret = setup_rt_frame(usig, ksig, oldset, regs)
{
struct rt_sigframe_user_layout user
/* 6.1.1) 设置信号处理函数的栈指针, 以及用于保存task原本返回用户态上下文的栈地址 */
res = get_sigframe(&user, ksig, regs) {
init_user_layout(user)
/* Determine the layout of optional records in the signal frame */
setup_sigframe_layout(struct rt_sigframe_user_layout *user = user, false)
sp = sp_top = sigsp(regs->sp, ksig)
sp = round_down(sp - sizeof(struct frame_record), 16)
user->next_frame = (struct frame_record __user *)sp
sp = round_down(sp, 16) - sigframe_size(user)
user->sigframe = (struct rt_sigframe __user *)sp
if (!access_ok(user->sigframe, sp_top - sp))
return -EFAULT
return 0
}//get_sigframe
if (res)
return 1
frame = user.sigframe
__put_user_error(0, &frame->uc.uc_flags, err)
__put_user_error(NULL, &frame->uc.uc_link, err)
/* 6.1.2) 为需要使用信号栈做准备, 记录信号栈的信息 */
err |= __save_altstack(stack_t __user *uss = &frame->uc.uc_stack, regs->sp) {
struct task_struct *t = current
int err = __put_user((void __user *)t->sas_ss_sp, &uss->ss_sp) |
__put_user(t->sas_ss_flags, &uss->ss_flags) |
__put_user(t->sas_ss_size, &uss->ss_size)
return err
}
/* 6.1.3) 将当前task原本返回用户态的上下文信息先保存到用户态的栈空间中, 并将sp、pc记录到uc_mcontext中,
* 用户态sig_handler可能会修改pc、sp来改变task处理完信号后返回到用户态的地址(如: pthread_cancel的实现中会这么干) */
err |= setup_sigframe(&user, regs, set) {
struct rt_sigframe __user *sf = user->sigframe
__put_user_error(regs->regs[29], &user->next_frame->fp, err)
__put_user_error(regs->regs[30], &user->next_frame->lr, err)
for (i = 0; i < 31; i++)
__put_user_error(regs->regs[i], &sf->uc.uc_mcontext.regs[i], err)
__put_user_error(regs->sp, &sf->uc.uc_mcontext.sp, err)
__put_user_error(regs->pc, &sf->uc.uc_mcontext.pc, err)
__put_user_error(regs->pstate, &sf->uc.uc_mcontext.pstate, err)
__put_user_error(current->thread.fault_address, &sf->uc.uc_mcontext.fault_address, err)
err |= __copy_to_user(&sf->uc.uc_sigmask, set, sizeof(*set))
return err
}
if (err == 0) {
/* 6.1.4) 设置task上下文里用于返回用户态执行信号处理函数的相关寄存器(参数x[0]、栈指针sp、栈帧x29、函数入口地址pc、函数返回地址x30) */
setup_return(regs, struct k_sigaction *ka = &ksig->ka, &user, usig)
{
__sigrestore_t sigtramp
regs->regs[0] = usig
regs->sp = (unsigned long)user->sigframe
regs->regs[29] = (unsigned long)&user->next_frame->fp
regs->pc = (unsigned long)ka->sa.sa_handler
if (ka->sa.sa_flags & SA_RESTORER)
sigtramp = ka->sa.sa_restorer
else
sigtramp = VDSO_SYMBOL(current->mm->context.vdso, sigtramp)
regs->regs[30] = (unsigned long)sigtramp
}//setup_return
/* 6.1.5) 若注册的信号处理函数需要提供SIGINFO信息, 则设置好siginfo对象并将相关地址设置给x1、x2作为信号处理函数的入参 */
if (ksig->ka.sa.sa_flags & SA_SIGINFO) {
err |= copy_siginfo_to_user(&frame->info, &ksig->info)
regs->regs[1] = (unsigned long)&frame->info
regs->regs[2] = (unsigned long)&frame->uc
}
}
}//setup_rt_frame
/* Check that the resulting registers are actually sane. */
ret |= !valid_user_regs(struct user_pt_regs *regs = ®s->user_regs, struct task_struct *task = current)
{
user_regs_reset_single_step(regs, task)
return valid_native_regs(regs) {
regs->pstate &= ~SPSR_EL1_AARCH64_RES0_BITS
if (user_mode(regs) && !(regs->pstate & PSR_MODE32_BIT) &&
(regs->pstate & PSR_D_BIT) == 0 &&
(regs->pstate & PSR_A_BIT) == 0 &&
(regs->pstate & PSR_I_BIT) == 0 &&
(regs->pstate & PSR_F_BIT) == 0)
{
return 1
}
regs->pstate &= PSR_N_BIT | PSR_Z_BIT | PSR_C_BIT | PSR_V_BIT
return 0
}
}//valid_user_regs
signal_setup_done(failed = ret, ksig, int stepping = test_thread_flag(TIF_SINGLESTEP)) {
/* 6.2) 若上面准备信号执行函数的栈空间内容时, 发生了错误(如栈溢出等), 则给当前进程发送SIGSEGV触发coredump */
if (failed)
{
force_sigsegv(ksig->sig) {
if (sig == SIGSEGV)
force_fatal_sig(SIGSEGV)
else
force_sig(SIGSEGV)
}
} else {
/* 6.3) 更新当前task的信号屏蔽掩码(信号掩码为: 当前待处理的信号、以及当前信号的处理函数注册时指定的屏蔽信号集) */
signal_delivered(ksig, stepping) {
clear_restore_sigmask()
sigorsets(&blocked, ¤t->blocked, &ksig->ka.sa.sa_mask)
set_current_blocked(&blocked) {
sigdelsetmask(newset, sigmask(SIGKILL) | sigmask(SIGSTOP))
__set_current_blocked(newset) {
if (sigequalsets(&tsk->blocked, newset))
return
spin_lock_irq(&tsk->sighand->siglock)
__set_task_blocked(tsk, newset) {
if (task_sigpending(tsk) && !thread_group_empty(tsk)) {
sigset_t newblocked
sigandnsets(&newblocked, newset, ¤t->blocked)
/* 6.4) 唤醒其他可处理的线程去处理当前任务无法处理的shared pending信号 */
retarget_shared_pending(tsk, &newblocked) {
sigandsets(&retarget, &tsk->signal->shared_pending.signal, which)
if (sigisemptyset(&retarget))
return
t = tsk
while_each_thread(tsk, t) {
if (t->flags & PF_EXITING)
continue
if (!has_pending_signals(&retarget, &t->blocked))
continue
/* Remove the signals this thread can handle. */
sigandsets(&retarget, &retarget, &t->blocked)
if (!task_sigpending(t))
signal_wake_up(t, 0)
if (sigisemptyset(&retarget))
break
}
}//retarget_shared_pending
}
}//__set_task_blocked
spin_unlock_irq(&tsk->sighand->siglock)
}//__set_current_blocked
}//set_current_blocked
}//signal_delivered
}
}//signal_setup_done
}//handle_signal
return
}
restore_saved_sigmask()
}//do_signal
2、rt_sigreturn实现
SYSCALL_DEFINE0(rt_sigreturn)
{
struct pt_regs *regs = current_pt_regs()
struct rt_sigframe __user *frame
current->restart_block.fn = do_no_restart_syscall
frame = (struct rt_sigframe __user *)regs->sp
if (!access_ok(frame, sizeof (*frame)))
goto badframe
ret = restore_sigframe(regs, struct rt_sigframe __user *sf = frame) {
/* 1) 恢复原本的task信号屏蔽掩码 */
err = __copy_from_user(&set, &sf->uc.uc_sigmask, sizeof(set))
if (err == 0)
set_current_blocked(&set)
/* 2) 恢复当前task原本返回用户态的上下文 */
for (i = 0; i < 31; i++)
__get_user_error(regs->regs[i], &sf->uc.uc_mcontext.regs[i], err)
__get_user_error(regs->sp, &sf->uc.uc_mcontext.sp, err)
__get_user_error(regs->pc, &sf->uc.uc_mcontext.pc, err)
__get_user_error(regs->pstate, &sf->uc.uc_mcontext.pstate, err)
forget_syscall(regs)
regs->syscallno = NO_SYSCALL
}//restore_sigframe
if (ret)
goto badframe
ret = restore_altstack(&frame->uc.uc_stack) {
copy_from_user(&new, uss, sizeof(stack_t))
do_sigaltstack(&new, NULL, current_user_stack_pointer(), MINSIGSTKSZ)
}//restore_altstack
if (ret)
goto badframe
/* 返回后, 最终回到用户态信号处理前原本返回的位置 */
return regs->regs[0]
badframe:
arm64_notify_segfault(regs->sp)
return 0
}
七、总结
信号处理在内核中的实现,主要就是获取pending信号,为pending信号设置好用户栈、寄存器上下文。
当用户态信号处理完毕后,会借助sigreturn通过svc的方式再次陷入内核,进而恢复到原本的用户态上下文。