自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

[[414544]]

本文轉(zhuǎn)載自微信公眾號(hào)「Linux內(nèi)核那些事」,作者songsong001。轉(zhuǎn)載本文請(qǐng)聯(lián)系Linux內(nèi)核那些事公眾號(hào)。

什么是信號(hào)

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

  • 程序錯(cuò)誤:除零,非法內(nèi)存訪問(wèn)等。
  • 外部信號(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))兩種類(lèi)型,對(duì)應(yīng)于 Linux 的信號(hào)值為 1-31 和 34-64。

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

  1. #include <signal.h> 
  2. #include <unistd.h> 
  3. #include <stdio.h> 
  4.  
  5. void sigcb(int signo) { 
  6.     switch (signo) { 
  7.     case SIGHUP: 
  8.         printf("Get a signal -- SIGHUP\n"); 
  9.         break; 
  10.     case SIGINT: 
  11.         printf("Get a signal -- SIGINT\n"); 
  12.         break; 
  13.     case SIGQUIT: 
  14.         printf("Get a signal -- SIGQUIT\n"); 
  15.         break; 
  16.     } 
  17.     return
  18.  
  19. int main() { 
  20.     signal(SIGHUP, sigcb); 
  21.     signal(SIGINT, sigcb); 
  22.     signal(SIGQUIT, sigcb); 
  23.     for (;;) { 
  24.         sleep(1); 
  25.     } 

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

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

接下來(lái)我們分析一下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)的字段,如下:

  1. struct task_struct { 
  2.     ... 
  3.     int sigpending; 
  4.     ... 
  5.     struct signal_struct *sig; 
  6.     sigset_t blocked; 
  7.     struct sigpending pending; 
  8.     ... 

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

  1. #define  _NSIG  64 
  2.  
  3. struct signal_struct { 
  4.  atomic_t  count
  5.  struct k_sigaction action[_NSIG]; 
  6.  spinlock_t  siglock; 
  7. }; 
  8.  
  9. typedef void (*__sighandler_t)(int); 
  10.  
  11. struct sigaction { 
  12.  __sighandler_t sa_handler; 
  13.  unsigned long sa_flags; 
  14.  void (*sa_restorer)(void); 
  15.  sigset_t sa_mask; 
  16. }; 
  17.  
  18. struct k_sigaction { 
  19.  struct sigaction sa; 
  20. }; 

可以看出,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è)賮?lái)看看 struct sigaction 這個(gè)結(jié)構(gòu),其中 sa_handler 成員是類(lèi)型為 __sighandler_t 的函數(shù)指針,代表著信號(hào)處理的方法。

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

  1. struct sigqueue { 
  2.  struct sigqueue *next
  3.  siginfo_t info; 
  4. }; 
  5.  
  6. struct sigpending { 
  7.  struct sigqueue *head, **tail; 
  8.  sigset_t signal; 
  9. }; 

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

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

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

  1. 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(),代碼如下:

  1. asmlinkage long 
  2. sys_kill(int pid, int sig) 
  3.  struct siginfo info; 
  4.  
  5.  info.si_signo = sig; 
  6.  info.si_errno = 0; 
  7.  info.si_code = SI_USER; 
  8.  info.si_pid = current->pid; 
  9.  info.si_uid = current->uid; 
  10.  
  11.  return kill_something_info(sig, &info, pid); 

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

  1. static int kill_something_info(int sig, struct siginfo *info, int pid) 
  2.  if (!pid) { 
  3.   return kill_pg_info(sig, info, current->pgrp); 
  4.  } else if (pid == -1) { 
  5.   int retval = 0, count = 0; 
  6.   struct task_struct * p; 
  7.  
  8.   read_lock(&tasklist_lock); 
  9.   for_each_task(p) { 
  10.    if (p->pid > 1 && p != current) { 
  11.     int err = send_sig_info(sig, info, p); 
  12.     ++count
  13.     if (err != -EPERM) 
  14.      retval = err; 
  15.    } 
  16.   } 
  17.   read_unlock(&tasklist_lock); 
  18.   return count ? retval : -ESRCH; 
  19.  } else if (pid < 0) { 
  20.   return kill_pg_info(sig, info, -pid); 
  21.  } else { 
  22.   return kill_proc_info(sig, info, pid); 
  23.  } 

kill_something_info() 函數(shù)根據(jù)傳入pid 的不同來(lái)進(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ù)來(lái)處理信號(hào)發(fā)送操作,其代碼如下:

  1. inline int 
  2. kill_proc_info(int sig, struct siginfo *info, pid_t pid) 
  3.  int error; 
  4.  struct task_struct *p; 
  5.  
  6.  read_lock(&tasklist_lock); 
  7.  p = find_task_by_pid(pid); 
  8.  error = -ESRCH; 
  9.  if (p) 
  10.   error = send_sig_info(sig, info, p); 
  11.  read_unlock(&tasklist_lock); 
  12.  return error; 

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

  1. int 
  2. send_sig_info(int sig, struct siginfo *info, struct task_struct *t) 
  3.     unsigned long flags; 
  4.     int ret; 
  5.  
  6.     ret = -EINVAL; 
  7.     if (sig < 0 || sig > _NSIG) 
  8.         goto out_nolock; 
  9.  
  10.     ret = -EPERM; 
  11.     if (bad_signal(sig, info, t)) 
  12.         goto out_nolock; 
  13.  
  14.     ret = 0; 
  15.     if (!sig || !t->sig) 
  16.         goto out_nolock; 
  17.  
  18.     spin_lock_irqsave(&t->sigmask_lock, flags); 
  19.     handle_stop_signal(sig, t); 
  20.  
  21.     if (ignored_signal(sig, t)) 
  22.         goto out
  23.  
  24.     if (sig < SIGRTMIN && sigismember(&t->pending.signal, sig)) 
  25.         goto out
  26.  
  27.     ret = deliver_signal(sig, info, t); 
  28. out
  29.     spin_unlock_irqrestore(&t->sigmask_lock, flags); 
  30.     if ((t->state & TASK_INTERRUPTIBLE) && signal_pending(t)) 
  31.         wake_up_process(t); 
  32.  
  33. out_nolock: 
  34.     return ret; 

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

  1. static int deliver_signal(int sig, struct siginfo *info, struct task_struct *t) 
  2.  int retval = send_signal(sig, info, &t->pending); 
  3.  
  4.  if (!retval && !sigismember(&t->blocked, sig)) 
  5.   signal_wake_up(t); 
  6.  
  7.  return retval; 

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

  1. static int send_signal(int sig, struct siginfo *info, struct sigpending *signals) 
  2.     struct sigqueue * q = NULL
  3.  
  4.     if (atomic_read(&nr_queued_signals) < max_queued_signals) { 
  5.         q = kmem_cache_alloc(sigqueue_cachep, GFP_ATOMIC); 
  6.     } 
  7.  
  8.     if (q) { 
  9.         atomic_inc(&nr_queued_signals); 
  10.         q->next = NULL
  11.         *signals->tail = q; 
  12.         signals->tail = &q->next
  13.         switch ((unsigned long) info) { 
  14.             case 0: 
  15.                 q->info.si_signo = sig; 
  16.                 q->info.si_errno = 0; 
  17.                 q->info.si_code = SI_USER; 
  18.                 q->info.si_pid = current->pid; 
  19.                 q->info.si_uid = current->uid; 
  20.                 break; 
  21.             case 1: 
  22.                 q->info.si_signo = sig; 
  23.                 q->info.si_errno = 0; 
  24.                 q->info.si_code = SI_KERNEL; 
  25.                 q->info.si_pid = 0; 
  26.                 q->info.si_uid = 0; 
  27.                 break; 
  28.             default
  29.                 copy_siginfo(&q->info, info); 
  30.                 break; 
  31.         } 
  32.     } else if (sig >= SIGRTMIN && info && (unsigned long)info != 1 
  33.            && info->si_code != SI_USER) { 
  34.         return -EAGAIN; 
  35.     } 
  36.  
  37.     sigaddset(&signals->signal, sig); 
  38.     return 0; 

send_signal() 函數(shù)雖然比較長(zhǎng),但邏輯還是比較簡(jiǎn)單的。在 信號(hào)處理相關(guān)的數(shù)據(jù)結(jié)構(gòu) 一節(jié)我們介紹過(guò)進(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ù)來(lái)申請(qǐng)一個(gè)類(lèi)型為 struct sigqueue 的隊(duì)列節(jié)點(diǎn),然后把節(jié)點(diǎn)添加到 pending 隊(duì)列中,接著根據(jù)參數(shù) info 的值來(lái)進(jìn)行不同的操作,最后通過(guò) sigaddset() 函數(shù)來(lái)設(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)完成,我們可以通過(guò)下面的調(diào)用鏈來(lái)更加直觀的理解此過(guò)程:

  1. kill()    
  2.   |                                         User Space 
  3. ========================================================= 
  4.   |                                         Kernel Space 
  5. sys_kill() 
  6.   └→  kill_something_info() 
  7.      └→ kill_proc_info() 
  8.         └→  find_task_by_pid() 
  9.         └→ send_sig_info() 
  10.            └→ bad_signal() 
  11.            └→ handle_stop_signal() 
  12.            └→ ignored_signal() 
  13.            └→ deliver_signal() 
  14.               └→ send_signal() 
  15.               |  └→  kmem_cache_alloc() 
  16.               |  └→ sigaddset() 
  17.               └→ 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)處理過(guò)程放置在進(jìn)程從內(nèi)核態(tài)返回到用戶(hù)態(tài)前,也就是在 ret_from_sys_call 處:

  1. // arch/i386/kernel/entry.S 
  2.  
  3. ENTRY(ret_from_sys_call) 
  4.  ... 
  5. ret_with_reschedule: 
  6.  ... 
  7.  cmpl $0, sigpending(%ebx)  // 檢查進(jìn)程的sigpending成員是否等于1 
  8.  jne signal_return          // 如果是就跳轉(zhuǎn)到 signal_return 處執(zhí)行 
  9. restore_all: 
  10.  RESTORE_ALL 
  11.  
  12.  ALIGN 
  13. signal_return: 
  14.  sti                             // 開(kāi)啟硬件中斷 
  15.  testl $(VM_MASK),EFLAGS(%esp) 
  16.  movl %esp,%eax 
  17.  jne v86_signal_return 
  18.  xorl %edx,%edx 
  19.  call SYMBOL_NAME(do_signal)    // 調(diào)用do_signal()函數(shù)進(jìn)行處理 
  20.  jmp restore_all 

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

  1. int do_signal(struct pt_regs *regs, sigset_t *oldset) 
  2.  siginfo_t info; 
  3.  struct k_sigaction *ka; 
  4.  
  5.  if ((regs->xcs & 3) != 3) 
  6.   return 1; 
  7.  
  8.  if (!oldset) 
  9.   oldset = &current->blocked; 
  10.  
  11.  for (;;) { 
  12.   unsigned long signr; 
  13.  
  14.   spin_lock_irq(&current->sigmask_lock); 
  15.   signr = dequeue_signal(&current->blocked, &info); 
  16.   spin_unlock_irq(&current->sigmask_lock); 
  17.  
  18.   if (!signr) 
  19.    break; 

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

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

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

  1. if (ka->sa.sa_handler == SIG_DFL) { 
  2.   int exit_code = signr; 
  3.  
  4.   /* Init gets no signals it doesn't want.  */ 
  5.   if (current->pid == 1) 
  6.    continue
  7.  
  8.   switch (signr) { 
  9.   case SIGCONT: case SIGCHLD: case SIGWINCH: 
  10.    continue
  11.  
  12.   case SIGTSTP: case SIGTTIN: case SIGTTOU: 
  13.    if (is_orphaned_pgrp(current->pgrp)) 
  14.     continue
  15.    /* FALLTHRU */ 
  16.  
  17.   case SIGSTOP: 
  18.    current->state = TASK_STOPPED; 
  19.    current->exit_code = signr; 
  20.    if (!(current->p_pptr->sig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP)) 
  21.     notify_parent(current, SIGCHLD); 
  22.    schedule(); 
  23.    continue
  24.  
  25.   case SIGQUIT: case SIGILL: case SIGTRAP: 
  26.   case SIGABRT: case SIGFPE: case SIGSEGV: 
  27.   case SIGBUS: case SIGSYS: case SIGXCPU: case SIGXFSZ: 
  28.    if (do_coredump(signr, regs)) 
  29.     exit_code |= 0x80; 
  30.    /* FALLTHRU */ 
  31.  
  32.   default
  33.    sigaddset(&current->pending.signal, signr); 
  34.    recalc_sigpending(current); 
  35.    current->flags |= PF_SIGNALED; 
  36.    do_exit(exit_code); 
  37.    /* NOTREACHED */ 
  38.   } 
  39.  } 
  40.  ... 
  41.  handle_signal(signr, ka, &info, oldset, regs); 
  42.  return 1; 
  43. ... 
  44. return 0; 

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

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

  1. static void 
  2. handle_signal(unsigned long sig, struct k_sigaction *ka, 
  3.        siginfo_t *info, sigset_t *oldset, struct pt_regs * regs) 
  4.  ... 
  5.  if (ka->sa.sa_flags & SA_SIGINFO) 
  6.   setup_rt_frame(sig, ka, info, oldset, regs); 
  7.  else 
  8.   setup_frame(sig, ka, oldset, regs); 
  9.  
  10.  if (ka->sa.sa_flags & SA_ONESHOT) 
  11.   ka->sa.sa_handler = SIG_DFL; 
  12.  
  13.  if (!(ka->sa.sa_flags & SA_NODEFER)) { 
  14.   spin_lock_irq(&current->sigmask_lock); 
  15.   sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask); 
  16.   sigaddset(&current->blocked,sig); 
  17.   recalc_sigpending(current); 
  18.   spin_unlock_irq(&current->sigmask_lock); 
  19.  } 

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

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

signal

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

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

signal-kernel-stack

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

  1. static void setup_frame(int sig, struct k_sigaction *ka, 
  2.    sigset_t *set, struct pt_regs * regs) 
  3.     ... 
  4.     regs->eip = (unsigned long) ka->sa.sa_handler; // regs是內(nèi)核棧中保存的寄存器集合 
  5.     ... 

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

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

  1. popl %eax  
  2. movl $__NR_sigreturn,%eax  
  3. int $0x80 

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

  1. static void setup_frame(int sig, struct k_sigaction *ka, 
  2.    sigset_t *set, struct pt_regs * regs) 
  3.  ... 
  4.   err |= __put_user(frame->retcode, &frame->pretcode); 
  5.   /* This is popl %eax ; movl $,%eax ; int $0x80 */ 
  6.   err |= __put_user(0xb858, (short *)(frame->retcode+0)); 
  7.   err |= __put_user(__NR_sigreturn, (int *)(frame->retcode+2)); 
  8.   err |= __put_user(0x80cd, (short *)(frame->retcode+6)); 
  9.  ... 

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

signal-user-stack

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

  1. asmlinkage int sys_sigreturn(unsigned long __unused) 
  2.  struct pt_regs *regs = (struct pt_regs *) &__unused; 
  3.  struct sigframe *frame = (struct sigframe *)(regs->esp - 8); 
  4.  sigset_t set
  5.  int eax; 
  6.  
  7.  if (verify_area(VERIFY_READ, frame, sizeof(*frame))) 
  8.   goto badframe; 
  9.  if (__get_user(set.sig[0], &frame->sc.oldmask) 
  10.      || (_NSIG_WORDS > 1 
  11.   && __copy_from_user(&set.sig[1], &frame->extramask, 
  12.         sizeof(frame->extramask)))) 
  13.   goto badframe; 
  14.  
  15.  sigdelsetmask(&set, ~_BLOCKABLE); 
  16.  spin_lock_irq(&current->sigmask_lock); 
  17.  current->blocked = set
  18.  recalc_sigpending(current); 
  19.  spin_unlock_irq(&current->sigmask_lock); 
  20.  
  21.  if (restore_sigcontext(regs, &frame->sc, &eax)) 
  22.   goto badframe; 
  23.  return eax; 
  24.  
  25. badframe: 
  26.  force_sig(SIGSEGV, current); 
  27.  return 0; 

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

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

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

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

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

  1. asmlinkage unsigned long 
  2. sys_signal(int sig, __sighandler_t handler) 
  3.  struct k_sigaction new_sa, old_sa; 
  4.  int ret; 
  5.  
  6.  new_sa.sa.sa_handler = handler; 
  7.  new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK; 
  8.  
  9.  ret = do_sigaction(sig, &new_sa, &old_sa); 
  10.  
  11.  return ret ? ret : (unsigned long)old_sa.sa.sa_handler; 

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

  1. int 
  2. do_sigaction(int sig, const struct k_sigaction *act, struct k_sigaction *oact) 
  3.     struct k_sigaction *k; 
  4.  
  5.     if (sig < 1 || sig > _NSIG || 
  6.         (act && (sig == SIGKILL || sig == SIGSTOP))) 
  7.         return -EINVAL; 
  8.  
  9.     k = &current->sig->action[sig-1]; 
  10.  
  11.     spin_lock(&current->sig->siglock); 
  12.  
  13.     if (oact) 
  14.         *oact = *k; 
  15.  
  16.     if (act) { 
  17.         *k = *act; 
  18.         sigdelsetmask(&k->sa.sa_mask, sigmask(SIGKILL) | sigmask(SIGSTOP)); 
  19.          
  20.         if (k->sa.sa_handler == SIG_IGN 
  21.             || (k->sa.sa_handler == SIG_DFL 
  22.             && (sig == SIGCONT || 
  23.                 sig == SIGCHLD || 
  24.                 sig == SIGWINCH))) { 
  25.             spin_lock_irq(&current->sigmask_lock); 
  26.             if (rm_sig_from_queue(sig, current)) 
  27.                 recalc_sigpending(current); 
  28.             spin_unlock_irq(&current->sigmask_lock); 
  29.         } 
  30.     } 
  31.  
  32.     spin_unlock(&current->sig->siglock); 
  33.     return 0; 

這個(gè)函數(shù)也不難,我們上面介紹過(guò),進(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 = ¤t->sig->action[sig-1] 就是獲取對(duì)應(yīng)信號(hào)的處理程序,然后把其設(shè)置為新的信號(hào)處理程序即可。

 

責(zé)任編輯:武曉燕 來(lái)源: Linux內(nèi)核那些事
相關(guān)推薦

2023-12-18 10:45:31

2019-07-01 09:22:15

Linux操作系統(tǒng)硬件

2020-03-31 14:40:24

HashMap源碼Java

2017-04-17 13:10:09

神經(jīng)網(wǎng)絡(luò)人工智能網(wǎng)絡(luò)

2024-10-10 17:55:57

LinuxACL訪問(wèn)控制列表

2021-06-06 13:06:34

JVM內(nèi)存分布

2016-08-18 00:21:12

網(wǎng)絡(luò)爬蟲(chóng)抓取網(wǎng)絡(luò)

2024-03-13 08:34:22

2024-08-12 12:30:27

2023-07-07 11:36:29

人工智能基礎(chǔ)模型

2025-01-09 11:14:14

2025-01-20 09:15:00

iOS 18.3蘋(píng)果iOS 18

2021-08-02 06:56:19

TypeScript編程語(yǔ)言編譯器

2019-05-22 09:50:42

Python沙箱逃逸網(wǎng)絡(luò)攻擊

2023-12-15 15:55:24

Linux線程同步

2021-10-06 20:23:08

Linux共享內(nèi)存

2019-02-26 15:20:31

CPU開(kāi)蓋器結(jié)構(gòu)

2021-06-23 16:05:05

鴻蒙HarmonyOS應(yīng)用

2021-11-02 10:53:56

Linux機(jī)制CPU

2021-02-21 11:25:17

云計(jì)算IaaSPaaS
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)