精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

精品高清在线| 亚洲性受xxx喷奶水| 国产精品123| 亚洲91av视频| 天天干天天舔天天操| 电影91久久久| 欧美日韩免费在线| 色99中文字幕| 亚洲免费国产视频| 日本伊人色综合网| 欧美极品美女视频网站在线观看免费 | 91国语精品自产拍在线观看性色| 五月天综合视频| 日韩精品亚洲专区在线观看| 爱久久·www| 综合亚洲自拍| 制服丝袜亚洲播放| 日韩黄色片视频| 91精品久久| 国产欧美日韩中文久久| 国产精品乱子乱xxxx| 一级黄色a毛片| 亚洲一区二区动漫| 欧美人与性动交| 国产又粗又长又黄的视频| 久久国产精品免费精品3p| 欧美福利电影网| 国产一区亚洲二区三区| 免费毛片在线看片免费丝瓜视频| 欧美激情在线一区二区| 国产一区二区久久久| 国产又粗又猛又爽又黄视频| 免费国产自线拍一欧美视频| 色综合91久久精品中文字幕| 成人信息集中地| 少妇精品久久久一区二区| 亚洲第一男人天堂| 中文字幕第10页| 999色成人| 欧美男生操女生| 黄色片在线免费| 裤袜国产欧美精品一区| 天天影视网天天综合色在线播放| 日本一二三区视频在线| 求av网址在线观看| 国产精品色在线观看| 日本精品一区二区| 日本天堂在线| 久久色在线观看| 久久亚洲精品欧美| 婷婷色在线视频| av中文字幕在线不卡| 国产高清在线精品一区二区三区| a级片免费视频| 国产激情精品久久久第一区二区| 91麻豆国产精品| 国产精品系列视频| 国内精品伊人久久久久av影院| 国产日韩精品综合网站| 亚洲综合网av| 国产精品原创巨作av| 91日本在线观看| 国产高潮在线观看| 国产成人精品亚洲日本在线桃色| 99九九视频| 国产小视频一区| 成人成人成人在线视频| 久久99精品久久久久久久久久| 天堂中文在线官网| www亚洲一区| 日韩一区二区电影在线观看| jizz在线免费观看| 亚洲日韩欧美一区二区在线| 青青视频免费在线| 国内在线视频| 日韩欧美大尺度| 成年网站在线播放| 国产高清日韩| 亚洲国产精品电影| 国产成人av一区二区三区不卡| 精品一区二区三区的国产在线观看| 丝袜美腿亚洲一区二区| 欧美日韩三级在线观看| 亚洲大片av| 国产999在线| 国产精品高潮呻吟av| 国产成+人+日韩+欧美+亚洲| 久久99精品国产99久久| aiai在线| 亚洲午夜电影网| 亚洲成熟丰满熟妇高潮xxxxx| 久久精品 人人爱| 精品毛片乱码1区2区3区| www.色多多| 国产精品黑丝在线播放| 久久久久国产精品免费| 无码人妻精品一区二区三区不卡| 久久91精品国产91久久小草| 国产免费一区二区三区| 阿v免费在线观看| 亚洲影视在线观看| 黄色免费网址大全| 亚洲精选av| 国产一区二区三区在线观看网站| 久久99久久99精品免费看小说| 尹人成人综合网| 国产精品久久久久久久久久东京 | 国产v亚洲v天堂无码| 可以在线观看的黄色| 亚洲欧美区自拍先锋| 欧美激情成人网| 国产精品chinese在线观看| 最近的2019中文字幕免费一页| 国产大片中文字幕| 美女在线观看视频一区二区| 国产一区二区三区av在线| 日本中文字幕在线播放| 精品美女永久免费视频| 熟妇无码乱子成人精品| 欧美精品色图| 青青草一区二区| 亚洲精品久久久久久久久久久久久久| 国产女人18水真多18精品一级做| 可以看毛片的网址| 精品视频一二| 久久精品中文字幕| 中国a一片一级一片| 久久综合九色综合欧美亚洲| 久久国产午夜精品理论片最新版本| 欧美成人aaa| 亚洲天堂网在线观看| 日本在线观看中文字幕| 国产一区二区三区综合| 亚洲欧洲国产日韩精品| 日日夜夜天天综合| 精品视频偷偷看在线观看| 日本三级片在线观看| 国产精品18久久久久久vr| 中文字幕精品—区二区日日骚| 粉嫩一区二区三区| 亚洲色图综合久久| 国产精品一区二区三区四| 成人小视频在线观看| 免费看日b视频| 久久国产精品美女| 美女少妇精品视频| 国产普通话bbwbbwbbw| 亚洲日穴在线视频| 日本一二三区在线| 欧美破处大片在线视频| 91在线在线观看| 免费毛片在线看片免费丝瓜视频 | 国产精品一区二区三区久久久| 久蕉依人在线视频| 日本丰满少妇一区二区三区| 欧洲美一区二区三区亚洲| 日韩精品一二三区| 天堂一区二区三区| 亚洲ww精品| 欧美另类高清videos| 亚洲国产福利视频| 亚洲成年人影院| 99久久久久久久久久| 久久亚洲影院| 一本久道久久综合| 91成人app| 欧美激情按摩在线| 亚洲 欧美 激情 小说 另类| 色婷婷av一区二区三区gif| 色欲AV无码精品一区二区久久| 热久久久久久久| 国产高潮呻吟久久久| 成午夜精品一区二区三区软件| 国内精品中文字幕| 激情小说 在线视频| 欧美日韩一区二区三区免费看| 男人在线观看视频| 成人国产免费视频| 少妇性l交大片| 婷婷激情图片久久| 国产精品一区二区免费看| 亚洲精品福利电影| xvideos国产精品| 精品人妻无码一区二区色欲产成人| 亚洲国产精品久久人人爱 | 亚洲欧美tv| 国产美女被下药99| av电影院在线看| 在线观看视频99| 亚洲国产精品欧美久久| 欧美视频在线观看免费| www.黄色在线| 成人在线视频首页| 久久婷婷综合色| 伊人久久婷婷| 一区二区三区四区国产| 成人爽a毛片免费啪啪红桃视频| 国产精品久久久久久av福利| 性国产高清在线观看| 亚洲精品视频免费| 国产极品999| 在线观看免费视频综合| 久久免费小视频| 国产精品人人做人人爽人人添| 极品白嫩的小少妇| 精品一区二区在线播放| 大陆极品少妇内射aaaaa| 欧美高清视频手机在在线| 精品久久久久久综合日本| 日韩一区二区三区四区五区| 欧美一区二区三区图| 操你啦在线视频| 国产一区二区av| 欧美自拍偷拍一区二区| 在线播放91灌醉迷j高跟美女| 久久亚洲精品国产| 亚洲一区在线观看网站| 秋霞欧美一区二区三区视频免费| 94色蜜桃网一区二区三区| 亚洲综合123| 日本vs亚洲vs韩国一区三区| 欧美a v在线播放| 欧美激情四色| 国产精品av免费| 日韩伦理视频| 日韩精品无码一区二区三区| 国产精品香蕉| 粉嫩av一区二区三区免费观看 | 国产18无套直看片| 91一区二区在线观看| 9.1在线观看免费| 国产电影精品久久禁18| 亚洲欧美日韩三级| 喷白浆一区二区| 国产自偷自偷免费一区| 麻豆亚洲精品| 国产免费毛卡片| 99在线|亚洲一区二区| 2019日韩中文字幕mv| 欧美激情 亚洲a∨综合| 91制片厂免费观看| 羞羞色午夜精品一区二区三区| 亚洲精品二区| 日韩欧美网站| 亚洲高清视频一区| 成人在线免费小视频| 日日噜噜噜噜夜夜爽亚洲精品| 欧美天堂影院| 久久精品中文字幕一区二区三区| 国产一区二区三区亚洲| 国产午夜精品在线| 噜噜噜天天躁狠狠躁夜夜精品| 国产视频精品网| 亚洲区小说区图片区qvod| 久久久久久久久久久久久久一区| 日韩电影在线观看完整免费观看| 国内外成人免费视频| 卡通动漫精品一区二区三区| 久久精品国产综合精品| 欧美日韩xxxx| 一区二区不卡在线观看| 婷婷综合伊人| 男人天堂手机在线视频| 99在线精品免费视频九九视| 波多野结衣家庭教师在线| 久久综合九色综合欧美狠狠| 熟女人妇 成熟妇女系列视频| 蜜臀久久久99精品久久久久久| www.com久久久| 国产传媒欧美日韩成人| 久久人妻少妇嫩草av无码专区 | 极品少妇一区二区三区精品视频 | 欧洲精品一区二区三区久久| 一本久道久久久| 成人免费无码av| 国产美女在线精品| 91黄色免费视频| 国产欧美一区二区精品仙草咪| 日韩一区二区三区四区视频| 亚洲欧美激情小说另类| 日本在线视频免费观看| 在线免费av一区| jlzzjlzz亚洲女人18| 亚洲国产黄色片| 北条麻妃在线| 欧美精品激情在线| 亚洲www免费| 91在线看www| 天堂综合网久久| 在线看成人av电影| 日韩亚洲国产精品| 欧美伦理片在线观看| 国产suv一区二区三区88区| 久久精品一区二区免费播放 | 日本成人福利| 成人欧美一区二区| 久久最新网址| 丰满人妻一区二区三区53号| 免费亚洲一区| 国产精品一级无码| 久久精品人人做人人爽97| 欧美成人国产精品高潮| 在线视频欧美区| 懂色av一区二区三区四区| 永久免费毛片在线播放不卡| 爱情岛亚洲播放路线| 国产日韩欧美电影在线观看| 欧美成人基地| 无码人妻aⅴ一区二区三区日本| 麻豆91精品| 一级黄色片毛片| 亚洲精品美国一| 亚洲综合精品在线| 亚洲男人天堂手机在线| 日韩伦理av| 成人乱色短篇合集| 奇米色欧美一区二区三区| av网站手机在线观看| 国产在线精品免费av| 免费福利视频网站| 欧美日韩一区二区在线| 亚洲高清视频在线播放| 久久精品视频导航| 色综合天天色| 免费成人深夜夜行视频| 亚洲高清不卡| 亚洲成人福利视频| 亚洲视频综合在线| 一级aaaa毛片| 日韩一区二区在线视频| 国产成人福利夜色影视| 欧美日韩精品久久| 国产精品主播| 中国一级特黄录像播放| 亚洲一级在线观看| 精品人妻一区二区三区四区不卡| 久久精品人人做人人爽| 99精品国产九九国产精品| 一区二区三区欧美在线| 精品一区二区三区在线观看| 国产99在线 | 亚洲| 欧美制服丝袜第一页| 黄色国产在线| 国产精品视频一区二区三区四| 女厕嘘嘘一区二区在线播放| 黄www在线观看| 久久久www成人免费毛片麻豆| 日本午夜视频在线观看| 亚洲精品午夜精品| 希岛爱理一区二区三区av高清| 欧美日韩电影一区二区三区| 国产精品视频| 国产成人av一区二区三区不卡| 91久久精品一区二区三| 成人在线观看网站| 国产精品揄拍一区二区| 天天天综合网| 亚洲av无一区二区三区久久| 亚洲一区免费视频| 少妇高潮一区二区三区69| 2019中文在线观看| 国产欧美高清视频在线| 在线观看免费污视频| 日韩美女啊v在线免费观看| 国产哺乳奶水91在线播放| 欧美第一页在线| 日韩有码一区| 无码少妇一区二区三区芒果| 亚洲欧洲精品天堂一级| 亚洲av无码国产精品永久一区| 97在线日本国产| 精品美女久久久| 能看毛片的网站| 午夜激情久久久| av电影在线观看| 风间由美久久久| 日本视频在线一区| 欧洲第一无人区观看| 亚洲大尺度美女在线| 日日av拍夜夜添久久免费| 椎名由奈jux491在线播放| 成+人+亚洲+综合天堂| 不卡av电影在线| 久久久精品免费| 乱亲女h秽乱长久久久| 邪恶网站在线观看| 亚洲国产美女搞黄色| 国产小视频在线| 99国产在线视频| 青青青爽久久午夜综合久久午夜| 国产女人被狂躁到高潮小说| 亚洲欧美日韩天堂| 麻豆国产一区二区三区四区| 六月丁香婷婷在线| 一区二区在线免费观看| 国产精品一级伦理| 国产另类自拍| 国产一区二区视频在线| 自拍偷拍18p| 国产69精品久久久久99|