電子產(chǎn)業(yè)一站式賦能平臺(tái)

PCB聯(lián)盟網(wǎng)

搜索
查看: 6|回復(fù): 0
收起左側(cè)

Linux內(nèi)核-信號(hào)的傳遞過程

[復(fù)制鏈接]

300

主題

300

帖子

2202

積分

三級(jí)會(huì)員

Rank: 3Rank: 3

積分
2202
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2024-5-16 11:00:00 | 只看該作者 |只看大圖 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
  • 1 執(zhí)行信號(hào)的默認(rèn)動(dòng)作
  • 2 捕獲信號(hào)
  • 3 系統(tǒng)調(diào)用的重新執(zhí)行
  • 4 x86_64架構(gòu)-do_signal()

    前面我們已經(jīng)介紹了內(nèi)核注意到信號(hào)的到來,調(diào)用相關(guān)函數(shù)更新進(jìn)程描述符以便進(jìn)程接收處理信號(hào)。但是,如果目標(biāo)進(jìn)程此時(shí)沒有運(yùn)行,內(nèi)核則推遲傳遞信號(hào),F(xiàn)在,我們看看內(nèi)核如何處理進(jìn)程掛起的信號(hào)。
    正如第4章的從中斷和異常返回一節(jié)中提到的,內(nèi)核允許在進(jìn)程返回到用戶態(tài)執(zhí)行之前,檢查進(jìn)程的TIF_SIGPENDING標(biāo)志。因此,內(nèi)核每次完成中斷或異常的處理后,都會(huì)檢查掛起信號(hào)是否存在。為了處理非阻塞的掛起信號(hào),內(nèi)核調(diào)用do_signal()函數(shù),其接受2個(gè)參數(shù):
  • regs
    current當(dāng)前進(jìn)程的用戶態(tài)寄存器內(nèi)容在內(nèi)核棧中保存位置的地址。
  • oldset
    用來保存阻塞信號(hào)位掩碼數(shù)組的變量地址。
    對(duì)do_signal()的描述,主要集中在信號(hào)傳遞的通用機(jī)制;真實(shí)的代碼中涵蓋了許多細(xì)節(jié),比如處理競態(tài)條件和其它特殊情況(如凍結(jié)系統(tǒng)、生成核心轉(zhuǎn)儲(chǔ)、停止和殺死整個(gè)線程組等等。我們將忽略這些細(xì)節(jié)。
    如前所述,do_signal()函數(shù)通常只在CPU打算返回到用戶態(tài)時(shí)才會(huì)被調(diào)用。所以,如果中斷處理程序里調(diào)用do_signal(),函數(shù)直接返回:
    if ((regs->xcs & 3) != 3)
        return 1;
    如果oldset是NULL,使用current->blocked的地址初始化它:
    if (!oldset)
        oldset = &current->blocked;
    do_signal()的核心是一段循環(huán)代碼,重復(fù)調(diào)用dequeue_signal()函數(shù),直到私有和共享掛起信號(hào)隊(duì)列沒有被阻塞的掛起信號(hào)。dequeue_signal()的返回值存儲(chǔ)在局部變量signr中,如果等于0,意味著所有掛起信號(hào)都被處理完,則do_signal()完成;如果返回非零,就有掛起信號(hào)等待被處理。do_signal()處理完這個(gè)信號(hào)后,會(huì)再次調(diào)用dequeue_signal()函數(shù)。
    dequeue_signal()首先考慮私有掛起信號(hào)隊(duì)列中的所有信號(hào)(數(shù)字從小到大),然后是共享掛起隊(duì)列中的信號(hào)。它會(huì)更新相應(yīng)的數(shù)據(jù)結(jié)構(gòu),標(biāo)識(shí)該信號(hào)不再掛起并返回信號(hào)值。其中,涉及到清除current->pending.signal或current->signal->shared_pending.signal中的相應(yīng)位,并調(diào)用recalc_sigpending()更新TIF_SIGPENDING的值。
    讓我們看一下do_signal()如何處理dequeue_signal()返回的每個(gè)掛起信號(hào)。首先,檢查當(dāng)前接收進(jìn)程是否正在被其它進(jìn)程監(jiān)控;如果是這種情況,do_signal()調(diào)用do_notify_parent_cldstop()和schedule()使監(jiān)控線程意識(shí)到該信號(hào)處理。
    然后,do_signal()將待處理信號(hào)的k_sigaction數(shù)據(jù)結(jié)構(gòu)的地址加載到局部變量ka中:
    ka = &current->sig->action[signr-1];
    依賴具體內(nèi)容,可能執(zhí)行三類動(dòng)作:忽略信號(hào),執(zhí)行默認(rèn)動(dòng)作或執(zhí)行信號(hào)處理程序。
    當(dāng)傳遞的信號(hào)被忽略,do_signal()則繼續(xù)處理其它掛起信號(hào):
    if (ka->sa.sa_handler == SIG_IGN)
        continue;
    接下來的兩節(jié),我們將描述如何執(zhí)行默認(rèn)動(dòng)作和信號(hào)處理程序。
    1 執(zhí)行信號(hào)的默認(rèn)動(dòng)作如果ka->sa.sa_handler等于SIG_DFL,do_signal()執(zhí)行信號(hào)的默認(rèn)動(dòng)作。唯一的例外是,當(dāng)接收進(jìn)程是init時(shí),這種情況下,信號(hào)會(huì)被拋棄:
    if (current->pid == 1)
        continue;
    對(duì)于其它進(jìn)程,忽略信號(hào)的默認(rèn)處理也非常簡單:
    if (signr==SIGCONT || signr==SIGCHLD ||
            signr==SIGWINCH || signr==SIGURG)
        continue;
    對(duì)于默認(rèn)動(dòng)作是stop的信號(hào),則會(huì)停止線程組中所有進(jìn)程。為此,do_signal()將它們的狀態(tài)設(shè)置為TASK_STOPPED,然后調(diào)用schedule()調(diào)度進(jìn)程:
    if (signr==SIGSTOP || signr==SIGTSTP ||
            signr==SIGTTIN || signr==SIGTTOU) {
        if (signr != SIGSTOP &&
                is_orphaned_pgrp(current->signal->pgrp))
            continue;
        do_signal_stop(signr);
    }
    SIGSTOP和其它信號(hào)有些許不同:SIGSTOP總是停止線程組,而其它信號(hào)只有在線程組處于孤兒進(jìn)程組中時(shí)才會(huì)停止該線程組。POSIX標(biāo)準(zhǔn)指明,只要線程組中某個(gè)進(jìn)程在同一個(gè)會(huì)話的不同進(jìn)程組中有一個(gè)父進(jìn)程,它就不是孤兒進(jìn)程組。因此,如果父進(jìn)程已死,但是,發(fā)起進(jìn)程的用戶仍然處于登錄中,該進(jìn)程組就不是孤兒進(jìn)程組。
    do_signal_stop()檢查當(dāng)前進(jìn)程是否是線程組中第一個(gè)被停止的進(jìn)程。如果是,它負(fù)責(zé)停止所有進(jìn)程:本質(zhì)上,該函數(shù)將信號(hào)描述符中的group_stop_count字段設(shè)置為正值,并喚醒線程組中的每個(gè)進(jìn)程。然后,每個(gè)進(jìn)程依次查看此字段以識(shí)別正在進(jìn)行的組停止,將其狀態(tài)更改為TASK_STOPPED,并調(diào)用schedule()重新調(diào)度進(jìn)程。do_signal_stop()函數(shù)還向線程組leader的父進(jìn)程發(fā)送SIGCHLD信號(hào),除非父進(jìn)程設(shè)置了SIGCHLD的SA_NOCLDSTOP標(biāo)志。
    默認(rèn)動(dòng)作為dump的信號(hào)會(huì)在進(jìn)程的工作目錄中創(chuàng)建核心轉(zhuǎn)儲(chǔ)文件:該文件列出了進(jìn)程地址空間和寄存器的完整內(nèi)容。do_signal()創(chuàng)建核心轉(zhuǎn)儲(chǔ)文件之后,會(huì)殺死線程組。其余18個(gè)信號(hào)的默認(rèn)動(dòng)作是terminate,就是殺死進(jìn)程。為此,調(diào)用do_group_exit(),執(zhí)行一個(gè)優(yōu)雅的group exit處理程序(可以參考第3章的進(jìn)程終止一節(jié))
    2 捕獲信號(hào)如果信號(hào)指定了處理程序,則do_signal()執(zhí)行該程序。通過調(diào)用invoking handle_signal()
    handle_signal(signr, &info, &ka, oldset, regs);
    if (ka->sa.sa_flags & SA_ONESHOT)
        ka->sa.sa_handler = SIG_DFL;
    return 1;
    如果接收的信號(hào)設(shè)置了SA_ONESHOT標(biāo)志,則必須將其重置為默認(rèn)操作,以便再次出現(xiàn)相同信號(hào)將不會(huì)觸發(fā)信號(hào)處理程序的執(zhí)行。注意do_signal()在處理完單個(gè)信號(hào)后是如何返回的。在下次調(diào)用do_signal()之前,不會(huì)考慮其他掛起的信號(hào)。這種方法確保了實(shí)時(shí)信號(hào)將按適當(dāng)?shù)捻樞蛱幚怼?br /> 執(zhí)行信號(hào)處理程序是一項(xiàng)相當(dāng)復(fù)雜的任務(wù),因?yàn)樵谟脩魬B(tài)和內(nèi)核態(tài)之間切換時(shí)需要小心地切換堆棧。我們?cè)谶@里詳細(xì)解釋一下:
    信號(hào)處理程序是由用戶進(jìn)程定義的函數(shù),包含在用戶代碼段中。handle_signal()函數(shù)在內(nèi)核態(tài)運(yùn)行,而信號(hào)處理程序在用戶態(tài)運(yùn)行;這意味著當(dāng)前進(jìn)程必須首先在用戶態(tài)執(zhí)行信號(hào)處理程序,然后才能被允許恢復(fù)其“正!眻(zhí)行。此外,當(dāng)內(nèi)核試圖恢復(fù)進(jìn)程的正常執(zhí)行時(shí),內(nèi)核堆棧不再包含被中斷程序的硬件上下文,因?yàn)閮?nèi)核堆棧在每次從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)時(shí)都會(huì)被清空。
    下圖11-2說明了捕獲信號(hào)的函數(shù)執(zhí)行流程。假設(shè)非阻塞信號(hào)被發(fā)送給進(jìn)程。中斷或異常發(fā)生時(shí),進(jìn)程切換到內(nèi)核態(tài)。在即將返回到用戶態(tài)之前,內(nèi)核調(diào)用do_signal()函數(shù),依次處理信號(hào)(handle_signal())并配置用戶態(tài)棧(setup_frame()或setup_rt_frame())。進(jìn)程切換到用戶態(tài)后,開始執(zhí)行信號(hào)處理程序,因?yàn)樵撎幚沓绦虻牡刂繁粡?qiáng)制加載到了PC程序計(jì)數(shù)器中。當(dāng)信號(hào)程序終止后,調(diào)用setup_frame()或setup_rt_frame()將返回代碼加載到用戶態(tài)棧中。這段返回代碼會(huì)調(diào)用sigreturn()和rt_sigreturn()系統(tǒng)調(diào)用;相應(yīng)的服務(wù)例程會(huì)將正常程序的硬件上下文內(nèi)容拷貝到內(nèi)核態(tài)棧并將用戶態(tài);謴(fù)到其原始狀態(tài)(restore_sigcontext())。當(dāng)系統(tǒng)調(diào)用終止時(shí),正常程序繼續(xù)其執(zhí)行。

    圖11-2 捕獲一個(gè)信號(hào)
    現(xiàn)在,讓我們看一下其執(zhí)行細(xì)節(jié):
    2.1 Setting up the frame為了正確設(shè)置進(jìn)程的用戶態(tài)棧,handle_signal()函數(shù)既可以調(diào)用setup_frame()(對(duì)于那些不需要siginfo_t的信號(hào)),也可以調(diào)用setup_rt_frame()(對(duì)于那些確定需要siginfo_t的信號(hào))。具體調(diào)用哪個(gè)函數(shù),依賴于信號(hào)的sigaction表中sa_flags字段的SA_SIGINFO標(biāo)志。
    接下來,我們看一下setup_frame()函數(shù)的具體實(shí)現(xiàn):(Linux內(nèi)核版本是v2.6.11,文件位置:arch/x86_64/kernel/signal.c)
    /* 這些符號(hào)的定義在vsyscall內(nèi)存頁中,查看vsyscall-sigreturn.S文件*/
    extern void __user __kernel_sigreturn;
    extern void __user __kernel_rt_sigreturn;
    static void setup_frame(int sig, struct k_sigaction *ka,
                sigset_t *set, struct pt_regs * regs)
    {
        void __user *restorer;
        struct sigframe __user *frame;
        int err = 0;
        int usig;
        frame = get_sigframe(ka, regs, sizeof(*frame));
        if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
            goto give_sigsegv;
        usig = current_thread_info()->exec_domain
            && current_thread_info()->exec_domain->signal_invmap
            && sig 32
            ? current_thread_info()->exec_domain->signal_invmap[sig]
            : sig;
        err = __put_user(usig, &frame->sig);
        if (err)
            goto give_sigsegv;
        err = setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]);
        if (err)
            goto give_sigsegv;
        if (_NSIG_WORDS > 1) {
            err = __copy_to_user(&frame->extramask, &set->sig[1],
                          sizeof(frame->extramask));
            if (err)
                goto give_sigsegv;
        }
        restorer = &__kernel_sigreturn;
        if (ka->sa.sa_flags & SA_RESTORER)
            restorer = ka->sa.sa_restorer;
        /* Set up to return from userspace.  */
        err |= __put_user(restorer, &frame->pretcode);
         
        /*
         * This is popl %eax ; movl $,%eax ; int $0x80
         *
         * WE DO NOT USE IT ANY MORE! It's only left here for historical
         * reasons and because gdb uses it as a signature to notice
         * signal handler stack frames.
         */
        err |= __put_user(0xb858, (short __user *)(frame->retcode+0));
        err |= __put_user(__NR_sigreturn, (int __user *)(frame->retcode+2));
        err |= __put_user(0x80cd, (short __user *)(frame->retcode+6));
        if (err)
            goto give_sigsegv;
        /* 為信號(hào)處理程序配置寄存器 */
        regs->esp = (unsigned long) frame;
        regs->eip = (unsigned long) ka->sa.sa_handler;
        regs->eax = (unsigned long) sig;
        regs->edx = (unsigned long) 0;
        regs->ecx = (unsigned long) 0;
        /* 恢復(fù)用戶態(tài)的段寄存器 */
        set_fs(USER_DS);
        regs->xds = __USER_DS;
        regs->xes = __USER_DS;
        regs->xss = __USER_DS;
        regs->xcs = __USER_CS;
        /* 在進(jìn)入信號(hào)處理程序時(shí)清除TF標(biāo)志,但通知正在單步跟蹤的跟蹤器,
         * 跟蹤器也可能希望在信號(hào)處理程序內(nèi)部進(jìn)行單步執(zhí)行
         */
        regs->eflags &= ~TF_MASK;
        if (test_thread_flag(TIF_SINGLESTEP))
            ptrace_notify(SIGTRAP);
        // ...省略,打印調(diào)試信息,然后返回。
    give_sigsegv:
        force_sigsegv(sig, current);
    }
    setup_frame()接收4個(gè)參數(shù),如下所示:
  • sig
    信號(hào)
  • ka
    信號(hào)的k_sigaction表地址
  • oldset
    阻塞信號(hào)的位掩碼組地址
  • regs
    用戶態(tài)寄存器內(nèi)容在內(nèi)核棧的保存位置
    setup_frame()將一個(gè)稱為frame的數(shù)據(jù)結(jié)構(gòu)壓倒用戶態(tài)棧中,該數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)著處理信號(hào)和能夠正確返回到sys_sigreturn()函數(shù)的所需要信息。frame是一個(gè)sigframe表,包含以下字段(參見圖11-3):
  • pretcode
    信號(hào)處理程序的返回地址。其實(shí)就是__kernel_sigreturn標(biāo)簽處的匯編代碼。
  • sig
    信號(hào),信號(hào)處理程序需要的一個(gè)參數(shù)。
  • sc
    包含用戶態(tài)進(jìn)程即將切換到內(nèi)核態(tài)之前的進(jìn)程上下文內(nèi)容,其數(shù)據(jù)類型為sigcontext(這些信息是從current的內(nèi)核態(tài)棧中拷貝而來)。另外,它還包含一個(gè)進(jìn)程阻塞信號(hào)的位數(shù)組。
  • fpstate
    用來保存用戶態(tài)進(jìn)程的浮點(diǎn)寄存器信息,數(shù)據(jù)結(jié)構(gòu)類型為_fpstate。(參見第3章的保存和加載FPU、MMX和XMM寄存器)。
  • extramask
    指定阻塞實(shí)時(shí)信號(hào)的位數(shù)組。
  • retcode
    發(fā)起sigreturn()系統(tǒng)調(diào)用的8字節(jié)代碼。在Linux早期版本中,這段代碼用來從信號(hào)處理程序返回;但Linux 2.6版本以后,僅用作符號(hào)簽名,以便調(diào)試器可以識(shí)別信號(hào)的棧幀。


    圖11-3 用戶態(tài)棧上的frame
    setup_frame()函數(shù)調(diào)用get_sigframe()計(jì)算frame第一個(gè)內(nèi)存位置,因?yàn)樵搩?nèi)存位置位于用戶態(tài)棧上,所以函數(shù)返回的值為(regs->esp - sizeof(struct sigframe)) & 0xfffffff8。
  • Linux允許進(jìn)程調(diào)用signaltstack()系統(tǒng)調(diào)用為它們的信號(hào)處理程序指定一個(gè)替換棧;這個(gè)特性也是X/Open標(biāo)準(zhǔn)要求的。如果使用的是替換棧,get_sigframe()函數(shù)返回的是替換棧中的一個(gè)地址。對(duì)于此特性,我們不過多討論,從概念上講,其與常規(guī)信號(hào)處理非常類似。因?yàn)樵趚86架構(gòu)上,棧是向下增長的,所以,frame的首地址等于當(dāng)前棧頂位置的地址減去frame的大小,結(jié)果按照8字節(jié)對(duì)齊。
    返回地址使用access_ok進(jìn)行驗(yàn)證:如果合法,函數(shù)重復(fù)調(diào)用__put_user(),以便填充frame的所有字段。pretcode字段初始化為&__kernel_sigreturn,這是vsyscall內(nèi)存頁上的一段匯編代碼地址。(參見第10章的通過sysenter指令發(fā)起系統(tǒng)調(diào)用的一節(jié))
    接下來,修改內(nèi)核態(tài)棧的regs內(nèi)容,保證當(dāng)current切換到用戶態(tài)時(shí),CPU控制權(quán)能夠傳遞給信號(hào)處理程序:
        regs->esp = (unsigned long) frame;
        regs->eip = (unsigned long) ka->sa.sa_handler;
        regs->eax = (unsigned long) sig;
        regs->edx = regs->ecx = 0;
        regs->xds = regs->xes = regs->xss = __USER_DS;
        regs->xcs = __USER_CS;
    最后,setup_frame()函數(shù)將保存在內(nèi)核態(tài)棧上的段寄存器復(fù)位成用戶態(tài)默認(rèn)值而終止,F(xiàn)在,信號(hào)處理程序所需的信息都在用戶態(tài)棧頂位置了。
    setup_rt_frame()函數(shù)與setup_frame()類似,但是它把一個(gè)擴(kuò)展幀(數(shù)據(jù)結(jié)構(gòu)為rt_sigframe)存放到了用戶態(tài)棧中,該擴(kuò)展幀還包括與信號(hào)有關(guān)的siginfo_t表的內(nèi)容。此外,該函數(shù)還將pretcode字段指向vsyscall內(nèi)存頁中的__kernel_rt_sigreturn代碼段。
    2.2 計(jì)算信號(hào)標(biāo)志配置完用戶態(tài)棧后,handle_signal()函數(shù)檢查與該信號(hào)相關(guān)的標(biāo)志。如果該信號(hào)沒有設(shè)置SA_NODEFER標(biāo)志,則在信號(hào)處理程序執(zhí)行期間,sigaction表中的sa_mask字段中的所有信號(hào)必須被阻塞,以便該信號(hào)快速處理完成:
        if (!(ka->sa.sa_flags & SA_NODEFER)) {
            spin_lock_irq(&current->sighand->siglock);
            sigorsets(&current->blocked, &current->blocked, &ka->sa.sa_mask);
            sigaddset(&current->blocked, sig);
            recalc_sigpending(current);
            spin_unlock_irq(&current->sighand->siglock);
        }
    正如先前描述的,recalc_sigpending()函數(shù)檢查該進(jìn)程是否有非阻塞的掛起信號(hào),并設(shè)置其相應(yīng)的TIF_SIGPENDING標(biāo)志。
    完成之后,返回do_signal(),隨即也返回。
    2.3 啟動(dòng)信號(hào)處理程序當(dāng)從do_signal()返回后,當(dāng)前進(jìn)程切換到用戶態(tài)執(zhí)行。因?yàn)閟etup_frame()的準(zhǔn)備工作,eip寄存器指向了信號(hào)處理程序的第一條指令,而esp指向了壓入用戶態(tài)棧頂?shù)膄rame的第一個(gè)內(nèi)存位置。于是,開始執(zhí)行信號(hào)處理程序。
    2.4 終止信號(hào)處理程序當(dāng)信號(hào)處理程序執(zhí)行完成時(shí),其棧頂?shù)姆祷氐刂分赶騰syscall內(nèi)存頁(frame中的pretcode字段)
    __kernel_sigreturn:
        popl %eax
        movl $__NR_sigreturn, %eax
        int $0x80
    因此,信號(hào)(也就是frame中的sig字段)被從棧中丟棄;然后,調(diào)用sigreturn()系統(tǒng)調(diào)用。
    sys_sigreturn()函數(shù)計(jì)算regs(類型為pt_regs)的地址,其中包含用戶進(jìn)程的硬件上下文內(nèi)容,以便完成內(nèi)核態(tài)切換到用戶態(tài)執(zhí)行。因?yàn)槲覀冊(cè)趶膬?nèi)核態(tài)切換到用戶態(tài)執(zhí)行信號(hào)處理程序的過程中,內(nèi)核態(tài)棧已經(jīng)被破壞,所以需要重新建立一個(gè)臨時(shí)內(nèi)核態(tài)棧,數(shù)據(jù)來源就是用戶態(tài)棧中配置的frame數(shù)據(jù)結(jié)構(gòu)。
    asmlinkage int sys_sigreturn(unsigned long __unused)
    {
        /* 建立進(jìn)程在內(nèi)核態(tài)的臨時(shí)棧 */
        struct pt_regs *regs = (struct pt_regs *) &__unused;
        // 內(nèi)核態(tài)棧中用戶存儲(chǔ)
        struct sigframe __user *frame = (struct sigframe __user *)(regs->esp - 8);
        sigset_t set;
        int eax;
        /* 驗(yàn)證`frame`數(shù)據(jù)結(jié)構(gòu)是否正確 */
        if (verify_area(VERIFY_READ, frame, sizeof(*frame)))
            goto badframe;
        /* 處理實(shí)時(shí)信號(hào) */
        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;
        /* 將在信號(hào)處理程序期間阻塞的信號(hào)恢復(fù)掛起狀態(tài) */
        sigdelsetmask(&set, ~_BLOCKABLE);
        spin_lock_irq(&current->sighand->siglock);
        current->blocked = set;
        recalc_sigpending();
        spin_unlock_irq(&current->sighand->siglock);
       
        /* 將用戶態(tài)棧中的frame中保存的用戶進(jìn)程硬件上下文拷貝到內(nèi)核態(tài)棧,并移除frame */
        if (restore_sigcontext(regs, &frame->sc, &eax))
            goto badframe;
        return eax;
        /* 錯(cuò)誤數(shù)據(jù)處理 */
    badframe:
        force_sig(SIGSEGV, current);
        return 0;
    }
    sys_sigreturn()函數(shù)計(jì)算出regs(類型為pt_regs)的地址,它包含用戶進(jìn)程的硬件上下文內(nèi)容(參考第10章的參數(shù)傳遞一節(jié)。根據(jù)regs中的esp字段,就能推斷出用戶棧中的frame地址。
    然后,從frame的sc字段中拷貝在調(diào)用信號(hào)處理程序之前被阻塞的信號(hào)(位數(shù)組)到當(dāng)前進(jìn)程current的blocked字段中。也就是將這些被阻塞的信號(hào)解除阻塞。調(diào)用recalc_sigpending()將這些信號(hào)重新加入到掛起信號(hào)隊(duì)列中。
    接下來,sys_sigreturn()函數(shù)需要將frame的sc字段中的進(jìn)程硬件上下文拷貝到內(nèi)核態(tài)棧,并從用戶態(tài)棧中移除frame數(shù)據(jù)。這兩步的完成都是restore_sigcontext()函數(shù)實(shí)現(xiàn)的。
    如果信號(hào)是由系統(tǒng)調(diào)用發(fā)送的(比如,rt_sigqueueinfo()),要求信號(hào)相關(guān)的siginfo_t表數(shù)據(jù),其機(jī)制與上面類似。擴(kuò)展幀中的pretcode字段指向__kernel_rt_sigreturn標(biāo)簽處的匯編代碼(位于vsyscall內(nèi)存頁),這段代碼會(huì)調(diào)用rt_sigreturn()系統(tǒng)調(diào)用。相應(yīng)的系統(tǒng)服務(wù)例程sys_rt_sigreturn()將擴(kuò)展幀中的進(jìn)程硬件上下文拷貝到內(nèi)核態(tài)棧,并且將擴(kuò)展幀從用戶態(tài)棧中移除,以便恢復(fù)原始的用戶態(tài)棧。
    3 系統(tǒng)調(diào)用的重新執(zhí)行對(duì)于系統(tǒng)調(diào)用請(qǐng)求,內(nèi)核有時(shí)不能立即滿足。這時(shí)候,發(fā)起系統(tǒng)調(diào)用的進(jìn)程會(huì)被置成TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE狀態(tài)。
    如果進(jìn)程處于TASK_INTERRUPTIBLE狀態(tài)且其它進(jìn)程發(fā)送信號(hào)給它,內(nèi)核在沒有完成系統(tǒng)調(diào)用的情況下將進(jìn)程置為TASK_RUNNING(參考第4章的從中斷和異常返回一節(jié))。當(dāng)進(jìn)程想要切換回用戶態(tài),同時(shí)信號(hào)傳遞過來時(shí),系統(tǒng)調(diào)用服務(wù)例程還沒有完成其工作,所以會(huì)返回錯(cuò)誤碼EINTR、ERESTARTNOHAND、ERESTART_RESTARTBLOCK、ERESTARTSYS、ERESTARTNOINTR。
    事實(shí)上,在這種場景下,用戶態(tài)進(jìn)程能得到的錯(cuò)誤碼只能是EINTR,這意味著系統(tǒng)調(diào)用還沒有完成。應(yīng)用編程者可以檢查這個(gè)錯(cuò)誤碼并決定是否重新發(fā)起系統(tǒng)調(diào)用。其余的錯(cuò)誤碼由內(nèi)核內(nèi)部使用,指定是否在信號(hào)處理程序結(jié)束之后自動(dòng)重新執(zhí)行系統(tǒng)調(diào)用。
    表11-11 列出了未完成系統(tǒng)調(diào)用相關(guān)的錯(cuò)誤碼,以及它們?nèi)N信號(hào)默認(rèn)行為的影響。表中的術(shù)語說明如下:
  • Terminate
    系統(tǒng)調(diào)用將不會(huì)自動(dòng)重新執(zhí)行;進(jìn)程將切換到用戶態(tài)下int $0x80或sysenter之后的指令處繼續(xù)執(zhí)行,同時(shí),通過寄存器eax返回-EINTR值。
  • Reexecute
    內(nèi)核強(qiáng)制用戶進(jìn)程重新加載系統(tǒng)調(diào)用號(hào)(eax),然后重新調(diào)用int $0x80或sysenter;而進(jìn)程不會(huì)意識(shí)到重新執(zhí)行,也不會(huì)傳遞錯(cuò)誤碼給它。
  • Depends
    只有被傳遞的信號(hào)設(shè)置了SA_RESTART標(biāo)志,系統(tǒng)調(diào)用才會(huì)被重新執(zhí)行;否則,系統(tǒng)調(diào)用將終止并返回錯(cuò)誤碼-EINTR。
    表11-11 系統(tǒng)調(diào)用的重新執(zhí)行
    信號(hào)行為EINTRERESTARTSYSERESTARTNOHAND
    ERESTART_RESTARTBLOCK*ERESTARTNOINTRDefaultTerminateReexecuteReexecuteReexecuteIgnoreTerminateReexecuteReexecuteReexecuteCatchTerminateDependsTerminateReexecute
  • ERESTARTNOHAND和ERESTART_RESTARTBLOCK重啟系統(tǒng)調(diào)用的機(jī)制不同。在傳遞信號(hào)時(shí),內(nèi)核必須在重新執(zhí)行它之前確保進(jìn)程發(fā)起了系統(tǒng)調(diào)用。這就是regs寄存器上下文的orig_eax字段發(fā)揮關(guān)鍵作用的地方。讓我們回憶一下,當(dāng)中斷或異常處理程序啟動(dòng)時(shí),這個(gè)字段是如何初始化的:
  • 中斷
    該字段為中斷IRQ減去256(因?yàn)橹袛嗵?hào)數(shù)量小于224,減去256表示內(nèi)核使用負(fù)數(shù)表示IRQ)(參考第4章的為中斷處理程序保存寄存器)。
  • 0x80異常(包括sysenter)
    該字段包含系統(tǒng)調(diào)用號(hào)(第10章的進(jìn)入和推出系統(tǒng)調(diào)用一節(jié))。
  • 其它異常
    該字段為–1(參考第4章的為異常處理程序保存寄存器)。
    因此,orig_eax中的非負(fù)值意味著信號(hào)喚醒了一個(gè)在系統(tǒng)調(diào)用中休眠的可中斷進(jìn)程(TASK_INTERRUPTIBLE)。服務(wù)例程意識(shí)到了系統(tǒng)調(diào)用被中斷,因此返回一個(gè)前面提到的錯(cuò)誤碼。
    3.1 重新啟動(dòng)non-caught信號(hào)中斷的系統(tǒng)調(diào)用對(duì)于被忽略或執(zhí)行默認(rèn)動(dòng)作的信號(hào),do_signal()分析系統(tǒng)調(diào)用的錯(cuò)誤碼,判斷系統(tǒng)調(diào)用是否自動(dòng)重新執(zhí)行,如表11-1所示。如果系統(tǒng)調(diào)用必須重啟,則修改regs上下文內(nèi)容:eip-2表示將eip指向int $0x80或sysenter,eax包含系統(tǒng)調(diào)用號(hào):
        if (regs->orig_eax >= 0) {
            if (regs->eax == -ERESTARTNOHAND || regs->eax == -ERESTARTSYS ||
                    regs->eax == -ERESTARTNOINTR) {
                regs->eax = regs->orig_eax;
                regs->eip -= 2;
            }
            if (regs->eax == -ERESTART_RESTARTBLOCK) {
                regs->eax = __NR_restart_syscall;
                regs->eip -= 2;
            }
        }
    regs->eax包含著系統(tǒng)調(diào)用服務(wù)例程的返回碼(參加第10章的進(jìn)入和退出系統(tǒng)調(diào)用一節(jié))。因?yàn)閕nt $0x80和sysreturn指令都是2個(gè)字節(jié)長度,所以eip-2指向了int $0x80或sysenter,可以再次觸發(fā)系統(tǒng)調(diào)用。
    錯(cuò)誤碼ERESTART_RESTARTBLOCK是特殊的,因?yàn)閑ax被設(shè)置為了restart_syscall()系統(tǒng)調(diào)用號(hào);因此,用戶不會(huì)重新啟動(dòng)被信號(hào)中斷的同一個(gè)系統(tǒng)調(diào)用。此錯(cuò)誤碼只有與時(shí)間有關(guān)的系統(tǒng)調(diào)用使用,這些系統(tǒng)調(diào)用重新啟動(dòng)時(shí),應(yīng)該調(diào)整其用戶態(tài)參數(shù)。典型的例子是nanosleep()系統(tǒng)調(diào)用(參考第6章的動(dòng)態(tài)定時(shí)器的應(yīng)用:nanosleep()系統(tǒng)調(diào)用):假設(shè)進(jìn)程調(diào)用它來暫停執(zhí)行20毫秒,隨后過了10毫秒之后發(fā)生了一個(gè)信號(hào)。如果系統(tǒng)調(diào)用還和平常一樣重啟,那么,總的延時(shí)時(shí)間會(huì)超過30毫秒。
    相反,如果nanosleep()系統(tǒng)調(diào)用服務(wù)例程被中斷,則使用特殊服務(wù)例程的地址填充current進(jìn)程的thread_info結(jié)構(gòu)體的restart_block字段,并返回ERESTART_RESTARTBLOCK錯(cuò)誤碼。sys_restart_syscall()服務(wù)例程只執(zhí)行前面特殊的服務(wù)里程,計(jì)算首次調(diào)用和重新啟動(dòng)之間經(jīng)過的時(shí)間,從而調(diào)整延時(shí)。
    3.2 重新啟動(dòng)caught信號(hào)中斷的系統(tǒng)調(diào)用如果信號(hào)需要捕獲處理,handle_signal()分析錯(cuò)誤碼,根據(jù)sigaction中的SA_RESTART標(biāo)志,判斷是否需要重啟:
        if (regs->orig_eax >= 0) {
            switch (regs->eax) {
                case -ERESTART_RESTARTBLOCK:
                case -ERESTARTNOHAND:
                    regs->eax = -EINTR;
                    break;
                case -ERESTARTSYS:
                    if (!(ka->sa.sa_flags & SA_RESTART)) {
                        regs->eax = -EINTR;
                        break;
                    }
                /* fallthrough */
                case -ERESTARTNOINTR:
                    regs->eax = regs->orig_eax;
                    regs->eip -= 2;
            }
        }
    如果必須重新啟動(dòng)系統(tǒng)調(diào)用,handle_signal()的處理方式與do_signal()完全相同;否則,它會(huì)向用戶進(jìn)程返回一個(gè)-EINTR錯(cuò)誤碼。
    4 x86_64架構(gòu)-do_signal()Linux內(nèi)核版本是v2.6.11,文件位置:arch/x86_64/kernel/signal.c:
    /*
    * 注意init是一個(gè)特殊進(jìn)程:它不會(huì)收到不想處理的信號(hào)。所以,即使錯(cuò)誤地發(fā)送
    * `SIGKILL`信號(hào)給它,也不會(huì)殺死它。
    */
    int do_signal(struct pt_regs *regs, sigset_t *oldset)
    {
        struct k_sigaction ka;
        siginfo_t info;
        int signr;
        /* 如果不是返回到用戶態(tài),則直接返回。 */
        if ((regs->cs & 3) != 3) {
            return 1;
        }   
        // ...省略
        if (!oldset)
            oldset = &current->blocked;
        signr = get_signal_to_deliver(&info, &ka, regs, NULL);
        if (signr > 0) {
            /*
             * 在將信號(hào)傳遞到用戶空間之前重新啟動(dòng)多有觀察點(diǎn)。
             * 如果觀察點(diǎn)在內(nèi)核內(nèi)部觸發(fā),寄存器將被清除。
             */
            if (current->thread.debugreg7)
                asm volatile("movq %0,%%db7"    : : "r" (current->thread.debugreg7));
            /* 傳遞信號(hào) */
            handle_signal(signr, &info, &ka, oldset, regs);
            return 1;
        }
    no_signal:
        /* 是否是系統(tǒng)調(diào)用 */
        // ...省略(見前面第3.1節(jié)的處理)
        return 0;
    }
    x86-64架構(gòu)的handle_signal()函數(shù)(文件位置:arch/x86_64/kernel/signal.c):
    static void
    handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,
            sigset_t *oldset, struct pt_regs *regs)
    {
        // ... 省略(調(diào)試信號(hào)信息)
        // 省略(被中斷的系統(tǒng)調(diào)用的相關(guān)處理)
        // 省略(IA32_EMULATION配置)
        setup_rt_frame(sig, ka, info, oldset, regs);
        if (!(ka->sa.sa_flags & SA_NODEFER)) {
            spin_lock_irq(&current->sighand->siglock);
            sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
            sigaddset(&current->blocked,sig);
            recalc_sigpending();
            spin_unlock_irq(&current->sighand->siglock);
        }
    }
    i386架構(gòu)的handle_signal()函數(shù)(文件位置:arch/i386/kernel/signal.c):
    static void
    handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,
              sigset_t *oldset, struct pt_regs * regs)
    {
        // 省略(被中斷的系統(tǒng)調(diào)用的相關(guān)處理)
        /* Set up the stack frame */
        if (ka->sa.sa_flags & SA_SIGINFO)
            setup_rt_frame(sig, ka, info, oldset, regs);
        else
            setup_frame(sig, ka, oldset, regs);
        if (!(ka->sa.sa_flags & SA_NODEFER)) {
            spin_lock_irq(&current->sighand->siglock);
            sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
            sigaddset(&current->blocked,sig);
            recalc_sigpending();
            spin_unlock_irq(&current->sighand->siglock);
        }
    }
    static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
                   sigset_t *set, struct pt_regs * regs)
    {
        struct rt_sigframe __user *frame;
        struct _fpstate __user *fp = NULL;
        int err = 0;
        struct task_struct *me = current;
        if (used_math()) {
            fp = get_stack(ka, regs, sizeof(struct _fpstate));
            frame = (void __user *)round_down((unsigned long)fp - sizeof(struct rt_sigframe), 16) - 8;
            if (!access_ok(VERIFY_WRITE, fp, sizeof(struct _fpstate))) {
            goto give_sigsegv;
            }
            if (save_i387(fp) 0)
                err |= -1;
        } else {
            frame = get_stack(ka, regs, sizeof(struct rt_sigframe)) - 8;
        }
        if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) {
            goto give_sigsegv;
        }
        if (ka->sa.sa_flags & SA_SIGINFO) {
            err |= copy_siginfo_to_user(&frame->info, info);
            if (err) {
                goto give_sigsegv;
        }
        }
            
        /* Create the ucontext.  */
        err |= __put_user(0, &frame->uc.uc_flags);
        err |= __put_user(0, &frame->uc.uc_link);
        err |= __put_user(me->sas_ss_sp, &frame->uc.uc_stack.ss_sp);
        err |= __put_user(sas_ss_flags(regs->rsp),
                  &frame->uc.uc_stack.ss_flags);
        err |= __put_user(me->sas_ss_size, &frame->uc.uc_stack.ss_size);
        err |= setup_sigcontext(&frame->uc.uc_mcontext, regs, set->sig[0], me);
        err |= __put_user(fp, &frame->uc.uc_mcontext.fpstate);
        if (sizeof(*set) == 16) {
            __put_user(set->sig[0], &frame->uc.uc_sigmask.sig[0]);
            __put_user(set->sig[1], &frame->uc.uc_sigmask.sig[1]);
        } else {        
        err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
        }
        /* Set up to return from userspace.  If provided, use a stub
           already in userspace.  */
        /* x86-64 should always use SA_RESTORER. */
        if (ka->sa.sa_flags & SA_RESTORER) {
            err |= __put_user(ka->sa.sa_restorer, &frame->pretcode);
        } else {
            /* could use a vstub here */
            goto give_sigsegv;
        }
        if (err) {
            goto give_sigsegv;
        }
    #ifdef DEBUG_SIG
        printk("%d old rip %lx old rsp %lx old rax %lx
    ", current->pid,regs->rip,regs->rsp,regs->rax);
    #endif
        /* Set up registers for signal handler */
        {
            struct exec_domain *ed = current_thread_info()->exec_domain;
            if (unlikely(ed && ed->signal_invmap && sig 32))
                sig = ed->signal_invmap[sig];
        }
        regs->rdi = sig;
        /* In case the signal handler was declared without prototypes */
        regs->rax = 0;  
        /* This also works for non SA_SIGINFO handlers because they expect the
           next argument after the signal number on the stack. */
        regs->rsi = (unsigned long)&frame->info;
        regs->rdx = (unsigned long)&frame->uc;
        regs->rip = (unsigned long) ka->sa.sa_handler;
        regs->rsp = (unsigned long)frame;
        set_fs(USER_DS);
        if (regs->eflags & TF_MASK) {
            if ((current->ptrace & (PT_PTRACED | PT_DTRACE)) == (PT_PTRACED | PT_DTRACE)) {
                ptrace_notify(SIGTRAP);
            } else {
                regs->eflags &= ~TF_MASK;
            }
        }
    #ifdef DEBUG_SIG
        printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p
    ",
            current->comm, current->pid, frame, regs->rip, frame->pretcode);
    #endif
        return;
    give_sigsegv:
        force_sigsegv(sig, current);
    }
    歡迎交流,可以掃描下面二維碼,關(guān)注本公眾號(hào)。
  • 發(fā)表回復(fù)

    本版積分規(guī)則

    關(guān)閉

    站長推薦上一條 /1 下一條


    聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表