精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

一文看懂 Linux 信號處理原理與實現

系統 Linux
信號是異步的,一個進程不必通過任何操作來等待信號的到達。事實上,進程也不知道信號到底什么時候到達。一般來說,我們只需要在進程中設置信號相應的處理函數,當有信號到達的時候,由系統異步觸發相應的處理函數即可。

[[414544]]

本文轉載自微信公眾號「Linux內核那些事」,作者songsong001。轉載本文請聯系Linux內核那些事公眾號。

什么是信號

信號本質上是在軟件層次上對中斷機制的一種模擬,其主要有以下幾種來源:

  • 程序錯誤:除零,非法內存訪問等。
  • 外部信號:終端 Ctrl-C 產生 SGINT 信號,定時器到期產生SIGALRM等。
  • 顯式請求:kill函數允許進程發送任何信號給其他進程或進程組。

目前 Linux 支持64種信號。信號分為非實時信號(不可靠信號)和實時信號(可靠信號)兩種類型,對應于 Linux 的信號值為 1-31 和 34-64。

信號是異步的,一個進程不必通過任何操作來等待信號的到達。事實上,進程也不知道信號到底什么時候到達。一般來說,我們只需要在進程中設置信號相應的處理函數,當有信號到達的時候,由系統異步觸發相應的處理函數即可。如下代碼:

  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.     } 

運行程序后,當我們按下 Ctrl+C 后,屏幕上將會打印 Get a signal -- SIGINT。當然我們可以使用 kill -s SIGINT pid 命令來發送一個信號給進程,屏幕同樣打印出 Get a signal -- SIGINT 的信息。

信號實現原理

接下來我們分析一下Linux對信號處理機制的實現原理。

信號處理相關的數據結構

在進程管理結構 task_struct 中有幾個與信號處理相關的字段,如下:

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

成員 sigpending 表示進程是否有信號需要處理(1表示有,0表示沒有)。成員 blocked 表示被屏蔽的信息,每個位代表一個被屏蔽的信號。成員 sig 表示信號相應的處理方法,其類型是 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 是個比較復雜的結構,其 action 成員是個 struct k_sigaction 結構的數組,數組中的每個成員代表著相應信號的處理信息,而 struct k_sigaction 結構其實是 struct sigaction 的簡單封裝。

我們再來看看 struct sigaction 這個結構,其中 sa_handler 成員是類型為 __sighandler_t 的函數指針,代表著信號處理的方法。

最后我們來看看 struct task_struct 結構的 pending 成員,其類型為 struct sigpending,存儲著進程接收到的信號隊列,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. }; 

當進程接收到一個信號時,就需要把接收到的信號添加 pending 這個隊列中。

發送信號

可以通過 kill() 系統調用發送一個信號給指定的進程,其原型如下:

  1. int kill(pid_t pid, int sig); 

參數 pid 指定要接收信號進程的ID,而參數 sig 是要發送的信號。kill() 系統調用最終會進入內核態,并且調用內核函數 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() 的代碼比較簡單,首先初始化 info 變量的成員,接著調用 kill_something_info() 函數來處理發送信號的操作。kill_something_info() 函數的代碼如下:

  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() 函數根據傳入pid 的不同來進行不同的操作,有如下4種可能:

  • pid 等于0時,表示信號將送往所有與調用 kill() 的那個進程屬同一個使用組的進程。
  • pid 大于零時,pid 是信號要送往的進程ID。
  • pid 等于-1時,信號將送往調用進程有權給其發送信號的所有進程,除了進程1(init)。
  • pid 小于-1時,信號將送往以-pid為組標識的進程。

我們這里只分析 pid 大于0的情況,從上面的代碼可以知道,當 pid 大于0時,會調用 kill_proc_info() 函數來處理信號發送操作,其代碼如下:

  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() 首先通過調用 find_task_by_pid() 函數來獲得 pid 對應的進程管理結構,然后通過 send_sig_info() 函數來發送信號給此進程,send_sig_info() 函數代碼如下:

  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() 首先調用 bad_signal() 函數來檢查是否有權發送信號給進程,然后調用 ignored_signal() 函數來檢查信號是否被忽略,接著調用 deliver_signal() 函數開始發送信號,最后如果進程是睡眠狀態就喚醒進程。我們接著來分析 deliver_signal() 函數:

  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() 首先調用 send_signal() 函數進行信號的發送,然后調用 signal_wake_up() 函數喚醒進程。我們來分析一下最重要的函數 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() 函數雖然比較長,但邏輯還是比較簡單的。在 信號處理相關的數據結構 一節我們介紹過進程管理結構 task_struct 有個 pending 的成員變量,其用于保存接收到的信號隊列。send_signal() 函數的第三個參數就是進程管理結構的 pending 成員變量。

send_signal() 首先調用 kmem_cache_alloc() 函數來申請一個類型為 struct sigqueue 的隊列節點,然后把節點添加到 pending 隊列中,接著根據參數 info 的值來進行不同的操作,最后通過 sigaddset() 函數來設置信號對應的標志位,表示進程接收到該信號。

signal_wake_up() 函數會把進程的 sigpending 成員變量設置為1,表示有信號需要處理,如果進程是睡眠可中斷狀態還會喚醒進程。

至此,發送信號的流程已經完成,我們可以通過下面的調用鏈來更加直觀的理解此過程:

  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() 

內核觸發信號處理函數

上面介紹了怎么發生一個信號給指定的進程,但是什么時候會觸發信號相應的處理函數呢?為了盡快讓信號得到處理,Linux把信號處理過程放置在進程從內核態返回到用戶態前,也就是在 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)  // 檢查進程的sigpending成員是否等于1 
  8.  jne signal_return          // 如果是就跳轉到 signal_return 處執行 
  9. restore_all: 
  10.  RESTORE_ALL 
  11.  
  12.  ALIGN 
  13. signal_return: 
  14.  sti                             // 開啟硬件中斷 
  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)    // 調用do_signal()函數進行處理 
  20.  jmp restore_all 

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

  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; 

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

  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

上面這段代碼首先獲取到信號對應的處理方法,如果對此信號的處理是忽略的話,那么就直接跳過。

  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; 

上面的代碼表示,如果指定為默認的處理方法,那么就使用系統的默認處理方法去處理信號,比如 SIGSEGV 信號的默認處理方法就是使用 do_coredump() 函數來生成一個 core dump 文件,并且通過調用 do_exit() 函數退出進程。

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

  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.  } 

由于信號處理程序是由用戶提供的,所以信號處理程序的代碼是在用戶態的。而從系統調用返回到用戶態前還是屬于內核態,CPU是禁止內核態執行用戶態代碼的,那么怎么辦?

答案先返回到用戶態執行信號處理程序,執行完信號處理程序后再返回到內核態,再在內核態完成收尾工作。聽起來有點繞,事實也的確是這樣。下面通過一副圖片來直觀的展示這個過程(圖片來源網絡):

signal

為了達到這個目的,Linux經歷了一個十分崎嶇的過程。我們知道,從內核態返回到用戶態時,CPU要從內核棧中找到返回到用戶態的地址(就是調用系統調用的下一條代碼指令地址),Linux為了先讓信號處理程序執行,所以就需要把這個返回地址修改為信號處理程序的入口,這樣當從系統調用返回到用戶態時,就可以執行信號處理程序了。

所以,handle_signal() 調用了 setup_frame() 函數來構建這個過程的運行環境(其實就是修改內核棧和用戶棧相應的數據來完成)。我們先來看看內核棧的內存布局圖:

signal-kernel-stack

圖中的 eip 就是內核態返回到用戶態后開始執行的第一條指令地址,所以把 eip 改成信號處理程序的地址就可以在內核態返回到用戶態的時候自動執行信號處理程序了。我們看看 setup_frame() 函數其中有一行代碼就是修改 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是內核棧中保存的寄存器集合 
  5.     ... 

現在可以在內核態返回到用戶態時自動執行信號處理程序了,但是當信號處理程序執行完怎么返回到內核態呢?Linux的做法就是在用戶態棧空間構建一個 Frame(幀)(我也不知道為什么要這樣叫),構建這個幀的目的就是為了執行完信號處理程序后返回到內核態,并恢復原來內核棧的內容。返回到內核態的方式是調用一個名為 sigreturn() 系統調用,然后再 sigreturn() 中恢復原來內核棧的內容。

怎樣能在執行完信號處理程序后調用 sigreturn() 系統調用呢?其實跟前面修改內核棧 eip 的值一樣,這里修改的是用戶棧 eip 的值,修改后跳轉到一個執行下面代碼的地方(用戶棧的某一處):

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

從上面的匯編代碼可以知道,這里就是調用了 sigreturn() 系統調用。修改用戶棧的代碼在 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.  ... 

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

signal-user-stack

這樣執行完信號處理程序后就會調用 sigreturn(),而 sigreturn() 要做的工作就是恢復原來內核棧的內容了,我們來看看 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; 

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

這樣,當從 sigreturn() 系統調用返回時,就可以按原來的路徑返回到用戶程序的下一個執行點(比如調用系統調用的下一行代碼)。

設置信號處理程序

最后我們來分析一下怎么設置一個信號處理程序。

用戶可以通過 signal() 系統調用設置一個信號處理程序,我們來看看 signal() 系統調用的代碼:

  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; 

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

  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; 

這個函數也不難,我們上面介紹過,進程管理結構中有個 sig 的字段,它是一個 struct k_sigaction 結構的數組,每個元素保存著對應信號的處理程序,所以 do_sigaction() 函數就是修改這個信號處理程序。代碼 k = ¤t->sig->action[sig-1] 就是獲取對應信號的處理程序,然后把其設置為新的信號處理程序即可。

 

責任編輯:武曉燕 來源: Linux內核那些事
相關推薦

2023-12-18 10:45:31

2019-07-01 09:22:15

Linux操作系統硬件

2020-03-31 14:40:24

HashMap源碼Java

2017-04-17 13:10:09

神經網絡人工智能網絡

2024-10-10 17:55:57

LinuxACL訪問控制列表

2025-07-11 01:45:00

SIM卡模塊識別

2021-06-06 13:06:34

JVM內存分布

2016-08-18 00:21:12

網絡爬蟲抓取網絡

2024-08-12 12:30:27

2024-03-13 08:34:22

2023-07-07 11:36:29

人工智能基礎模型

2025-01-09 11:14:14

2021-08-02 06:56:19

TypeScript編程語言編譯器

2025-01-20 09:15:00

iOS 18.3蘋果iOS 18

2025-06-09 04:00:00

2019-05-22 09:50:42

Python沙箱逃逸網絡攻擊

2023-12-15 15:55:24

Linux線程同步

2021-10-06 20:23:08

Linux共享內存

2021-11-02 10:53:56

Linux機制CPU

2019-02-26 15:20:31

CPU開蓋器結構
點贊
收藏

51CTO技術棧公眾號

国产熟女一区二区丰满| 最近更新的2019中文字幕| 7m第一福利500精品视频| 性久久久久久久久久久久久久| 国产区av在线| 国产精品任我爽爆在线播放| 一区二区视频在线| 久久国产精品久久精品国产| 国产精品熟女视频| 99精品视频在线观看免费播放| 亚洲国产精品综合小说图片区| 久久riav二区三区| 国产精品国产av| 在线视频亚洲| 伊伊综合在线| 天堂久久久久va久久久久| 日韩视频一区在线观看| 少妇大叫太大太粗太爽了a片小说| 国产精品久久免费观看| 日韩欧美三区| 欧美日韩亚洲系列| 成人在线免费观看视视频| 欧美美女性生活视频| 日韩精品视频中文字幕| 欧洲一区在线电影| 日本少妇高潮喷水视频| 亚洲精品喷潮一区二区三区| 免费一级欧美片在线播放| 亚洲美女黄色片| 在线观看av网页| 蜜桃视频在线观看免费视频| 一区二区在线观看免费视频播放| 亚洲精品永久www嫩草| 天堂资源最新在线| 99热在线精品观看| 久久久久久九九九| 国产极品国产极品| 四虎成人av| 国产一区二区三区欧美| 醉酒壮男gay强迫野外xx| 亚洲天堂中文字幕在线观看| 欧美精品123区| 日日干夜夜操s8| 最新日韩一区| 欧美综合久久久| 人人干人人视频| 黑人巨大精品| 日韩欧美视频一区二区三区| 北条麻妃在线视频观看| 美女在线视频免费| 欧美性猛xxx| 欧美xxxxx在线视频| 校园春色亚洲| 日韩欧美主播在线| 欧美牲交a欧美牲交aⅴ免费下载| 性感女国产在线| 日韩欧美中文第一页| 啊啊啊一区二区| 男人久久天堂| 色妞www精品视频| 精品www久久久久奶水| 校园春色亚洲| 在线免费精品视频| www.99在线| 久久天堂影院| 欧美一区二区三区爱爱| 18深夜在线观看免费视频| 亚洲午夜免费| 亚洲韩国青草视频| 不卡一区二区在线观看| 精品国产乱码| 日韩视频中文字幕| 一区二区成人免费视频| 亚洲国产高清视频| 青青草国产精品一区二区| 亚洲无码精品一区二区三区| 麻豆精品国产91久久久久久| 国产综合香蕉五月婷在线| 99国产揄拍国产精品| 成人av网在线| 色之综合天天综合色天天棕色| 一级毛片视频在线| 亚洲激情男女视频| 久久精品国产亚洲一区二区| 亚洲特黄一级片| 青青青爽久久午夜综合久久午夜| 日本大胆欧美| 午夜激情一区二区| 免费观看成人网| 日韩有码欧美| 精品国产a毛片| 国产精品成久久久久三级| 五月激情四射婷婷| 欧美一区二区三区另类| 91成人在线观看国产| 一级久久久久久| 国产成人亚洲综合a∨猫咪| 韩日午夜在线资源一区二区| 亚洲精品一区二区口爆| 99免费精品视频| 亚洲一区三区电影在线观看| 国产探花视频在线观看| 日本精品视频一区二区三区| 18深夜在线观看免费视频| 免费视频亚洲| 欧美日韩国产成人在线| 懂色av蜜臀av粉嫩av分享吧最新章节| 精品一区二区三区免费观看| 久久精品国产精品国产精品污| 午夜不卡视频| 欧美色视频日本版| 日韩欧美中文在线视频| 精品国产一区二区三区av片| 欧美极品少妇xxxxx| 在线播放国产一区| 91麻豆精东视频| 毛片av在线播放| 欧美激情三区| 亚洲欧美中文日韩v在线观看| 欧美成欧美va| 免费成人在线观看| 精品网站在线看| 伊人影院蕉久影院在线播放| 欧美在线观看视频在线| 制服丝袜第一页在线观看| 国产电影一区二区在线观看| 国产成人精品电影久久久| 欧洲av在线播放| 亚洲日本在线a| 在线观看免费黄网站| 亚洲影院天堂中文av色| 国产日本亚洲高清| 久久麻豆精品| 精品黑人一区二区三区久久| 日韩毛片无码永久免费看| 一区二区久久| 欧美三级在线看| 日韩视频在线观看一区二区三区| 国产一区二区三区电影在线观看| 亚州成人av在线| 丰满人妻一区二区三区免费视频| 亚洲美女在线一区| 日韩视频在线观看一区二区三区| 婷婷精品进入| 成人在线一区二区| 国内外激情在线| 这里只有精品免费| 永久免费看片直接| 黄色日韩网站视频| 四虎免费在线观看视频| gogo大尺度成人免费视频| www国产精品视频| 91麻豆成人精品国产| 亚洲欧美综合在线精品| 亚洲成人福利在线| 91精品秘密在线观看| 92国产精品视频| 免费大片黄在线| 日韩一区二区高清| 精品无码久久久久久久久| 从欧美一区二区三区| 欧美精品自拍视频| 一区二区美女| 国产欧美日韩最新| 97超碰资源站在线观看| 精品日韩一区二区三区免费视频| 久久免费视频99| 99r精品视频| 男女无套免费视频网站动漫| 天天做天天爱天天爽综合网| 亚洲最大av网站| 欧美家庭影院| 日韩精品在线影院| 亚洲一级片免费看| 亚洲综合av网| 美女久久久久久久久久| 男人的天堂亚洲一区| 黄色网络在线观看| 欧美调教视频| 国产精品私拍pans大尺度在线| 飘雪影视在线观看免费观看 | 日本三级网站在线观看| 97久久久精品综合88久久| 久久午夜夜伦鲁鲁一区二区| 亚洲欧洲日韩| 久久精品人成| 久久亚洲资源中文字| 欧美精品久久久久久久免费观看| 国产美女三级无套内谢| 亚洲成人av一区| 精品一区二区三孕妇视频| 国产精品66部| 91看片就是不一样| 女人天堂亚洲aⅴ在线观看| 开心色怡人综合网站| **精品中文字幕一区二区三区| 久久全国免费视频| 91社区在线观看播放| 亚洲精品一区二区三区精华液| 看黄色一级大片| 亚洲午夜在线观看视频在线| 国产综合精品久久久久成人av| 懂色av中文一区二区三区| 污污动漫在线观看| 99综合在线| 九色视频成人porny| 亚洲一区有码| 国产成人精品一区| 欧美精品videossex少妇| 三级精品视频久久久久| 亚洲人妻一区二区三区| 日韩一级精品视频在线观看| 波多野结衣一本一道| 性久久久久久久久| 外国一级黄色片| 国产精品久久久久久久久久免费看| 国产精品九九视频| 伊人青青综合网| www.99精品| 国产精品国产三级国产aⅴ原创| 欧美性生交大片免费| 日韩精品一区二区在线| 91久久偷偷做嫩草影院| 最爽无遮挡行房视频在线| 亚洲天堂av综合网| 熟妇高潮一区二区三区| 日韩女优电影在线观看| 91成人国产综合久久精品| 色诱视频网站一区| 中日韩黄色大片| 亚洲一区精品在线| 国产67194| 国产精品黄色在线观看| 亚洲精品国产91| 91丨九色丨蝌蚪富婆spa| 中文字幕人妻熟女在线| 国产xxx精品视频大全| 欧美日韩理论片| 午夜久久av| 欧美激情视频在线观看| 亚洲av成人精品毛片| 亚洲福利视频专区| 亚洲乱色熟女一区二区三区| 日韩三级视频在线看| a天堂中文在线观看| 无人区乱码一区二区三区| 欧美一级一区二区| 96亚洲精品久久久蜜桃| 欧美私模裸体表演在线观看| 日韩精品成人在线| 亚洲欧美色一区| 在线观看av日韩| 欧美 日韩 国产一区二区在线视频| 在线不卡日本| 一区二区中文| 国产青草视频在线观看| 精品成人免费| 女性女同性aⅴ免费观女性恋| 国产欧美激情| 精品一区二区成人免费视频 | 品久久久久久久久久96高清| 亚洲妇女av| 狠狠色综合色区| 九一成人免费视频| 视频一区免费观看| 国产精品久久久久久妇女| 国产精品黄色av| 99视频有精品高清视频| 中文字幕日韩在线视频| 人妻精品一区二区三区| 精品无人区太爽高潮在线播放| 男人的天堂在线免费视频| 一区二区av在线| 欧美国产视频一区| 日韩人体视频| 国产精品人成电影在线观看| 国产成人免费9x9x人网站视频| 久久综合色影院| 国产黄色大片在线观看| 日韩电影免费在线观看中文字幕| 免费在线国产| 亚洲大胆人体av| 黑人精品一区二区三区| 亚洲第一区中文字幕| 国产露出视频在线观看| 日韩经典第一页| 黄色美女网站在线观看| 欧美日本免费一区二区三区| 午夜精品无码一区二区三区| 亚洲精品视频播放| 福利片在线观看| 欧美国产第二页| 日韩高清在线| 岛国视频一区| 欧美色图一区| 日韩黄色在线免费观看| 少妇献身老头系列| 狠狠色狠狠色综合系列| 中文字幕人妻一区二区三区| 91美女片黄在线观看91美女| 日本美女黄色一级片| 午夜精品久久久久久久| 亚洲天堂狠狠干| 欧美精品一区二区三区四区| 手机看片福利永久| 美日韩精品视频免费看| 国模一区二区| 国产精品18毛片一区二区| 欧美一性一交| 男人日女人的bb| 亚洲一区视频| 久久久久中文字幕亚洲精品| 国产精品丝袜一区| 国产美女久久久久久| 色94色欧美sute亚洲13| 免费黄色小视频在线观看| 欧美巨大另类极品videosbest| 三级做a全过程在线观看| 亚洲精品日韩欧美| 91福利在线免费| 奇米一区二区三区四区久久| 成人av动漫| 日韩视频 中文字幕| 精品一区二区三区免费毛片爱| av永久免费观看| 91麻豆精品一区二区三区| 日本在线精品视频| 欧美xxxx做受欧美护士| 国产区日韩欧美| 区一区二视频| 日本黄网站免费| 久久亚洲不卡| 波多野结衣福利| 午夜精品久久久久久久蜜桃app| 中文字幕乱码人妻无码久久| 亚洲毛片在线看| 香蕉视频亚洲一级| 久久久久久欧美精品色一二三四| 亚洲精品二区三区| 久久久精品麻豆| 国产视频一区二区在线| 国产一区二区三区高清视频| www.中文字幕在线| 天堂中文字幕在线观看| 不卡一区综合视频| 91精品福利在线一区二区三区| 亚洲天堂网在线观看视频| 成人免费高清在线| 中文在线观看免费网站| 欧美一区二区三区视频在线| 午夜伦全在线观看| 成人福利网站在线观看11| 天天综合一区| 国偷自产av一区二区三区麻豆| 一区二区三区四区精品在线视频| 精品国产九九九| 高清亚洲成在人网站天堂| 精品三级在线观看视频| 欧美日韩国产精品激情在线播放| 91浏览器在线视频| 中文字幕一区二区三区四区欧美| 午夜在线成人av| 白丝女仆被免费网站| 欧美日韩在线一区| 狠狠色伊人亚洲综合网站l| 国产成人亚洲综合青青| 日韩大片在线| www.51色.com| 亚洲小说欧美激情另类| 午夜福利理论片在线观看| 国产97在线亚洲| 国产电影一区二区在线观看| 四虎国产精品免费| 天天综合天天综合色| 韩国三级av在线免费观看| 国产一区在线播放| 欧美日韩亚洲一区| 亚洲午夜久久久久久久久红桃| 欧美午夜宅男影院| 成人短视频在线| 久久久国产精品一区二区三区| 日本一不卡视频| 欧美另类视频在线观看| 亚洲男人天堂2019| 国产一区二区三区亚洲综合| 人妻av中文系列| 国产精品日产欧美久久久久| 亚洲国产精品视频在线| 日韩av片免费在线观看| 久久久久久久久久久妇女| 亚洲av永久无码精品| 欧美视频一区二区三区四区| 久草成色在线| 色婷婷精品国产一区二区三区| 国产精品资源在线看| 国产伦精品一区二区三区视频网站| 久久九九精品99国产精品| 一区三区在线欧| 337p日本欧洲亚洲大胆张筱雨| 欧洲另类一二三四区|