歡迎光臨
每天分享高質量文章

內核信號機制

1 信號(signal)

在中斷和異常一章,80×86處理器發佈了大約20種異常,每一種異常都有其對應的異常向量和異常處理程式。比如向量0對應除法故障,向量15對應頁故障。當15號異常發生時,異常處理程式就會向觸發該異常的行程發送SIGSEGV信號。所以一句話總結什麼是信號:信號是用於通知行程特定事件的發生的一個整數。

信號有兩種狀態:未決阻塞(pending and blocked)和未決未阻塞(pending and nonblocked)。 
當一個信號產生時,內核通常在行程表中以某種形式設置一個標誌。當對信號採取某些動作時(比如執行信號處理程式),稱遞送(delivery)了一個信號。在信號產生(特定事件發生)和遞送的間隙,稱信號時未決的。

行程可以選擇阻塞信號的遞送。如果為行程產生了一個阻塞的信號,而且對該信號的動作是系統預設動作和捕捉該信號,則為該行程將此信號保持為未決狀態,直到對該信號解除了阻塞,或者將該信號的動作改為忽略。行程可以通過呼叫sigpending函式判斷那些信號是設置為未決並阻塞的。

每個行程都有一個信號屏蔽字(signal mask),它規定了行程要阻塞的信號。函式sigprocmask可以設置行程的信號屏蔽字。

內核中共有64種信號,前32種是普通信號(regular signal 0 – 31)後32種是實時信號(rt-signal 32-63),一個普通信號被髮送多次,只有一個被髮送到接收行程。實時信號可以被排隊發送。內核中並不使用實時信號。

2 與信號有關的資料結構

2.1 task_struct和信號

本節敘述行程描述符和信號的關係,在task_struct中存在幾個與信號有關的資料結構: 
signal中包含一個存放共享信號的掛起佇列,該結構的型別是struct signal_struct。 
sighand指向一個含有64個信號處理程式的結構,結構的型別是struct sighand_struct 
blocked是行程的信號屏蔽字,是sigset_t型別。而sigset_t 是一個包含兩個元素的無符號長整型陣列 
sas_ss_sp是信號處理程式的備用棧 
sas_ss_size是信號處理程式的備用棧地址

  1. struct signal_struct *signal;

  2. struct sighand_struct *sighand;

  3. sigset_t blocked

  4. struct sigpending pending;

  5. unsigned long sas_ss_sp;

  6. size_t sas_ss_size;

  7. typedef struct {

  8.        unsigned long sig[2];

  9. }sigset_t;

2.2 未決信號佇列(pending signal queue)

和未決信號佇列有關的結構由兩個:sigpending結構和sigqueue結構。sigpending是佇列的頭節點儲存有關於信號佇列的信息。一個信號位圖signal,signal的每一位表示一種信號,通過signal就可以得到佇列中到底有哪些信號在排隊。 


每個行程有兩種信號佇列,一個是私有未決信號佇列pending,另一個是共享未決信號佇列shared_pending。在信號的傳遞時,優先處理私有佇列中的信號。

  1. truct sigpending {

  2.    struct list_head list;

  3.    sigset_t signal;

  4. };

  5. struct sigqueue {

  6.    struct list_head list;

  7.    spinlock_t *lock;

  8.    int flags;

  9.    siginfo_t info;

  10.    struct user_struct *user;

  11. };

2.3 sigaction結構

sigacition有兩個主要欄位,sa_handler表明如何處理信號。sa_flags則提供了為信號處理添加了額外的行為,如SA_RESTART(自動重新啟動被信號中斷的系統呼叫)。 
sa_handler有三種值,SIG_DFL(執行預設操作),SIG_IGN(忽略信號),或者信號處理程式的一個指標。

  1. struct k_sigaction {

  2.    struct sigaction sa;

  3. };

  4. struct sigaction {

  5.    __sighandler_t sa_handler;

  6.    unsigned long sa_flags;

  7.    __sigrestore_t sa_restorer;

  8.    sigset_t sa_mask;       /* mask last for extensibility */

  9. };

3 信號的產生(generate)

信號的產生從內核角度來說,就是將信號插入行程的未決信號佇列。

3.1 specific_send_sig_info

函式specific_send_sig_info負責向指定的行程發送信號,函式有三個引數, 
sig:是要產生的信號型別 
info:等於0表示信號由用戶行程產生,1表示由內核產生,2表示信號是由內核產生的SIGSTOP或SIGKILL 
t:信號的接收行程

  1. tatic int

  2. specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)

  3. {

  4.    int ret = 0;

  5.    if (!irqs_disabled())

  6.        BUG();

  7.    assert_spin_locked(&t->sighand->siglock);

  8.    if (((unsigned long)info > 2) && (info->si_code == SI_TIMER))

  9.        ret = info->si_sys_private;

  10.        //判斷信號是否滿足被忽略的條件

  11.    if (sig_ignored(t, sig))

  12.        goto out;

  13.    if (LEGACY_QUEUE(&t->pending, sig))

  14.        goto out;

  15.        //呼叫send_signal將信號加入佇列

  16.    ret = send_signal(sig, info, t, &t->pending);

  17.    if (!ret && !sigismember(&t->blocked, sig))

  18.        signal_wake_up(t, sig == SIGKILL);

  19. out:

  20.    return ret;

  21. }

3.2 send_signal

send_signal負責將信號插入到標的行程的pending佇列中,該函式接收四個引數; 
sig: 表示發送的信號 
info: struct siginfo型別,含有此次發送的信號的信息 
t : 指向接收行程 
signals: 指向行程的未決信號佇列(pending signal queue)

  1. tatic int send_signal(int sig, struct siginfo *info, struct task_struct *t,

  2.            struct sigpending *signals)

  3. {

  4.    struct sigqueue * q = NULL; //pending signal queue

  5.    int ret = 0;

  6.    /*

  7.     * fast-pathed signals for kernel-internal things like SIGSTOP

  8.     * or SIGKILL.

  9.     */

  10.    if ((unsigned long)info == 2)

  11.        goto out_set;

  12.    q = __sigqueue_alloc(t, GFP_ATOMIC);

  13.    if (q) {

  14.                //add signal to queue

  15.        list_add_tail(&q->list, &signals->list);

  16.        switch ((unsigned long) info) {

  17.                //0 represent signal come from kernel ,1 user process

  18.        case 0:

  19.            q->info.si_signo = sig;

  20.            q->info.si_errno = 0;

  21.            q->info.si_code = SI_USER;

  22.            q->info.si_pid = current->pid;

  23.            q->info.si_uid = current->uid;

  24.            break;

  25.        case 1:

  26.            q->info.si_signo = sig;

  27.            q->info.si_errno = 0;

  28.            q->info.si_code = SI_KERNEL;

  29.            q->info.si_pid = 0;

  30.            q->info.si_uid = 0;

  31.            break;

  32.        default:

  33.            copy_siginfo(&q->info, info);

  34.            break;

  35.        }

  36.    } else {

  37.        if (sig >= SIGRTMIN && info && (unsigned long)info != 1

  38.           && info->si_code != SI_USER)

  39.        /*

  40.         * Queue overflow, abort.  We may abort if the signal was rt

  41.         * and sent by user using something other than kill().

  42.         */

  43.            return -EAGAIN;

  44.        if (((unsigned long)info > 1) && (info->si_code == SI_TIMER))

  45.            /*

  46.             * Set up a return to indicate that we dropped

  47.             * the signal.

  48.             */

  49.            ret = info->si_sys_private;

  50.    }

  51. out_set:

  52.    sigaddset(&signals->signal, sig);

  53.    return ret;

  54. }

4 信號的傳遞(delivery)

信號的傳遞就是對信號的處理過程。

4.1 信號處理的時機

內核在允許行程恢覆在用戶態的執行之前,檢查thread_info的標誌TIF_SIGPENDING的值。每當內核處理完一個中斷或異常時,就檢查是否存在未決的信號。

4.2 忽略和預設操作

內核呼叫do_signal()函式處理信號。前面提過,對於每種信號內核有三種處理手段, 忽略信號,執行預設操作,捕捉信號。

do_signal 接收兩個引數: 
struct pt_regs regs : regs是內核的硬體背景關係,儲存一組暫存器值 
sigset_t 
oldset :oldset指向信號接收行程的信號屏蔽字

do_signal的核心是重覆呼叫dequeue_signal()函式的死迴圈,直到私有未決信號佇列和共享未決信號佇列都沒有非阻塞的未決信號時,迴圈結束。dequeue_signal首先處理私有未決信號佇列中的信號然後處理共享佇列中的信號。

  1. signr = dequeue_signal(current, mask, info);

et_signal_to_deliver

信號傳遞的大部分工作由get_signal_to_deliver完成。

  1. nt get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,

  2.              struct pt_regs *regs, void *cookie)

  3. {

  4.        //get the signal mask of current process

  5.    sigset_t *mask = &current->blocked;

  6.    int signr = 0;

  7. relock:

  8.    spin_lock_irq(&current->sighand->siglock);

  9.    for (;;) {

  10.        struct k_sigaction *ka;

  11.                //核心陳述句

  12.        signr = dequeue_signal(current, mask, info);

  13.                /*exit point 18*/

  14.        if (!signr)

  15.            break; /* will return 0 */

  16.                //獲取對信號的aciton

  17.        ka = &current->sighand->action[signr-1];

  18.        if (ka->sa.sa_handler == SIG_IGN) /* Do nothing.  */

  19.            continue;

  20.        if (ka->sa.sa_handler != SIG_DFL) {

  21.            /* Run the handler.  */

  22.            *return_ka = *ka;

  23.            if (ka->sa.sa_flags & SA_ONESHOT)

  24.                ka->sa.sa_handler = SIG_DFL;

  25.            break; /* will return non-zero "signr" value */

  26.        }

  27.        if (sig_kernel_ignore(signr)) /* Default is nothing. */

  28.            continue;

  29.        /* Init gets no signals it doesn't want.  */

  30.        if (current->pid == 1)

  31.            continue;

  32.        if (sig_kernel_stop(signr)) {

  33.            if (signr != SIGSTOP) {

  34.                spin_unlock_irq(&current->sighand->siglock);

  35.                if (is_orphaned_pgrp(process_group(current)))

  36.                    goto relock;

  37.                spin_lock_irq(&current->sighand->siglock);

  38.            }

  39.            if (likely(do_signal_stop(signr))) {

  40.                /* It released the siglock.  */

  41.                goto relock;

  42.            }

  43.            continue;

  44.        }

  45.        spin_unlock_irq(&current->sighand->siglock);

  46.        /*

  47.         * Anything else is fatal, maybe with a core dump.

  48.         */

  49.        current->flags |= PF_SIGNALED;

  50.        if (sig_kernel_coredump(signr)) {

  51.            do_coredump((long)signr, signr, regs);

  52.        }

  53.        do_group_exit(signr);

  54.    }

  55.    spin_unlock_irq(&current->sighand->siglock);

  56.    return signr;

  57. }

4.2 捕捉信號handle_signal

信號的捕捉由handle_signal完成,函式由5個引數 
sig: 信號編碼 
info: 攜帶有描述信號的信息 
ka: 攜帶有對於信號的處理函式 
oldset: 信號屏蔽字 
regs: 內核棧中的硬體背景關係(暫存器)

關於信號處理函式的執行有一點需要說明,由於信號處理函式是在用戶態下執行,而從內核態切換到用戶態時內核棧會被清空。而內核棧中儲存有被信號中斷的程式的cs和eip。如果內核棧被清空則無法在從內核態中恢復被信號中斷的程式的執行。 
解決辦法是在內核處理信號時,把儲存在內核態堆棧的硬體背景關係拷貝到當前行程的用戶態堆棧中。 
這樣在信號處理末期,就可以從內核棧中恢復被中斷程式的CS和EIP,從而恢復程式的正常執行。

  1. tatic void

  2. handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,

  3.          sigset_t *oldset, struct pt_regs * regs)

  4. {

  5.    /* Are we from a system call? */

  6.    if (regs->orig_eax >= 0) {

  7.        /* If so, check system call restarting.. */

  8.        switch (regs->eax) {

  9.                case -ERESTART_RESTARTBLOCK:

  10.            case -ERESTARTNOHAND:

  11.                regs->eax = -EINTR;

  12.                break;

  13.            case -ERESTARTSYS:

  14.                if (!(ka->sa.sa_flags & SA_RESTART)) {

  15.                    regs->eax = -EINTR;

  16.                    break;

  17.                }

  18.            /* fallthrough */

  19.            case -ERESTARTNOINTR:

  20.                regs->eax = regs->orig_eax;

  21.                regs->eip -= 2;

  22.        }

  23.    }

  24.    /* Set up the stack frame */

  25.    if (ka->sa.sa_flags & SA_SIGINFO)

  26.        setup_rt_frame(sig, ka, info, oldset, regs);

  27.    else

  28.        setup_frame(sig, ka, oldset, regs);

  29.    if (!(ka->sa.sa_flags & SA_NODEFER)) {

  30.        spin_lock_irq(&current->sighand->siglock);

  31.        sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);

  32.        sigaddset(&current->blocked,sig);

  33.        recalc_sigpending();

  34.        spin_unlock_irq(&current->sighand->siglock);

  35.    }

  36. }

赞(0)

分享創造快樂

© 2021 知識星球   网站地图