Linux Signal handling(信号处理)Arm64

来源:互联网 发布:mysql恢复删除的数据 编辑:程序博客网 时间:2024/06/11 19:46

本文基于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(&regs->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(&regs->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把上面存储的上下文信息,重新恢复了

当程序由系统调用返回时,进程开始执行以前被打断的指令。

整个信号处理过程就结束了。








 

原创粉丝点击