<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文完全看懂 | Linux 信號(hào)處理原理與實(shí)現(xiàn)

          共 25922字,需瀏覽 52分鐘

           ·

          2021-08-01 08:54

          什么是信號(hào)

          信號(hào)本質(zhì)上是在軟件層次上對(duì)中斷機(jī)制的一種模擬,其主要有以下幾種來源:

          • 程序錯(cuò)誤:除零,非法內(nèi)存訪問等。
          • 外部信號(hào):終端 Ctrl-C 產(chǎn)生 SGINT 信號(hào),定時(shí)器到期產(chǎn)生SIGALRM等。
          • 顯式請(qǐng)求:kill函數(shù)允許進(jìn)程發(fā)送任何信號(hào)給其他進(jìn)程或進(jìn)程組。

          目前 Linux 支持64種信號(hào)。信號(hào)分為非實(shí)時(shí)信號(hào)(不可靠信號(hào))和實(shí)時(shí)信號(hào)(可靠信號(hào))兩種類型,對(duì)應(yīng)于 Linux 的信號(hào)值為 1-31 和 34-64。

          信號(hào)是異步的,一個(gè)進(jìn)程不必通過任何操作來等待信號(hào)的到達(dá)。事實(shí)上,進(jìn)程也不知道信號(hào)到底什么時(shí)候到達(dá)。一般來說,我們只需要在進(jìn)程中設(shè)置信號(hào)相應(yīng)的處理函數(shù),當(dāng)有信號(hào)到達(dá)的時(shí)候,由系統(tǒng)異步觸發(fā)相應(yīng)的處理函數(shù)即可。如下代碼:

          #include <signal.h>
          #include <unistd.h>
          #include <stdio.h>

          void sigcb(int signo) {
              switch (signo) {
              case SIGHUP:
                  printf("Get a signal -- SIGHUP\n");
                  break;
              case SIGINT:
                  printf("Get a signal -- SIGINT\n");
                  break;
              case SIGQUIT:
                  printf("Get a signal -- SIGQUIT\n");
                  break;
              }
              return;
          }

          int main() {
              signal(SIGHUP, sigcb);
              signal(SIGINT, sigcb);
              signal(SIGQUIT, sigcb);
              for (;;) {
                  sleep(1);
              }
          }

          運(yùn)行程序后,當(dāng)我們按下 Ctrl+C 后,屏幕上將會(huì)打印 Get a signal -- SIGINT。當(dāng)然我們可以使用 kill -s SIGINT pid 命令來發(fā)送一個(gè)信號(hào)給進(jìn)程,屏幕同樣打印出 Get a signal -- SIGINT 的信息。

          信號(hào)實(shí)現(xiàn)原理

          接下來我們分析一下Linux對(duì)信號(hào)處理機(jī)制的實(shí)現(xiàn)原理。

          信號(hào)處理相關(guān)的數(shù)據(jù)結(jié)構(gòu)

          在進(jìn)程管理結(jié)構(gòu) task_struct 中有幾個(gè)與信號(hào)處理相關(guān)的字段,如下:

          struct task_struct {
              ...
              int sigpending;
              ...
              struct signal_struct *sig;
              sigset_t blocked;
              struct sigpending pending;
              ...
          }

          成員 sigpending 表示進(jìn)程是否有信號(hào)需要處理(1表示有,0表示沒有)。成員 blocked 表示被屏蔽的信息,每個(gè)位代表一個(gè)被屏蔽的信號(hào)。成員 sig 表示信號(hào)相應(yīng)的處理方法,其類型是 struct signal_struct,定義如下:

          #define  _NSIG  64

          struct signal_struct {
           atomic_t  count;
           struct k_sigaction action[_NSIG];
           spinlock_t  siglock;
          };

          typedef void (*__sighandler_t)(int);

          struct sigaction {
           __sighandler_t sa_handler;
           unsigned long sa_flags;
           void (*sa_restorer)(void);
           sigset_t sa_mask;
          };

          struct k_sigaction {
           struct sigaction sa;
          };

          可以看出,struct signal_struct 是個(gè)比較復(fù)雜的結(jié)構(gòu),其 action 成員是個(gè) struct k_sigaction 結(jié)構(gòu)的數(shù)組,數(shù)組中的每個(gè)成員代表著相應(yīng)信號(hào)的處理信息,而 struct k_sigaction 結(jié)構(gòu)其實(shí)是 struct sigaction 的簡(jiǎn)單封裝。

          我們?cè)賮砜纯?nbsp;struct sigaction 這個(gè)結(jié)構(gòu),其中 sa_handler 成員是類型為 __sighandler_t 的函數(shù)指針,代表著信號(hào)處理的方法。

          最后我們來看看 struct task_struct 結(jié)構(gòu)的 pending 成員,其類型為 struct sigpending,存儲(chǔ)著進(jìn)程接收到的信號(hào)隊(duì)列,struct sigpending 的定義如下:

          struct sigqueue {
           struct sigqueue *next;
           siginfo_t info;
          };

          struct sigpending {
           struct sigqueue *head, **tail;
           sigset_t signal;
          };

          當(dāng)進(jìn)程接收到一個(gè)信號(hào)時(shí),就需要把接收到的信號(hào)添加 pending 這個(gè)隊(duì)列中。

          發(fā)送信號(hào)

          可以通過 kill() 系統(tǒng)調(diào)用發(fā)送一個(gè)信號(hào)給指定的進(jìn)程,其原型如下:

          int kill(pid_t pid, int sig);

          參數(shù) pid 指定要接收信號(hào)進(jìn)程的ID,而參數(shù) sig 是要發(fā)送的信號(hào)。kill() 系統(tǒng)調(diào)用最終會(huì)進(jìn)入內(nèi)核態(tài),并且調(diào)用內(nèi)核函數(shù) sys_kill(),代碼如下:

          asmlinkage long
          sys_kill(int pid, int sig)
          {
           struct siginfo info;

           info.si_signo = sig;
           info.si_errno = 0;
           info.si_code = SI_USER;
           info.si_pid = current->pid;
           info.si_uid = current->uid;

           return kill_something_info(sig, &info, pid);
          }

          sys_kill() 的代碼比較簡(jiǎn)單,首先初始化 info 變量的成員,接著調(diào)用 kill_something_info() 函數(shù)來處理發(fā)送信號(hào)的操作。kill_something_info() 函數(shù)的代碼如下:

          static int kill_something_info(int sig, struct siginfo *info, int pid)
          {
           if (!pid) {
            return kill_pg_info(sig, info, current->pgrp);
           } else if (pid == -1) {
            int retval = 0, count = 0;
            struct task_struct * p;

            read_lock(&tasklist_lock);
            for_each_task(p) {
             if (p->pid > 1 && p != current) {
              int err = send_sig_info(sig, info, p);
              ++count;
              if (err != -EPERM)
               retval = err;
             }
            }
            read_unlock(&tasklist_lock);
            return count ? retval : -ESRCH;
           } else if (pid < 0) {
            return kill_pg_info(sig, info, -pid);
           } else {
            return kill_proc_info(sig, info, pid);
           }
          }

          kill_something_info() 函數(shù)根據(jù)傳入pid 的不同來進(jìn)行不同的操作,有如下4種可能:

          • pid 等于0時(shí),表示信號(hào)將送往所有與調(diào)用 kill() 的那個(gè)進(jìn)程屬同一個(gè)使用組的進(jìn)程。
          • pid 大于零時(shí),pid 是信號(hào)要送往的進(jìn)程ID。
          • pid 等于-1時(shí),信號(hào)將送往調(diào)用進(jìn)程有權(quán)給其發(fā)送信號(hào)的所有進(jìn)程,除了進(jìn)程1(init)。
          • pid 小于-1時(shí),信號(hào)將送往以-pid為組標(biāo)識(shí)的進(jìn)程。

          我們這里只分析 pid 大于0的情況,從上面的代碼可以知道,當(dāng) pid 大于0時(shí),會(huì)調(diào)用 kill_proc_info() 函數(shù)來處理信號(hào)發(fā)送操作,其代碼如下:

          inline int
          kill_proc_info(int sig, struct siginfo *info, pid_t pid)
          {
           int error;
           struct task_struct *p;

           read_lock(&tasklist_lock);
           p = find_task_by_pid(pid);
           error = -ESRCH;
           if (p)
            error = send_sig_info(sig, info, p);
           read_unlock(&tasklist_lock);
           return error;
          }

          kill_proc_info() 首先通過調(diào)用 find_task_by_pid() 函數(shù)來獲得 pid 對(duì)應(yīng)的進(jìn)程管理結(jié)構(gòu),然后通過 send_sig_info() 函數(shù)來發(fā)送信號(hào)給此進(jìn)程,send_sig_info() 函數(shù)代碼如下:

          int
          send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
          {
              unsigned long flags;
              int ret;

              ret = -EINVAL;
              if (sig < 0 || sig > _NSIG)
                  goto out_nolock;

              ret = -EPERM;
              if (bad_signal(sig, info, t))
                  goto out_nolock;

              ret = 0;
              if (!sig || !t->sig)
                  goto out_nolock;

              spin_lock_irqsave(&t->sigmask_lock, flags);
              handle_stop_signal(sig, t);

              if (ignored_signal(sig, t))
                  goto out;

              if (sig < SIGRTMIN && sigismember(&t->pending.signal, sig))
                  goto out;

              ret = deliver_signal(sig, info, t);
          out:
              spin_unlock_irqrestore(&t->sigmask_lock, flags);
              if ((t->state & TASK_INTERRUPTIBLE) && signal_pending(t))
                  wake_up_process(t);

          out_nolock:
              return ret;
          }

          send_sig_info() 首先調(diào)用 bad_signal() 函數(shù)來檢查是否有權(quán)發(fā)送信號(hào)給進(jìn)程,然后調(diào)用 ignored_signal() 函數(shù)來檢查信號(hào)是否被忽略,接著調(diào)用 deliver_signal() 函數(shù)開始發(fā)送信號(hào),最后如果進(jìn)程是睡眠狀態(tài)就喚醒進(jìn)程。我們接著來分析 deliver_signal() 函數(shù):

          static int deliver_signal(int sig, struct siginfo *info, struct task_struct *t)
          {
           int retval = send_signal(sig, info, &t->pending);

           if (!retval && !sigismember(&t->blocked, sig))
            signal_wake_up(t);

           return retval;
          }

          deliver_signal() 首先調(diào)用 send_signal() 函數(shù)進(jìn)行信號(hào)的發(fā)送,然后調(diào)用 signal_wake_up() 函數(shù)喚醒進(jìn)程。我們來分析一下最重要的函數(shù) send_signal()

          static int send_signal(int sig, struct siginfo *info, struct sigpending *signals)
          {
              struct sigqueue * q = NULL;

              if (atomic_read(&nr_queued_signals) < max_queued_signals) {
                  q = kmem_cache_alloc(sigqueue_cachep, GFP_ATOMIC);
              }

              if (q) {
                  atomic_inc(&nr_queued_signals);
                  q->next = NULL;
                  *signals->tail = q;
                  signals->tail = &q->next;
                  switch ((unsigned long) info) {
                      case 0:
                          q->info.si_signo = sig;
                          q->info.si_errno = 0;
                          q->info.si_code = SI_USER;
                          q->info.si_pid = current->pid;
                          q->info.si_uid = current->uid;
                          break;
                      case 1:
                          q->info.si_signo = sig;
                          q->info.si_errno = 0;
                          q->info.si_code = SI_KERNEL;
                          q->info.si_pid = 0;
                          q->info.si_uid = 0;
                          break;
                      default:
                          copy_siginfo(&q->info, info);
                          break;
                  }
              } else if (sig >= SIGRTMIN && info && (unsigned long)info != 1
                     && info->si_code != SI_USER) {
                  return -EAGAIN;
              }

              sigaddset(&signals->signal, sig);
              return 0;
          }

          send_signal() 函數(shù)雖然比較長,但邏輯還是比較簡(jiǎn)單的。在 信號(hào)處理相關(guān)的數(shù)據(jù)結(jié)構(gòu) 一節(jié)我們介紹過進(jìn)程管理結(jié)構(gòu) task_struct 有個(gè) pending 的成員變量,其用于保存接收到的信號(hào)隊(duì)列。send_signal() 函數(shù)的第三個(gè)參數(shù)就是進(jìn)程管理結(jié)構(gòu)的 pending 成員變量。

          send_signal() 首先調(diào)用 kmem_cache_alloc() 函數(shù)來申請(qǐng)一個(gè)類型為 struct sigqueue 的隊(duì)列節(jié)點(diǎn),然后把節(jié)點(diǎn)添加到 pending 隊(duì)列中,接著根據(jù)參數(shù) info 的值來進(jìn)行不同的操作,最后通過 sigaddset() 函數(shù)來設(shè)置信號(hào)對(duì)應(yīng)的標(biāo)志位,表示進(jìn)程接收到該信號(hào)。

          signal_wake_up() 函數(shù)會(huì)把進(jìn)程的 sigpending 成員變量設(shè)置為1,表示有信號(hào)需要處理,如果進(jìn)程是睡眠可中斷狀態(tài)還會(huì)喚醒進(jìn)程。

          至此,發(fā)送信號(hào)的流程已經(jīng)完成,我們可以通過下面的調(diào)用鏈來更加直觀的理解此過程:

          kill()   
          | User Space
          =========================================================
          | Kernel Space
          sys_kill()
          └→ kill_something_info()
          └→ kill_proc_info()
          └→ find_task_by_pid()
          └→ send_sig_info()
          └→ bad_signal()
          └→ handle_stop_signal()
          └→ ignored_signal()
          └→ deliver_signal()
          └→ send_signal()
          | └→ kmem_cache_alloc()
          | └→ sigaddset()
          └→ signal_wake_up()

          內(nèi)核觸發(fā)信號(hào)處理函數(shù)

          上面介紹了怎么發(fā)生一個(gè)信號(hào)給指定的進(jìn)程,但是什么時(shí)候會(huì)觸發(fā)信號(hào)相應(yīng)的處理函數(shù)呢?為了盡快讓信號(hào)得到處理,Linux把信號(hào)處理過程放置在進(jìn)程從內(nèi)核態(tài)返回到用戶態(tài)前,也就是在 ret_from_sys_call 處:

          // arch/i386/kernel/entry.S

          ENTRY(ret_from_sys_call)
           ...
          ret_with_reschedule:
           ...
           cmpl $0, sigpending(%ebx)  // 檢查進(jìn)程的sigpending成員是否等于1
           jne signal_return          // 如果是就跳轉(zhuǎn)到 signal_return 處執(zhí)行
          restore_all:
           RESTORE_ALL

           ALIGN
          signal_return:
           sti                             // 開啟硬件中斷
           testl $(VM_MASK),EFLAGS(%esp)
           movl %esp,%eax
           jne v86_signal_return
           xorl %edx,%edx
           call SYMBOL_NAME(do_signal)    // 調(diào)用do_signal()函數(shù)進(jìn)行處理
           jmp restore_all

          由于這是一段匯編代碼,有點(diǎn)不太直觀(大概知道意思就可以了),所以我在代碼中進(jìn)行了注釋。主要的邏輯就是首先檢查進(jìn)程的 sigpending 成員是否等于1,如果是調(diào)用 do_signal() 函數(shù)進(jìn)行處理,由于 do_signal() 函數(shù)代碼比較長,所以我們分段來說明,如下:

          int do_signal(struct pt_regs *regs, sigset_t *oldset)
          {
           siginfo_t info;
           struct k_sigaction *ka;

           if ((regs->xcs & 3) != 3)
            return 1;

           if (!oldset)
            oldset = &current->blocked;

           for (;;) {
            unsigned long signr;

            spin_lock_irq(&current->sigmask_lock);
            signr = dequeue_signal(&current->blocked, &info);
            spin_unlock_irq(&current->sigmask_lock);

            if (!signr)
             break;

          上面這段代碼的主要邏輯是通過 dequeue_signal() 函數(shù)獲取到進(jìn)程接收隊(duì)列中的一個(gè)信號(hào),如果沒有信號(hào),那么就跳出循環(huán)。我們接著來分析:

            ka = &current->sig->action[signr-1];
            if (ka->sa.sa_handler == SIG_IGN) {
             if (signr != SIGCHLD)
              continue;
             /* Check for SIGCHLD: it's special.  */
             while (sys_wait4(-1NULL, WNOHANG, NULL) > 0)
              /* nothing */;
             continue;
            }

          上面這段代碼首先獲取到信號(hào)對(duì)應(yīng)的處理方法,如果對(duì)此信號(hào)的處理是忽略的話,那么就直接跳過。

            if (ka->sa.sa_handler == SIG_DFL) {
             int exit_code = signr;

             /* Init gets no signals it doesn't want.  */
             if (current->pid == 1)
              continue;

             switch (signr) {
             case SIGCONT: case SIGCHLD: case SIGWINCH:
              continue;

             case SIGTSTP: case SIGTTIN: case SIGTTOU:
              if (is_orphaned_pgrp(current->pgrp))
               continue;
              /* FALLTHRU */

             case SIGSTOP:
              current->state = TASK_STOPPED;
              current->exit_code = signr;
              if (!(current->p_pptr->sig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP))
               notify_parent(current, SIGCHLD);
              schedule();
              continue;

             case SIGQUIT: case SIGILL: case SIGTRAP:
             case SIGABRT: case SIGFPE: case SIGSEGV:
             case SIGBUS: case SIGSYS: case SIGXCPU: case SIGXFSZ:
              if (do_coredump(signr, regs))
               exit_code |= 0x80;
              /* FALLTHRU */

             default:
              sigaddset(&current->pending.signal, signr);
              recalc_sigpending(current);
              current->flags |= PF_SIGNALED;
              do_exit(exit_code);
              /* NOTREACHED */
             }
            }
            ...
            handle_signal(signr, ka, &info, oldset, regs);
            return 1;
           }
           ...
           return 0;
          }

          上面的代碼表示,如果指定為默認(rèn)的處理方法,那么就使用系統(tǒng)的默認(rèn)處理方法去處理信號(hào),比如 SIGSEGV 信號(hào)的默認(rèn)處理方法就是使用 do_coredump() 函數(shù)來生成一個(gè) core dump 文件,并且通過調(diào)用 do_exit() 函數(shù)退出進(jìn)程。

          如果指定了自定義的處理方法,那么就通過 handle_signal() 函數(shù)去進(jìn)行處理,handle_signal() 函數(shù)代碼如下:

          static void
          handle_signal(unsigned long sig, struct k_sigaction *ka,
                 siginfo_t *info, sigset_t *oldset, struct pt_regs * regs)

          {
           ...
           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_ONESHOT)
            ka->sa.sa_handler = SIG_DFL;

           if (!(ka->sa.sa_flags & SA_NODEFER)) {
            spin_lock_irq(&current->sigmask_lock);
            sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
            sigaddset(&current->blocked,sig);
            recalc_sigpending(current);
            spin_unlock_irq(&current->sigmask_lock);
           }
          }

          由于信號(hào)處理程序是由用戶提供的,所以信號(hào)處理程序的代碼是在用戶態(tài)的。而從系統(tǒng)調(diào)用返回到用戶態(tài)前還是屬于內(nèi)核態(tài),CPU是禁止內(nèi)核態(tài)執(zhí)行用戶態(tài)代碼的,那么怎么辦?

          答案先返回到用戶態(tài)執(zhí)行信號(hào)處理程序,執(zhí)行完信號(hào)處理程序后再返回到內(nèi)核態(tài),再在內(nèi)核態(tài)完成收尾工作。聽起來有點(diǎn)繞,事實(shí)也的確是這樣。下面通過一副圖片來直觀的展示這個(gè)過程(圖片來源網(wǎng)絡(luò)):

          signal

          為了達(dá)到這個(gè)目的,Linux經(jīng)歷了一個(gè)十分崎嶇的過程。我們知道,從內(nèi)核態(tài)返回到用戶態(tài)時(shí),CPU要從內(nèi)核棧中找到返回到用戶態(tài)的地址(就是調(diào)用系統(tǒng)調(diào)用的下一條代碼指令地址),Linux為了先讓信號(hào)處理程序執(zhí)行,所以就需要把這個(gè)返回地址修改為信號(hào)處理程序的入口,這樣當(dāng)從系統(tǒng)調(diào)用返回到用戶態(tài)時(shí),就可以執(zhí)行信號(hào)處理程序了。

          所以,handle_signal() 調(diào)用了 setup_frame() 函數(shù)來構(gòu)建這個(gè)過程的運(yùn)行環(huán)境(其實(shí)就是修改內(nèi)核棧和用戶棧相應(yīng)的數(shù)據(jù)來完成)。我們先來看看內(nèi)核棧的內(nèi)存布局圖:

          signal-kernel-stack

          圖中的 eip 就是內(nèi)核態(tài)返回到用戶態(tài)后開始執(zhí)行的第一條指令地址,所以把 eip 改成信號(hào)處理程序的地址就可以在內(nèi)核態(tài)返回到用戶態(tài)的時(shí)候自動(dòng)執(zhí)行信號(hào)處理程序了。我們看看 setup_frame() 函數(shù)其中有一行代碼就是修改 eip 的值,如下:

          static void setup_frame(int sig, struct k_sigaction *ka,
             sigset_t *set, struct pt_regs * regs)

          {
              ...
              regs->eip = (unsigned long) ka->sa.sa_handler; // regs是內(nèi)核棧中保存的寄存器集合
              ...
          }

          現(xiàn)在可以在內(nèi)核態(tài)返回到用戶態(tài)時(shí)自動(dòng)執(zhí)行信號(hào)處理程序了,但是當(dāng)信號(hào)處理程序執(zhí)行完怎么返回到內(nèi)核態(tài)呢?Linux的做法就是在用戶態(tài)棧空間構(gòu)建一個(gè) Frame(幀)(我也不知道為什么要這樣叫),構(gòu)建這個(gè)幀的目的就是為了執(zhí)行完信號(hào)處理程序后返回到內(nèi)核態(tài),并恢復(fù)原來內(nèi)核棧的內(nèi)容。返回到內(nèi)核態(tài)的方式是調(diào)用一個(gè)名為 sigreturn() 系統(tǒng)調(diào)用,然后再 sigreturn() 中恢復(fù)原來內(nèi)核棧的內(nèi)容。

          怎樣能在執(zhí)行完信號(hào)處理程序后調(diào)用 sigreturn() 系統(tǒng)調(diào)用呢?其實(shí)跟前面修改內(nèi)核棧 eip 的值一樣,這里修改的是用戶棧 eip 的值,修改后跳轉(zhuǎn)到一個(gè)執(zhí)行下面代碼的地方(用戶棧的某一處):

          popl %eax 
          movl $__NR_sigreturn,%eax
          int $0x80

          從上面的匯編代碼可以知道,這里就是調(diào)用了 sigreturn() 系統(tǒng)調(diào)用。修改用戶棧的代碼在 setup_frame() 中,代碼如下:

          static void setup_frame(int sig, struct k_sigaction *ka,
             sigset_t *set, struct pt_regs * regs)

          {
           ...
            err |= __put_user(frame->retcode, &frame->pretcode);
            /* This is popl %eax ; movl $,%eax ; int $0x80 */
            err |= __put_user(0xb858, (short *)(frame->retcode+0));
            err |= __put_user(__NR_sigreturn, (int *)(frame->retcode+2));
            err |= __put_user(0x80cd, (short *)(frame->retcode+6));
           ...
          }

          這幾行代碼比較難懂,其實(shí)就是修改信號(hào)程序程序返回后要執(zhí)行代碼的地址。修改后如下圖:

          signal-user-stack

          這樣執(zhí)行完信號(hào)處理程序后就會(huì)調(diào)用 sigreturn(),而 sigreturn() 要做的工作就是恢復(fù)原來內(nèi)核棧的內(nèi)容了,我們來看看 sigreturn() 的代碼:

          asmlinkage int sys_sigreturn(unsigned long __unused)
          {
           struct pt_regs *regs = (struct pt_regs *) &__unused;
           struct sigframe *frame = (struct sigframe *)(regs->esp - 8);
           sigset_t set;
           int eax;

           if (verify_area(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->sigmask_lock);
           current->blocked = set;
           recalc_sigpending(current);
           spin_unlock_irq(&current->sigmask_lock);

           if (restore_sigcontext(regs, &frame->sc, &eax))
            goto badframe;
           return eax;

          badframe:
           force_sig(SIGSEGV, current);
           return 0;
          }

          其中最重要的是調(diào)用 restore_sigcontext() 恢復(fù)原來內(nèi)核棧的內(nèi)容,要恢復(fù)原來內(nèi)核棧的內(nèi)容首先是要指定原來內(nèi)核棧的內(nèi)容,所以先要保存原來內(nèi)核棧的內(nèi)容。保存原來內(nèi)核棧的內(nèi)容也是在 setup_frame() 函數(shù)中,setup_frame() 函數(shù)把原來內(nèi)核棧的內(nèi)容保存到用戶棧中(也就是上面所說的  中)。restore_sigcontext() 函數(shù)就是從用戶棧中讀取原來內(nèi)核棧的數(shù)據(jù),然后恢復(fù)之。保存內(nèi)核棧內(nèi)容主要由 setup_sigcontext() 函數(shù)完成,有興趣可以查閱代碼,這里就不做詳細(xì)說明了。

          這樣,當(dāng)從 sigreturn() 系統(tǒng)調(diào)用返回時(shí),就可以按原來的路徑返回到用戶程序的下一個(gè)執(zhí)行點(diǎn)(比如調(diào)用系統(tǒng)調(diào)用的下一行代碼)。

          設(shè)置信號(hào)處理程序

          最后我們來分析一下怎么設(shè)置一個(gè)信號(hào)處理程序。

          用戶可以通過 signal() 系統(tǒng)調(diào)用設(shè)置一個(gè)信號(hào)處理程序,我們來看看 signal() 系統(tǒng)調(diào)用的代碼:

          asmlinkage unsigned long
          sys_signal(int sig, __sighandler_t handler)
          {
           struct k_sigaction new_saold_sa;
           int ret;

           new_sa.sa.sa_handler = handler;
           new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;

           ret = do_sigaction(sig, &new_sa, &old_sa);

           return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
          }

          代碼比較簡(jiǎn)單,就是先設(shè)置一個(gè)新的 struct k_sigaction 結(jié)構(gòu),把其 sa.sa_handler 字段設(shè)置為用戶自定義的處理程序。然后通過 do_sigaction() 函數(shù)進(jìn)行設(shè)置,代碼如下:

          int
          do_sigaction(int sig, const struct k_sigaction *act, struct k_sigaction *oact)
          {
              struct k_sigaction *k;

              if (sig < 1 || sig > _NSIG ||
                  (act && (sig == SIGKILL || sig == SIGSTOP)))
                  return -EINVAL;

              k = &current->sig->action[sig-1];

              spin_lock(&current->sig->siglock);

              if (oact)
                  *oact = *k;

              if (act) {
                  *k = *act;
                  sigdelsetmask(&k->sa.sa_mask, sigmask(SIGKILL) | sigmask(SIGSTOP));
                  
                  if (k->sa.sa_handler == SIG_IGN
                      || (k->sa.sa_handler == SIG_DFL
                      && (sig == SIGCONT ||
                          sig == SIGCHLD ||
                          sig == SIGWINCH))) {
                      spin_lock_irq(&current->sigmask_lock);
                      if (rm_sig_from_queue(sig, current))
                          recalc_sigpending(current);
                      spin_unlock_irq(&current->sigmask_lock);
                  }
              }

              spin_unlock(&current->sig->siglock);
              return 0;
          }

          這個(gè)函數(shù)也不難,我們上面介紹過,進(jìn)程管理結(jié)構(gòu)中有個(gè) sig 的字段,它是一個(gè) struct k_sigaction 結(jié)構(gòu)的數(shù)組,每個(gè)元素保存著對(duì)應(yīng)信號(hào)的處理程序,所以 do_sigaction() 函數(shù)就是修改這個(gè)信號(hào)處理程序。代碼 k = &current->sig->action[sig-1] 就是獲取對(duì)應(yīng)信號(hào)的處理程序,然后把其設(shè)置為新的信號(hào)處理程序即可。


          瀏覽 73
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产投拍导航 | 淫色淫色网站 | 精品毛片一区二区免费看 | 豆花视频网站在线 | 丁香激情五月 |