歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> linux進程管理之信號處理(1)

linux進程管理之信號處理(1)

日期:2017/3/3 16:40:56   编辑:關於Linux

信號是操作系統中一種很重要的通信方式.近幾個版本中,信號處理這部份很少有大的變動.我們從用戶空間的信號應用來分析Linux內核的信號實現方式.

一:信號有關的數據結構

在task_struct中有關的信號結構:

struct task_struct {
……
//指向進程信號描述符
  struct signal_struct *signal;
   //指向信號的處理描述符
   struct sighand_struct *sighand;
 
   //阻塞信號的掩碼
   sigset_t blocked, real_blocked;
   //保存的信號掩碼.當定義TIF_RESTORE_SIGMASK的時候,恢復信號掩碼
   sigset_t saved_sigmask;   /* To be restored with TIF_RESTORE_SIGMASK */
   //存放掛起的信號
   struct sigpending pending;
  
   //指定信號處理程序的棧地址
   unsigned long sas_ss_sp;
   //信號處理程序的棧大小
   size_t sas_ss_size;
   //反映向一個函數的指針,設備驅動用此來阻塞進程的某些信號
   int (*notifier)(void *priv);
   //notifier()的參數
   void *notifier_data;
   //驅動程序通過notifier()所阻塞信號的位圖
   sigset_t *notifier_mask;
   ……
}

Sigset_t的數據結構如下:

//信號位圖.
typedef struct {
     //在x86中需要64位掩碼,即2元素的32位數組
   unsigned long sig[_NSIG_WORDS];
} sigset_t;
#define _NSIG   64
 
#ifdef __i386__
# define _NSIG_BPW 32
#else
# define _NSIG_BPW 64
#endif
 
#define _NSIG_WORDS  (_NSIG / _NSIG_BPW)

在linux中共有64個信號.前32個為常規信號.後32個為實時信號.實時信號與常規信號的唯一區別就是實時信號會排隊等候.

struct sigpending結構如下:

//信號等待隊列
struct sigpending {
   struct list_head list;
   //如果某信號在等待,則該信號表示的位置1
   sigset_t signal;
};

Struct sighand_struct的結構如下:

struct sighand_struct {
//引用計數
atomic_t   count;
//信號向量表
struct k_sigaction action[_NSIG];
spinlock_t     siglock;
wait_queue_head_t signalfd_wqh;
}

同中斷處理一樣,每一個信號都對應action中的一個處理函數.

struct k_sigaction結構如下示:

struct sigaction {
   //信號處理函數
__sighandler_t sa_handler;
   //指定的信號處理標志
unsigned long sa_flags;
__sigrestore_t sa_restorer;
   //在運行處理信號的時候要屏弊的信號
sigset_t sa_mask;   /* mask last for extensibility */
};

Struct signal_struct結構如下:

struct signal_struct {
 
//共享計數
atomic_t   count;
//線程組內存活的信號
atomic_t   live;
 
//wait_chldexit:子進程的等待隊列
wait_queue_head_t wait_chldexit;   /* for wait4() */
 
/* current thread group signal load-balancing target: */
//線程組內最使收到信號的進程
struct task_struct *curr_target;
 
/* shared signal handling: */
//共享信號的等待隊列
struct sigpending shared_pending;
 
/* thread group exit support */
//線程組的終止碼
int      group_exit_code;
/* overloaded:
* - notify group_exit_task when ->count is equal to notify_count
* - everyone except group_exit_task is stopped during signal delivery
*  of fatal signals, group_exit_task processes the signal.
*/
 
//當kill 掉整個線程組的時候使用
struct task_struct *group_exit_task;
//當kill 掉整個線程組的時候使用
int      notify_count;
 
/* thread group stop support, overloads group_exit_code too */
//當整個線程組停止的時候使用  
int      group_stop_count;
unsigned int    flags; /* see SIGNAL_* flags below */
……
}

上述所討論的數據結構可以用下圖表示(摘自<<Understanding.the.Linux.Kernel>>):

二:更改信號的處理函數

在用戶空間編程的時候,我們常用的注冊信號處理函數的API有:

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

兩者都可以更改信號.sigaction是Unix後期才出現的接口.這個接口較signal()更為健壯也更為強大:

Signal()只能為指定的信號設置信號處理函數.而sigaction()不僅可以設置信號處理函數,還可以設置進程的信號掩碼.返回設置之前的sigaction結構.sigaction結構在上面已經分析過了.

這兩個用戶空間的接口對應的系統調用為別是:

sys_signal(int sig, __sighandler_t handler)

sys_sigaction(int sig, const struct old_sigaction __user *act, struct old_sigaction __user *oact)

我們來分析一下內核是怎麼樣處理的.sys_signal()代碼如下:

asmlinkage unsigned long
sys_signal(int sig, __sighandler_t handler)
{
   struct k_sigaction new_sa, old_sa;
   int ret;
 
   new_sa.sa.sa_handler = handler;
 
   //SA_ONESHOT:使用了函數指針之後,將其處理函數設為SIG_DEF
   //SA_NOMASK: 在執行信號處理的時候,不執行任何信號屏弊
  
   new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;
   //清除信號掩碼.表示在處理該信號的時候不要屏弊任何信號
   sigemptyset(&new_sa.sa.sa_mask);
 
   ret = do_sigaction(sig, &new_sa, &old_sa);
 
   //如果調用錯誤,返回錯誤碼.如果成功,返回之前的處理函數
   return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
}

sys_sigaction()的代碼如下:

asmlinkage int
sys_sigaction(int sig, const struct old_sigaction __user *act,
      struct old_sigaction __user *oact)
{
   struct k_sigaction new_ka, old_ka;
   int ret;
 
   //將用戶空間的sigaction 拷貝到內核空間
   if (act) {
     old_sigset_t mask;
     if (!access_ok(VERIFY_READ, act, sizeof(*act)) ||
       __get_user(new_ka.sa.sa_handler, &act->sa_handler) ||
       __get_user(new_ka.sa.sa_restorer, &act->sa_restorer))
       return -EFAULT;
     __get_user(new_ka.sa.sa_flags, &act->sa_flags);
     __get_user(mask, &act->sa_mask);
     siginitset(&new_ka.sa.sa_mask, mask);
   }
 
   ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL);
 
   //出錯,返回錯誤代碼.否則返回信號的sigaction結構
   if (!ret && oact) {
     if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) ||
       __put_user(old_ka.sa.sa_handler, &oact->sa_handler) ||
       __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer))
       return -EFAULT;
     __put_user(old_ka.sa.sa_flags, &oact->sa_flags);
     __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask);
   }
 
   return ret;
}

由此可以看出,兩個函數最終都會調用do_sigaction()進行處理.該函數代碼如下:

int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
{
   struct k_sigaction *k;
   sigset_t mask;
 
   //sig_kernel_only:判斷sig是否為SIGKILL SIGSTOP
 
   //不能為KILL, STOP信號重設處理函數
   if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))
     return -EINVAL;
 
   //取進程的舊k_sigaction
   k = &current->sighand->action[sig-1];
 
   spin_lock_irq(&current->sighand->siglock);
 
   // 如果oact不為空,則將其賦給oact .oact參數返回舊的k_sigaction
   if (oact)
     *oact = *k;
 
   if (act) {
 
     //使SIGKILL SIGSTOP不可屏弊
     sigdelsetmask(&act->sa.sa_mask,
          sigmask(SIGKILL) | sigmask(SIGSTOP));
 
     //將新的k_siaction賦值到k
     *k = *act;
/*
     * POSIX 3.3.1.3:
     * "Setting a signal action to SIG_IGN for a signal that is
     *  pending shall cause the pending signal to be discarded,
     *  whether or not it is blocked."
     *
     * "Setting a signal action to SIG_DFL for a signal that is
     *  pending and whose default action is to ignore the signal
     *  (for example, SIGCHLD), shall cause the pending signal to
     *  be discarded, whether or not it is blocked"
     */
 
     //POSIX標准:
     //如果設置的處理為SIG_IGN 或者是SIG_DEL而且是對SIGCONT SIGCHILD SIGWINCH
     //進行重設時
     //如果有一個或者幾個這樣的信號在等待,則刪除之
     if (act->sa.sa_handler == SIG_IGN ||
       (act->sa.sa_handler == SIG_DFL && sig_kernel_ignore(sig))) {
       struct task_struct *t = current;
       sigemptyset(&mask);
       sigaddset(&mask, sig);
       rm_from_queue_full(&mask, &t->signal->shared_pending);
 
       //如果不是共享信號,在線程中的線程等待隊列中將該信號
       //刪除
       do {
          rm_from_queue_full(&mask, &t->pending);
          t = next_thread(t);
       } while (t != current);
     }
   }
 
   spin_unlock_irq(&current->sighand->siglock);
   return 0;
}

Rm_from_queue_full()用來將等待隊列中的信號刪除.並清除等待隊列中的位圖.代碼如下:

static int rm_from_queue_full(sigset_t *mask, struct sigpending *s)
{
   struct sigqueue *q, *n;
   sigset_t m;
 
 
   //如果進程接收到了一個信號,但末處理,只是將sigpending->signal簡單置位
 
   //在等待隊列中無此信號
   sigandsets(&m, mask, &s->signal);
   if (sigisemptyset(&m))
     return 0;
 
   // 刪除等待的信號
   signandsets(&s->signal, &s->signal, mask);
   list_for_each_entry_safe(q, n, &s->list, list) {
     //如果該信號就是mask中設置的信號
     if (sigismember(mask, q->info.si_signo)) {
       //將其脫鏈並且初始化
       list_del_init(&q->list);
       //釋放對應項
       __sigqueue_free(q);
     }
   }
   return 1;
}

上面有關POSIX標准,請自行查閱相關資料.

三:發送信號

在用戶空間中,我們可以用kill()給指定進程發送相應信號.它在用戶空間的定義如下所示:

int kill(pid_t pid, int signo)

pid的含義如下所示:

pid > 0 將信號發送給進程ID為pid的進程。

pid == 0 將信號發送給其進程組ID等於發送進程的進程組ID,而且發送進程有許可權向其發送信號的所有進程。

這裡用的術語“所有進程”不包括實現定義的系統進程集。對於大多數U N I X系統,系統進程集包括:交換進程(pid 0),init (pid 1)以及頁精靈進程(pid 2)。

Pid == -1 將信號發送給所有進程.除了swapper(0),init(1)和當前進程pid < 0 將信號發送給其進程組I D等於p i d絕對值,而且發送進程有許可權向其發送信號的所有進程。如上所述一樣,“所有進程”並不包括系統進程集中的進程.

Kill()的系統調用接口為sys_kill():

asmlinkage long
sys_kill(int pid, int sig)
{
   struct siginfo info;
 
   //構造一個siginfo
   info.si_signo = sig;
   info.si_errno = 0;
   info.si_code = SI_USER;
   info.si_pid = task_tgid_vnr(current);
   info.si_uid = current->uid;
 
   return kill_something_info(sig, &info, pid);
}

轉到kill_something_info():

static int kill_something_info(int sig, struct siginfo *info, int pid)
{
   int ret;
   rcu_read_lock();
  
   if (!pid) {
 
     //將信號發送到進程組
     ret = kill_pgrp_info(sig, info, task_pgrp(current));
   } else if (pid == -1) {
 
     //將信號發送到所有大於1的進程
     int retval = 0, count = 0;
     struct task_struct * p;
 
     read_lock(&tasklist_lock);
     for_each_process(p) {
       if (p->pid > 1 && !same_thread_group(p, current)) {
          int err = group_send_sig_info(sig, info, p);
          ++count;
          if (err != -EPERM)
            retval = err;
       }
     }
     read_unlock(&tasklist_lock);
     ret = count ? retval : -ESRCH;
   } else if (pid < 0) {
     //把信號發送到進程組-pid的所有進程
     ret = kill_pgrp_info(sig, info, find_vpid(-pid));
   } else {
 
     //將信號發送到pid的進程
     ret = kill_pid_info(sig, info, find_vpid(pid));
   }
   rcu_read_unlock();
   return ret;
}

假設pid > 0.轉入kill_pid_info().即把信號發送到pid的進程

int kill_pid_info(int sig, struct siginfo *info, struct pid *pid)
{
   int error;
   struct task_struct *p;
 
   rcu_read_lock();
   if (unlikely(sig_needs_tasklist(sig)))
     read_lock(&tasklist_lock);
 
   //找到進程號為pid 的進程
   p = pid_task(pid, PIDTYPE_PID);
   error = -ESRCH;
   if (p)
     error = group_send_sig_info(sig, info, p);
 
   if (unlikely(sig_needs_tasklist(sig)))
     read_unlock(&tasklist_lock);
   rcu_read_unlock();
   return error;
}

在這裡將pid轉化為對應的task_struct.然後調用group_send_sig_info().代碼如下:

int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
   unsigned long flags;
   int ret;
 
   //檢查是否有權限發送信號
   ret = check_kill_permission(sig, info, p);
 
   if (!ret && sig) {
     ret = -ESRCH;
 
       //為了防止競爭.加鎖
     if (lock_task_sighand(p,flags)) {
       //發送信號
       ret = __group_send_sig_info(sig, info, p);
 
       //解鎖
       unlock_task_sighand(p, &flags);
     }
   }
 
   return ret;
}

首先,要給進程發送信號,應該先判斷它是否具有這樣的權限.判斷的依據為:

如果是用戶空間發送的信號,檢查其是否有相應的權限

必須要滿足以下幾個條件中的任一個才可以發送:

1:發送信號者必須擁有相關的權能

2: 如果是發送SIGCONT且發送進程與種目標進程處於同一個注冊會話中

3:屬於同一個用戶的進程

轉入__group_send_sig_info():

int
__group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
   int ret = 0;
 
   assert_spin_locked(&p->sighand->siglock);
 
   //對會引起進程停止的進程進行一些特定的處理
   handle_stop_signal(sig, p);
 
   /* Short-circuit ignored signals. */
 
   //判斷信號是不是被忽略
   if (sig_ignored(p, sig))
     return ret;
 
   //如果不是一個RT信號,且等待隊列中已經有這個信號了,返回即可
   //TODO: 常規信號是不會排隊的
   if (LEGACY_QUEUE(&p->signal->shared_pending, sig))
     /* This is a non-RT signal and we already have one queued. */
     return ret;
 
   /*
   * Put this signal on the shared-pending queue, or fail with EAGAIN.
   * We always use the shared queue for process-wide signals,
   * to avoid several races.
   */
   ret = send_signal(sig, info, p, &p->signal->shared_pending);
   if (unlikely(ret))
     return ret;
 
   //喚醒該進程對該信號進行處理
   //如果該進程對此信號進行了屏弊,則選擇線程組中一個合適的進程來喚醒
   __group_complete_signal(sig, p);
   return 0;
}

具體的進程發送過程是在send_signal()完成的.它的代碼如下:

static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
       struct sigpending *signals)
{
   struct sigqueue * q = NULL;
   int ret = 0;
 
   /*
   * Deliver the signal to listening signalfds. This must be called
   * with the sighand lock held.
   */
 
   //選擇編譯函數
   signalfd_notify(t, sig);
 
   /*
   * fast-pathed signals for kernel-internal things like SIGSTOP
   * or SIGKILL.
   */
   if (info == SEND_SIG_FORCED)
     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. */
 
   //分配一個sigqueue
   q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN &&
               (is_si_special(info) ||
               info->si_code >= 0)));
   if (q) {
 
     //將分配的sigqueue 加入等待隊列
     list_add_tail(&q->list, &signals->list);
     switch ((unsigned long) info) {
     case (unsigned long) SEND_SIG_NOINFO:
       q->info.si_signo = sig;
       q->info.si_errno = 0;
       q->info.si_code = SI_USER;
       q->info.si_pid = task_pid_vnr(current);
       q->info.si_uid = current->uid;
       break;
     case (unsigned long) SEND_SIG_PRIV:
       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 (!is_si_special(info)) {
     if (sig >= SIGRTMIN && 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;
   }
 
out_set:
   //更新等待隊列的signal 位圖,表示收到了一個信號,但沒有處理
   sigaddset(&signals->signal, sig);
   return ret;
}

經過這個過程,我們看到了進程怎麼將信號發送到另外的進程.特別要注意的是,目標進程接收到信號之後會將其喚醒.這時如果目標進程是系統調用阻塞狀態就會將它的系統調用中斷.

Copyright © Linux教程網 All Rights Reserved