歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

linux內核中的信號機制--信號處理

Kernel version:2.6.14
CPU architecture:ARM920T
Author:ce123(http://blog.csdn.net/ce123)
當進程被調度時,會調用do_notify_resume()來處理信號隊列中的信號。信號處理主要就是調用sighand_struct結構中對應的信號處理函數。do_notify_resume()(arch/arm/kernel/signal.c)函數的定義如下:
asmlinkage void  
do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall)  
{  
    if (thread_flags & _TIF_SIGPENDING)  
        do_signal(&current->blocked, regs, syscall);  
}

_TIF_SIGPENDING標志是在signal_wake_up()函數中設置的,檢查該標志後,接下來就調用do_signal()函數,我們來看看do_signal()(arch/arm/kernel/signal.c)的具體定義:
/*  
 * Note that 'init' is a special process: it doesn't get signals it doesn't  
 * want to handle. Thus you cannot kill init even with a SIGKILL even by  
 * mistake.  
 *  
 * Note that we go through the signals twice: once to check the signals that  
 * the kernel can handle, and then we build all the user-level signal handling  
 * stack-frames in one go after that.  
 */  
static int do_signal(sigset_t *oldset, struct pt_regs *regs, int syscall)  
{  
    struct k_sigaction ka;  
    siginfo_t info;  
    int signr;  
  
    /*  
     * We want the common case to go fast, which  
     * is why we may in certain cases get here from  
     * kernel mode. Just return without doing anything  
     * if so.  
     */  
    if (!user_mode(regs))//regs保存的是進入內核態之前的寄存器現場,必須為用戶模式,否則直接返回  
        return 0;  
  
    if (try_to_freeze())  
        goto no_signal;  
  
    if (current->ptrace & PT_SINGLESTEP)  
        ptrace_cancel_bpt(current);//和調試相關,我們在後面的文章中會具體分析  
  
    signr = get_signal_to_deliver(&info, &ka, regs, NULL);//取出等待處理的信號  
    if (signr > 0) {  
        handle_signal(signr, &ka, &info, oldset, regs, syscall);//處理信號  
        if (current->ptrace & PT_SINGLESTEP)  
            ptrace_set_bpt(current);  
        return 1;  
    }  
  
 no_signal:  
    /*  
     * No signal to deliver to the process - restart the syscall.  
     */  
    if (syscall) {  
        if (regs->ARM_r0 == -ERESTART_RESTARTBLOCK) {  
            if (thumb_mode(regs)) {  
                regs->ARM_r7 = __NR_restart_syscall;  
                regs->ARM_pc -= 2;  
            } else {  
                u32 __user *usp;  
  
                regs->ARM_sp -= 12;  
                usp = (u32 __user *)regs->ARM_sp;  
  
                put_user(regs->ARM_pc, &usp[0]);  
                /* swi __NR_restart_syscall */  
                put_user(0xef000000 | __NR_restart_syscall, &usp[1]);  
                /* ldr  pc, [sp], #12 */  
                put_user(0xe49df00c, &usp[2]);  
  
                flush_icache_range((unsigned long)usp,  
                           (unsigned long)(usp + 3));  
  
                regs->ARM_pc = regs->ARM_sp + 4;  
            }  
        }  
        if (regs->ARM_r0 == -ERESTARTNOHAND ||  
            regs->ARM_r0 == -ERESTARTSYS ||  
            regs->ARM_r0 == -ERESTARTNOINTR) {  
            restart_syscall(regs);  
        }  
    }  
    if (current->ptrace & PT_SINGLESTEP)  
        ptrace_set_bpt(current);  
    return 0;  
}

執行do_signal()函數時,進程一定處於內核空間,通常進程只有通過中斷或者系統調用才能進入內核空間,regs保存著系統調用或者中斷時的現場。user_mode()根據regs中的cpsr寄存器來判斷是中斷現場環境還是用戶態環境。如果不是用戶態環境,就不對信號進行任何處理,直接從do_signal()函數返回。

如果user_mode()函數發現regs的現場是內核態,那就意味著這不是一次系統調用的返回,也不是一次普通的中斷返回,而是一次嵌套中斷返回(或者在系統調用過程中發生了中斷)。此時大概的執行路徑應該是這樣的:假設進場現在運行在用戶態,此時發生一次中斷,進場進入內核態(此時user_mode(regs)返回1,意味著中斷現場是用戶態。),此後在中斷返回前,發生了一個更高優先級的中斷,於是CPU開始執行高優先級的處理函數(此時user_mode(regs)返回0,意味著中斷現場是在內核態)。當高優先級中斷處理結束後,在它返回時,是不應該處理信號的,因為信號的優先級比中斷的優先級低。在這種情況下,對信號的處理將會延遲到低優先級中斷處理結束之後。相對於Windows內核來說,盡管linux內核中沒有一組顯式的操作函數來實現這一系列的優先級管理方案,但是linux內核和Windows內核都使用了同樣的機制,優先級關系為:高優先級中斷->低優先級中斷->軟中斷(類似Windows內中的DPC)->信號(類似Windows內核中的APC)->進程運行。

如果user_mode(regs)返回1,接下來會執行(中間略去一下和本文關系不大的代碼)get_signal_to_deliver(),這個函數從當前進程的信號隊列(保存Private Signal Queue和Shared Signal Queue)取出等待處理的信號(調用dequeue_signal()函數),然後根據信號定位到對應的signal_struct結構,如果信號的處理函數sa_handler為SIG_IGN,就忽略該信號,繼續取下一個信號;如果信號的處理函數sa_handler為SIG_DFL,意味著按照信號默認的處理方式對待就可以了(例如直接調用do_coredump()等)。

如果get_signal_to_deliver()函數返回值大於0,說明這個信號的處理函數是在用戶態空間(通過signal()和sigaction()等函數設置的自定義信號處理函數。),將調用handle_signal()函數進行處理。handle_signal()函數的定義如下:
/*  
 * OK, we're invoking a handler  
 */   
static void  
handle_signal(unsigned long sig, struct k_sigaction *ka,  
          siginfo_t *info, sigset_t *oldset,  
          struct pt_regs * regs, int syscall)  
{  
    struct thread_info *thread = current_thread_info();  
    struct task_struct *tsk = current;  
    int usig = sig;  
    int ret;  
  
    /*  
     * If we were from a system call, check for system call restarting...  
     */  
    if (syscall) {  
        switch (regs->ARM_r0) {  
        case -ERESTART_RESTARTBLOCK:  
        case -ERESTARTNOHAND:  
            regs->ARM_r0 = -EINTR;  
            break;  
        case -ERESTARTSYS:  
            if (!(ka->sa.sa_flags & SA_RESTART)) {  
                regs->ARM_r0 = -EINTR;  
                break;  
            }  
            /* fallthrough */  
        case -ERESTARTNOINTR:  
            restart_syscall(regs);  
        }  
    }  
  
    /*  
     * translate the signal  
     */  
    if (usig < 32 && thread->exec_domain && thread->exec_domain->signal_invmap)  
        usig = thread->exec_domain->signal_invmap[usig];  
  
    /*  
     * Set up the stack frame//設置棧幀  
     */  
    if (ka->sa.sa_flags & SA_SIGINFO)  
        ret = setup_rt_frame(usig, ka, info, oldset, regs);  
    else  
        ret = setup_frame(usig, ka, oldset, regs);  
  
    /*  
     * Check that the resulting registers are actually sane.  
     */  
    ret |= !valid_user_regs(regs);  
  
    /*  
     * Block the signal if we were unsuccessful.  
     */  
    if (ret != 0) {  
        spin_lock_irq(&tsk->sighand->siglock);  
        sigorsets(&tsk->blocked, &tsk->blocked,  
              &ka->sa.sa_mask);  
        if (!(ka->sa.sa_flags & SA_NODEFER))  
            sigaddset(&tsk->blocked, sig);  
        recalc_sigpending();  
        spin_unlock_irq(&tsk->sighand->siglock);  
    }  
  
    if (ret == 0)  
        return;  
  
    force_sigsegv(sig, tsk);  
}

在這樣情況下,進程當前處於內核態,而信號處理函數卻處於用戶態,為此必須在進程的用戶態構造一個臨時的堆棧環境(因為進程的信號處理函數在進行函數調用以及使用局部變量時需要使用堆棧。),然後進入用戶態執行信號處理函數,最後再返回內核態繼續執行。在這個過程中,有以下幾個問題需要解決:

1.臨時的用戶態堆棧在哪裡呢?這個很好解決,因為可以直接使用進程現有的用戶態堆棧,這裡要保證的是:使用結束後這個堆棧必須和使用前是一模一樣的。

2.臨時堆棧解決後,需要確定的是通過什麼方法來保證返回到用戶態後,進程執行的是信號的處理函數。我們知道在進入內核態時,內核態堆棧中保存了一個中斷現場,也就是一個pt_regs結構,中斷返回地址就保存在pt_regts中的pc中,因此我們這裡只要把當前進程的pt_regs中pc設置為sa_handler,然後返回到用戶態就開始從sa_handler處開始執行了。
unsigned long handler = (unsigned long)ka->sa.sa_handler;  
regs->ARM_pc = handler;

3.當信號的用戶態處理函數執行結束時,需要再次進入內核態,還原用戶態堆棧,並且修改pt_regs中的pc,保證將來能夠按照正常的方式返回用戶態。我們知道進程要主動進入內核態只有通過系統調用,出發異常等方法,為此內核專門提供了一個系統調用sys_sigreturn()(還有一個sys_rt_sigreturn()),但是如何調用sys_sigreturn()呢?強制安裝信號處理函數最後必須調用一個sigreturn()不是一個好辦法,因為不了解內核的程序員會對這個限制感到不解,為此程序員常常忘記在它們的信號處理函數的末尾調用sigreturn(),如果真是這樣,arm-linux-gcc也檢測不出這個錯誤。為此,內核修改regs的ARM_lr值:
regs->ARM_lr = retcode; 

當用戶態信號處理函數運行結束時,會從lr取出返回地址,因此內核在構建臨時regs時,會把上面這段代碼的入口地址保存在lr,這樣當信號處理完成後,就會順利的通過系統調用sys_sigreturn()進入內核。

4.當通過構造的sys_sigreturn()返回到內核態之後,內核需要順利的返回到用戶態執行原來的代碼(信號處理前應該返回的用戶空間狀態),但是此時進入內核空間的pt_regs上下文是通過sys_sigreturn()構造的,而最初的內核堆棧中的pt_regs上下文在第一次返回用戶空間執行信號處理函數時,就已經被“銷毀”了(內核態堆棧的pt_regs上下文在中斷返回後就不復存在了)。而現在必須通過最初的pt_regs上下文返回用戶態,為此,在構建臨時堆棧環境時,內核會把最初的pt_regs上下文備份到臨時堆棧中(位於用戶態堆棧),當通過系統調用sys_sigreturn()再次進入內核時,內核從用戶態空間還原出原始的pt_regs。最後正常返回。

通過上面的討論,我們知道在這個迂回的處理過程中,關鍵在於用戶態的臨時堆棧環境的建立,這是一個sigframe結構:
/*  
 * Do a signal return; undo the signal stack.  These are aligned to 64-bit.  
 */  
struct sigframe {  
    struct sigcontext sc;//保存一組寄存器上下文  
    unsigned long extramask[_NSIG_WORDS-1];  
    unsigned long retcode;//保存返回地址  
    struct aux_sigframe aux __attribute__((aligned(8)));  
};  
  
struct rt_sigframe {  
    struct siginfo __user *pinfo;  
    void __user *puc;  
    struct siginfo info;  
    struct ucontext uc;  
    unsigned long retcode;  
    struct aux_sigframe aux __attribute__((aligned(8)));  
};

其中的sigcontext的作用類似於pt_regs用於保存相關寄存器上下文,原始的pt_regs的相關信息就備份在這裡。而整個sigframe結構是通過get_sigframe()函數在進程用戶態空間分配的,get_sigframe()定義如下:
static inline void __user *  
get_sigframe(struct k_sigaction *ka, struct pt_regs *regs, int framesize)  
{  
    unsigned long sp = regs->ARM_sp;  
    void __user *frame;  
  
    /*  
     * This is the X/Open sanctioned signal stack switching.  
     */  
    if ((ka->sa.sa_flags & SA_ONSTACK) && !sas_ss_flags(sp))  
        sp = current->sas_ss_sp + current->sas_ss_size;  
  
    /*  
     * ATPCS B01 mandates 8-byte alignment  
     */  
    frame = (void __user *)((sp - framesize) & ~7);  
  
    /*  
     * Check that we can actually write to the signal frame.  
     */  
    if (!access_ok(VERIFY_WRITE, frame, framesize))  
        frame = NULL;  
  
    return frame;  
}

get_sigframe()通過用戶態空間堆棧的sp-sizeof(struct sigframe)在用戶態堆棧的頂部分配了一篇存儲空間,將來使用完成後,再通過sp+sizeof(struct sigframe)還原。

通過上面的討論,我們再回到do_signal()中來,如果有用戶態的信號處理函數,do_signal()會調用handle_signal(),handle_signal()將調用setup_frame()或者setup_rt_frame()來完成實際的工作,這裡我們以setup_frame()為例進行進一步討論。
static int  
setup_frame(int usig, struct k_sigaction *ka, sigset_t *set, struct pt_regs *regs)  
{  
    //在用戶態堆棧上分配一個sigframe結構  
    struct sigframe __user *frame = get_sigframe(ka, regs, sizeof(*frame));  
    int err = 0;  
  
    if (!frame)  
        return 1;  
  
    //把相關信息從內核態備份到用戶態堆棧的sigframe結構中  
    err |= setup_sigcontext(&frame->sc, &frame->aux, regs, set->sig[0]);  
  
    if (_NSIG_WORDS > 1) {  
        err |= __copy_to_user(frame->extramask, &set->sig[1],  
                      sizeof(frame->extramask));  
    }  
  
    if (err == 0)  
        err = setup_return(regs, ka, &frame->retcode, frame, usig);  
  
    return err;  
}

setup_return()設置返回地址,其定義如下:
static int  
setup_return(struct pt_regs *regs, struct k_sigaction *ka,  
         unsigned long __user *rc, void __user *frame, int usig)  
{  
    unsigned long handler = (unsigned long)ka->sa.sa_handler;  
    unsigned long retcode;  
    int thumb = 0;  
    unsigned long cpsr = regs->ARM_cpsr & ~PSR_f;  
  
    /*  
     * Maybe we need to deliver a 32-bit signal to a 26-bit task.  
     */  
    if (ka->sa.sa_flags & SA_THIRTYTWO)  
        cpsr = (cpsr & ~MODE_MASK) | USR_MODE;  
  
#ifdef CONFIG_ARM_THUMB  
    if (elf_hwcap & HWCAP_THUMB) {  
        /*  
         * The LSB of the handler determines if we're going to  
         * be using THUMB or ARM mode for this signal handler.  
         */  
        thumb = handler & 1;  
  
        if (thumb)  
            cpsr |= PSR_T_BIT;  
        else  
            cpsr &= ~PSR_T_BIT;  
    }  
#endif  
<span style="white-space:pre">  </span>//這裡的retcode就是保存手工構造的sigreturn()代碼  
    if (ka->sa.sa_flags & SA_RESTORER) {  
        retcode = (unsigned long)ka->sa.sa_restorer;  
    } else {  
        unsigned int idx = thumb;  
  
        if (ka->sa.sa_flags & SA_SIGINFO)  
            idx += 2;  
  
        if (__put_user(sigreturn_codes[idx], rc))  
            return 1;  
  
        if (cpsr & MODE32_BIT) {  
            /*  
             * 32-bit code can use the new high-page  
             * signal return code support.  
             */  
            retcode = KERN_SIGRETURN_CODE + (idx << 2) + thumb;  
        } else {  
            /*  
             * Ensure that the instruction cache sees  
             * the return code written onto the stack.  
             */  
            flush_icache_range((unsigned long)rc,  
                       (unsigned long)(rc + 1));  
  
            retcode = ((unsigned long)rc) + thumb;  
        }  
    }  
  
    regs->ARM_r0 = usig;  
    regs->ARM_sp = (unsigned long)frame;//堆棧  
    regs->ARM_lr = retcode;//返回地址,當用戶態信號處理函數結束時,就會把這個地址作為返回地址  
    regs->ARM_pc = handler;//信號處理函數  
    regs->ARM_cpsr = cpsr;  
  
    return 0;  
}

當setup_frame()返回後,一切准備就緒,因此可以從內核態返回了,這樣就順利過渡到用戶態的信號處理函數。當這個函數處理結束後,會通過retcode再次進入內核態,現在我們看看retcode是如何處理的,下面的代碼選自glibc(2.3.2):
#include <sysdep.h>  
  
/* If no SA_RESTORER function was specified by the application we use  
   one of these.  This avoids the need for the kernel to synthesise a return  
   instruction on the stack, which would involve expensive cache flushes. */  
  
ENTRY(__default_sa_restorer)  
    swi SYS_ify(sigreturn)  
  
#ifdef __NR_rt_sigreturn  
  
ENTRY(__default_rt_sa_restorer)  
    swi SYS_ify(rt_sigreturn)  
  
#define SYS_ify(syscall_name)   (__NR_##syscall_name)

下面具體看看sys_sigreturn()的定義:
asmlinkage int sys_sigreturn(struct pt_regs *regs)  
{  
    struct sigframe __user *frame;  
    sigset_t set;  
  
    /* Always make any pending restarted system calls return -EINTR */  
    current_thread_info()->restart_block.fn = do_no_restart_syscall;  
  
    /*  
     * Since we stacked the signal on a 64-bit boundary,  
     * then 'sp' should be word aligned here.  If it's  
     * not, then the user is trying to mess with us.  
     */  
    if (regs->ARM_sp & 7)  
        goto badframe;  
  
    frame = (struct sigframe __user *)regs->ARM_sp;  
  
    if (!access_ok(VERIFY_READ, frame, sizeof (*frame)))  
        goto badframe;  
    if (__get_user(set.sig[0], &frame->sc.oldmask)  
        || (_NSIG_WORDS > 1  
            && __copy_from_user(&set.sig[1], &frame->extramask,  
                    sizeof(frame->extramask))))  
        goto badframe;  
  
    sigdelsetmask(&set, ~_BLOCKABLE);  
    spin_lock_irq(&current->sighand->siglock);  
    current->blocked = set;  
    recalc_sigpending();  
    spin_unlock_irq(&currentt->sighand->siglock);  
  
    if (restore_sigcontext(regs, &frame->sc, &frame->aux))  
        goto badframe;  
  
    /* Send SIGTRAP if we're single-stepping */  
    if (current->ptrace & PT_SINGLESTEP) {  
        ptrace_cancel_bpt(current);  
        send_sig(SIGTRAP, current, 1);  
    }  
  
    return regs->ARM_r0;  
  
badframe:  
    force_sig(SIGSEGV, current);  
    return 0;  
}

這個函數主要調用restore_sigcontext()根據用戶態態堆棧上的sigframe備份還原pt_regs。
static int  
restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc,  
           struct aux_sigframe __user *aux)  
{  
    int err = 0;  
  
    __get_user_error(regs->ARM_r0, &sc->arm_r0, err);  
    __get_user_error(regs->ARM_r1, &sc->arm_r1, err);  
    __get_user_error(regs->ARM_r2, &sc->arm_r2, err);  
    __get_user_error(regs->ARM_r3, &sc->arm_r3, err);  
    __get_user_error(regs->ARM_r4, &sc->arm_r4, err);  
    __get_user_error(regs->ARM_r5, &sc->arm_r5, err);  
    __get_user_error(regs->ARM_r6, &sc->arm_r6, err);  
    __get_user_error(regs->ARM_r7, &sc->arm_r7, err);  
    __get_user_error(regs->ARM_r8, &sc->arm_r8, err);  
    __get_user_error(regs->ARM_r9, &sc->arm_r9, err);  
    __get_user_error(regs->ARM_r10, &sc->arm_r10, err);  
    __get_user_error(regs->ARM_fp, &sc->arm_fp, err);  
    __get_user_error(regs->ARM_ip, &sc->arm_ip, err);  
    __get_user_error(regs->ARM_sp, &sc->arm_sp, err);  
    __get_user_error(regs->ARM_lr, &sc->arm_lr, err);  
    __get_user_error(regs->ARM_pc, &sc->arm_pc, err);  
    __get_user_error(regs->ARM_cpsr, &sc->arm_cpsr, err);  
  
    err |= !valid_user_regs(regs);  
  
#ifdef CONFIG_IWMMXT  
    if (err == 0 && test_thread_flag(TIF_USING_IWMMXT))  
        err |= restore_iwmmxt_context(&aux->iwmmxt);  
#endif  
#ifdef CONFIG_VFP  
//  if (err == 0)  
//      err |= vfp_restore_state(&aux->vfp);  
#endif  
  
    return err;  
}
restore_sigcontext()很簡單,當它返回後,通過sys_sigreturn()在內核態堆棧建立的pt_regs已經變為調用信號處理函數之前的pt_regs。這樣,sys_sigreturn()返回用戶態時,就順利地過渡到處理之前的狀態了。
Copyright © Linux教程網 All Rights Reserved