linux核心中的訊號機制--訊號發送
Kernel version:2.6.14
CPU architecture:ARM920T
Author:ce123(http://blog.csdn.net/ce123)
應用程式發送訊號時,主要通過kill進行。注意:不要被“kill”迷惑,它並不是發送SIGKILL訊號專用函數。這個函數主要通過系統調用sys_kill()進入核心,它接收兩個參數:
第一個參數為目標進程id,kill()可以向進程(或進程組),線程(輕權線程)發送訊號,因此pid有以下幾種情況:
- pid>0:目標進程(可能是輕權進程)由pid指定。
- pid=0:訊號被發送到當前進程組中的每一個進程。
- pid=-1:訊號被發送到任何一個進程,init進程(PID=1)和以及當前進程無法發送訊號的進程除外。
- pid<-1:訊號被發送到目標進程組,其id由參數中的pid的絕對值指定。
第二個參數為需要發送的訊號。
由於sys_kill處理的情況比較多,分析起來比較複雜,我們從tkill()函數入手,這個函數把訊號發送到由參數指定pid指定的線程(輕權進程)中。tkill的核心入口是sys_tkill(kernel/signal.c),其定義如下:
/* * Send a signal to only one task, even if it's a CLONE_THREAD task. */asmlinkage longsys_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 intspecific_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 state. * By using wake_up_state, we ensure the process will wake up and * handle its death signal. */mask = TASK_INTERRUPTIBLE;if (resume)mask |= TASK_STOPPED | TASK_TRACED;if (!wake_up_state(t, mask))kick_process(t);}
signal_wake_up()首先為進程設定TIF_SIGPENDING標誌,說明該進程有延遲的訊號要等待處理。然後再調用wake_up_state()喚醒目標進程,如果目標進程在其他的CPU上運行,wake_up_state()將返回0,此時調用kick_process()向該CPU發送一個處理器間中斷。當中斷返回前戲,會為當前進程處理延遲的訊號。
此後當該進程被調度時,在進程返回使用者空間前,會調用do_notify_resume()處理該進程的訊號。