萬盛學電腦網

 萬盛學電腦網 >> 服務器教程 >> Linux內核中的信號機制的介紹

Linux內核中的信號機制的介紹

   應用程序發送信號時,主要通過kill進行。注意:不要被“kill”迷惑,它並不是發送SIGKILL信號專用函數。這個函數主要通過系統調用sys_kill()進入內核,它接收兩個參數:

  第一個參數為目標進程id,kill()可以向進程(或進程組),線程(輕權線程)發送信號,因此pid有以下幾種情況:

  ● pid>0:目標進程(可能是輕權進程)由pid指定。

  ● pid=0:信號被發送到當前進程組中的每一個進程。

  ● pid=-1:信號被發送到任何一個進程,init進程(PID=1)和以及當前進程無法發送信號的進程除外。

  ● pid<-1:信號被發送到目標進程組,其id由參數中的pid的絕對值指定。

  第二個參數為需要發送的信號。

  由於sys_kill處理的情況比較多,分析起來比較復雜,我們從太累了入手,這個函數把信號發送到由參數指定pid指定的線程(輕權進程)中。tkill的內核入口是sys_tkill(kernel/signal.c),其定義如下:

  /*

  * Send a signal to only one task, even if it's a CLONE_THREAD task.

  */

  asmlinkage long

  sys_tkill(int pid, int sig)

  {

  struct siginfo info;

  int error;

  struct task_struct *p;

  /* This is only valid for single tasks */

  if (pid <= 0)//對參數pid進行檢查

  return -EINVAL;

  info.si_signo = sig; //根據參數初始化一個siginfo結構

  info.si_errno = 0;

  info.si_code = SI_TKILL;

  info.si_pid = current->tgid;

  info.si_uid = current->uid;

  read_lock(&tasklist_lock);

  p = find_task_by_pid(pid);//獲取由pid指定的線程的task_struct結構

  error = -ESRCH;

  if (p) {

  error = check_kill_permission(sig, &info, p);//權限檢查

  /*

  * The null signal is a permissions and process existence

  * probe. No signal is actually delivered.

  */

  if (!error && sig && p->sighand) {

  spin_lock_irq(&p->sighand->siglock);

  handle_stop_signal(sig, p);

  //對某些特殊信號進程處理,例如當收到SIGSTOP時,需要把信號隊列中的SIGCONT全部刪除

  error = specific_send_sig_info(sig, &info, p);//把信號加入到信號隊列

  spin_unlock_irq(&p->sighand->siglock);

  }

  }

  read_unlock(&tasklist_lock);

  return error;

  }

  sys_tkill函數主要是通過pecific_send_sig_info()函數實現的,下面我們看一下pecific_send_sig_info()(kernel/signal.c)的定義:

  static int

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

  {

  int ret = 0;

  if (!irqs_disabled())

  BUG();

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

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

  /*

  * Set up a return to indicate that we dropped the signal.

  */

  ret = info->si_sys_private;

  /*信號被忽略*/

  /* Short-circuit ignored signals. */

  if (sig_ignored(t, sig))

  goto out;

  /* Support queueing exactly one non-rt signal, so that we

  can get more detailed information about the cause of

  the signal. */

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

  goto out;

  ret = send_signal(sig, info, t, &t->pending);//實際的發送工作

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

  signal_wake_up(t, sig == SIGKILL);

  out:

  return ret;

  }

  首先調用sig_ignored檢查信號是否被忽略,然後檢查發送的信號是不是普通信號,如果是普通信號,就需要根據信號位圖來檢查當前信號隊列中是否已經存在該信號,如果已經存在,對於普通信號不需要做任何處理。然後調用send_signal來完成實際的發送工作,send_signal()是信號發送的重點,除sys_tkill之外的函數,最終都是通過send_signal()來完成信號的發送工作的。

  這裡注意到想send_signal()傳遞的參數時t->pending,也就是連接Private Signal Queue的那條鏈。最後,如果發送成功就調用signal_wake_up()來喚醒目標進程,這樣可以保證該進程進入就緒狀態,從而有機會被調度執行信號處理函數。

  現在我們來看看send_signal()(kernel/signal.c)函數,這個函數的主要工作就是分配並初始化一個sigqueue結構,然後把它添加到信號隊列中。

  static int send_signal(int sig, struct siginfo *info, struct task_struct *t,

  struct sigpending *signals)

  {

  struct sigqueue * q = NULL;

  int ret = 0;

  /*

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

  * or SIGKILL.

  */

  if ((unsigned long)info == 2)

  goto out_set;

  /* Real-time signals must be queued if sent by sigqueue, or

  some other real-time mechanism. It is implementation

  defined whether kill() does so. We attempt to do so, on

  the principle of least surprise, but since kill is not

  allowed to fail with EAGAIN when low on memory we just

  make sure at least one signal gets delivered and don't

  pass on the info struct. */

  q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN &&

  ((unsigned long) info < 2 ||

  info->si_code >= 0)));//分配sigqueue結構

  if (q) {//如果成功分配到sigqueue結構,就把它添加到隊列中,並對其初始化

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

  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);//拷貝sigqueue結構

  break;

  }

  } else {

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

  && info->si_code != SI_USER)

  /*

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

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

  */

  return -EAGAIN;

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

  /*

  * Set up a return to indicate that we dropped

  * the signal.

  */

  ret = info->si_sys_private;

  }

  out_set:

  sigaddset(&signals->signal, sig);//設置信號位圖

  return ret;

  }

  從上面的分析可以看出,我們看到信號被添加到信號隊列之後,會調用signal_wake_up()喚醒這個進程,signal_wake_up()(kernel/signal.c)的定義如下:

  /*

  * Tell a process that it has a new active signal..

  *

  * NOTE! we rely on the previous spin_lock to

  * lock interrupts for us! We can only be called with

  * "siglock" held, and the local interrupt must

  * have been disabled when that got acquired!

  *

  * No need to set need_resched since signal event passing

  * goes through ->blocked

  */

  void signal_wake_up(struct task_struct *t, int resume)

  {

  unsigned int mask;

  set_tsk_thread_flag(t, TIF_SIGPENDING);//為進程設置TIF_SIGPENDING標志

  /*

  * For SIGKILL, we want to wake it up in the stopped/traced case.

  * We don't check t->state here because there is a race with it

  * executing another processor and just now entering stopped stat

copyright © 萬盛學電腦網 all rights reserved