本文基于Arm64架构详述Linux的信号处理过程,Linux把信号处理分为俩个部分,第一部分是信号发送,第二部分是信号处理。信号发送部分主要是设置相应进程的信号阻塞位;信号处理则是接收到信号的进程调用信号处理函数。
在内核代码中:
kernel/signal.c
static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
int group)
{
int from_ancestor_ns = 0;
#ifdef CONFIG_PID_NS
from_ancestor_ns = si_fromuser(info) &&
!task_pid_nr_ns(current, task_active_pid_ns(t));
#endif
return __send_signal(sig, info, t, group, from_ancestor_ns);
}
static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
int group, int from_ancestor_ns)
{
struct sigpending *pending;
struct sigqueue *q;
int override_rlimit;
int ret = 0, result;
assert_spin_locked(&t->sighand->siglock);
result = TRACE_SIGNAL_IGNORED;
if (!prepare_signal(sig, t,
from_ancestor_ns || (info == SEND_SIG_FORCED)))
goto ret;
pending = group ? &t->signal->shared_pending : &t->pending; //Task 的阻塞标志
/*
* Short-circuit ignored signals and support queuing
* exactly one non-rt signal, so that we can get more
* detailed information about the cause of the signal.
*/
result = TRACE_SIGNAL_ALREADY_PENDING;
if (legacy_queue(pending, sig))
goto ret;
result = TRACE_SIGNAL_DELIVERED;
/*
........
out_set:
signalfd_notify(t, sig);
sigaddset(&pending->signal, sig); //设置信号位
complete_signal(sig, t, group);
ret:
trace_signal_generate(sig, info, t, group, result);
return ret;
}
由以上函数蓝色标记部分可以看到信号阻塞位的设置。
接收到信号的进程,并不会立即调用信号处理函数,也就是信号量是属于异步的事件处理。接收信号的进程在什么时候调用信号处理函数呢?如何调用信号处理函数呢?信号处理函数是用户态的程序,不可能在内核态调用,这是一个基本的约定。用户态的程序没有权限访问内核态的数据结构和程序。task_struct 是内核的数据结构,在他的信号阻塞位改变时,如果接受进程正在运行,无法知道这个位的设置。
信号的处理是在接收信号的进程,由内核态切换到用户态时,进行处理。
arch/arm64/kernel/entry.S
ret_to_user:
disable_irq // disable interrupts
ldr x1, [tsk, #TSK_TI_FLAGS]
and x2, x1, #_TIF_WORK_MASK
cbnz x2, work_pending
finish_ret_to_user:
enable_step_tsk x1, x2
kernel_exit 0
ENDPROC(ret_to_user)
* Ok, we need to do extra processing, enter the slow path.
*/
work_pending:
mov x0, sp // 'regs'
bl do_notify_resume
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on // enabled while in userspace
#endif
ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for single-step
b finish_ret_to_user
#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \
_TIF_NOTIFY_RESUME | _TIF_FOREIGN_FPSTATE | \
_TIF_UPROBE)
由以上可以看到,如果有未处理的信号量,ret_to_user-》work_pending-》do_notify_resume
arch/arm64/kernel/signal.c
asmlinkage void do_notify_resume(struct pt_regs *regs,
unsigned int thread_flags)
{
/*
* The assembly code enters us with IRQs off, but it hasn't
* informed the tracing code of that for efficiency reasons.
* Update the trace code with the current status.
*/
trace_hardirqs_off();
do {
if (thread_flags & _TIF_NEED_RESCHED) {
schedule();
} else {
local_irq_enable();
if (thread_flags & _TIF_UPROBE)
uprobe_notify_resume(regs);
if (thread_flags & _TIF_SIGPENDING)
do_signal(regs);
if (thread_flags & _TIF_NOTIFY_RESUME) {
clear_thread_flag(TIF_NOTIFY_RESUME);
tracehook_notify_resume(regs);
}
if (thread_flags & _TIF_FOREIGN_FPSTATE)
fpsimd_restore_current_state();
}
local_irq_disable();
thread_flags = READ_ONCE(current_thread_info()->flags);
} while (thread_flags & _TIF_WORK_MASK);
}
ret_to_user-》work_pending-》do_notify_resume-》do_signal
static void do_signal(struct pt_regs *regs)
{
unsigned long continue_addr = 0, restart_addr = 0;
int retval = 0;
int syscall = (int)regs->syscallno;
struct ksignal ksig;
/*
* If we were from a system call, check for system call restarting...
*/
if (syscall >= 0) {
continue_addr = regs->pc;
restart_addr = continue_addr - (compat_thumb_mode(regs) ? 2 : 4);
retval = regs->regs[0];
/*
* Avoid additional syscall restarting via ret_to_user.
*/
regs->syscallno = ~0UL;
/*
* Prepare for system call restart. We do this here so that a
* debugger will see the already changed PC.
*/
switch (retval) {
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
case -ERESTART_RESTARTBLOCK:
regs->regs[0] = regs->orig_x0;
regs->pc = restart_addr;
break;
}
}
/*
* Get the signal to deliver. When running under ptrace, at this point
* the debugger may change all of our registers.
*/
if (get_signal(&ksig)) {
/*
* Depending on the signal settings, we may need to revert the
* decision to restart the system call, but skip this if a
* debugger has chosen to restart at a different PC.
*/
if (regs->pc == restart_addr &&
(retval == -ERESTARTNOHAND ||
retval == -ERESTART_RESTARTBLOCK ||
(retval == -ERESTARTSYS &&
!(ksig.ka.sa.sa_flags & SA_RESTART)))) {
regs->regs[0] = -EINTR;
regs->pc = continue_addr;
}
handle_signal(&ksig, regs);
return;
}
/*
* Handle restarting a different system call. As above, if a debugger
* has chosen to restart at a different PC, ignore the restart.
*/
if (syscall >= 0 && regs->pc == restart_addr) {
if (retval == -ERESTART_RESTARTBLOCK)
setup_restart_syscall(regs);
user_rewind_single_step(current);
}
restore_saved_sigmask();
}
ret_to_user-》work_pending-》do_notify_resume-》do_signal-》handle_signal
/*
* OK, we're invoking a handler
*/
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
{
struct task_struct *tsk = current;
sigset_t *oldset = sigmask_to_save();
int usig = ksig->sig;
int ret;
/*
* Set up the stack frame
*/
if (is_compat_task()) {
if (ksig->ka.sa.sa_flags & SA_SIGINFO)
ret = compat_setup_rt_frame(usig, ksig, oldset, regs);
else
ret = compat_setup_frame(usig, ksig, oldset, regs);
} else {
ret = setup_rt_frame(usig, ksig, oldset, regs);
}
/*
* Check that the resulting registers are actually sane.
*/
ret |= !valid_user_regs(®s->user_regs, current);
/*
* Fast forward the stepping logic so we step into the signal
* handler.
*/
if (!ret)
user_fastforward_single_step(tsk);
signal_setup_done(ret, ksig, 0);
}
ret_to_user-》work_pending-》do_notify_resume-》do_signal-》handle_signal-》setup_rt_frame
static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,
struct pt_regs *regs)
{
struct rt_sigframe __user *frame;
int err = 0;
frame = get_sigframe(ksig, regs);
if (!frame)
return 1;
__put_user_error(0, &frame->uc.uc_flags, err);
__put_user_error(NULL, &frame->uc.uc_link, err);
err |= __save_altstack(&frame->uc.uc_stack, regs->sp);
err |= setup_sigframe(frame, regs, set);
if (err == 0) {
setup_return(regs, &ksig->ka, frame, usig);
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;
}
}
return err;
}
static struct rt_sigframe __user *get_sigframe(struct ksignal *ksig,
struct pt_regs *regs)
{
unsigned long sp, sp_top;
struct rt_sigframe __user *frame;
sp = sp_top = sigsp(regs->sp, ksig);
sp = (sp - sizeof(struct rt_sigframe)) & ~15;
frame = (struct rt_sigframe __user *)sp;
/*
* Check that we can actually write to the signal frame.
*/
if (!access_ok(VERIFY_WRITE, frame, sp_top - sp))
frame = NULL;
return frame;
}
新分配的Frame是在用户进程空间的栈上,就是在当前的栈上分配一个rt_sigframe大小的空间。
ret_to_user-》work_pending-》do_notify_resume-》do_signal-》handle_signal-》setup_rt_frame-》setup_sigframe
static int setup_sigframe(struct rt_sigframe __user *sf,
struct pt_regs *regs, sigset_t *set)
{
int i, err = 0;
void *aux = sf->uc.uc_mcontext.__reserved;
struct _aarch64_ctx *end;
/* set up the stack frame for unwinding */
__put_user_error(regs->regs[29], &sf->fp, err);
__put_user_error(regs->regs[30], &sf->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));
if (err == 0) {
struct fpsimd_context *fpsimd_ctx =
container_of(aux, struct fpsimd_context, head);
err |= preserve_fpsimd_context(fpsimd_ctx);
aux += sizeof(*fpsimd_ctx);
}
/* fault information, if valid */
if (current->thread.fault_code) {
struct esr_context *esr_ctx =
container_of(aux, struct esr_context, head);
__put_user_error(ESR_MAGIC, &esr_ctx->head.magic, err);
__put_user_error(sizeof(*esr_ctx), &esr_ctx->head.size, err);
__put_user_error(current->thread.fault_code, &esr_ctx->esr, err);
aux += sizeof(*esr_ctx);
}
/* set the "end" magic */
end = aux;
__put_user_error(0, &end->magic, err);
__put_user_error(0, &end->size, err);
return err;
}
该函数实现把原来的用户态的寄存器值,也即是上下文保存到新分配的Frame。
ret_to_user-》work_pending-》do_notify_resume-》do_signal-》handle_signal-》setup_rt_frame-》setup_return
static void setup_return(struct pt_regs *regs, struct k_sigaction *ka,
void __user *frame, int usig)
{
__sigrestore_t sigtramp;
regs->regs[0] = usig;
regs->sp = (unsigned long)frame;
regs->regs[29] = regs->sp + offsetof(struct rt_sigframe, 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;
}
在该函数中,设置寄存器0, SP, FP, LR, PC。 PC指向信号处理函数,LR指向VDSO_sigtramp
在ret_to_user返回到用户态时,信号处理函数,就被调用了。
信号处理函数处理结束后,要调用VDSO_sigtramp
arch/arm64/kernel/vdso/vdso.lds.S:
VDSO_sigtramp = __kernel_rt_sigreturn;
这是内核给用户态的程序提供的一个接口。arch/arm64/kernel/vdso/vdso.lds.S
ENTRY(__kernel_rt_sigreturn)
.cfi_startproc
.cfi_signal_frame
.cfi_def_cfa x29, 0
.cfi_offset x29, 0 * 8
.cfi_offset x30, 1 * 8
mov x8, #__NR_rt_sigreturn
svc #0
.cfi_endproc
ENDPROC(__kernel_rt_sigreturn)
__kernel_rt_sigreturn,做一个系统调用,查询系统调用表,对应的函数是sys_rt_sigreturn
asmlinkage long sys_rt_sigreturn(struct pt_regs *regs)
{
struct rt_sigframe __user *frame;
/* Always make any pending restarted system calls return -EINTR */
current->restart_block.fn = do_no_restart_syscall;
/*
* Since we stacked the signal on a 128-bit boundary, then 'sp' should
* be word aligned here.
*/
if (regs->sp & 15)
goto badframe;
frame = (struct rt_sigframe __user *)regs->sp;
if (!access_ok(VERIFY_READ, frame, sizeof (*frame)))
goto badframe;
if (restore_sigframe(regs, frame))
goto badframe;
if (restore_altstack(&frame->uc.uc_stack))
goto badframe;
return regs->regs[0];
badframe:
if (show_unhandled_signals)
pr_info_ratelimited("%s[%d]: bad frame in %s: pc=%08llx sp=%08llx\n",
current->comm, task_pid_nr(current), __func__,
regs->pc, regs->sp);
force_sig(SIGSEGV, current);
return 0;
}
sys_rt_sigreturn-》restore_sigframe
static int restore_sigframe(struct pt_regs *regs,
struct rt_sigframe __user *sf)
{
sigset_t set;
int i, err;
void *aux = sf->uc.uc_mcontext.__reserved;
err = __copy_from_user(&set, &sf->uc.uc_sigmask, sizeof(set));
if (err == 0)
set_current_blocked(&set);
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);
/*
* Avoid sys_rt_sigreturn() restarting.
*/
regs->syscallno = ~0UL;
err |= !valid_user_regs(®s->user_regs, current);
if (err == 0) {
struct fpsimd_context *fpsimd_ctx =
container_of(aux, struct fpsimd_context, head);
err |= restore_fpsimd_context(fpsimd_ctx);
}
return err;
}
restore_sigframe把上面存储的上下文信息,重新恢复了
当程序由系统调用返回时,进程开始执行以前被打断的指令。
整个信号处理过程就结束了。