1 訊號(signal)
在中斷和異常一章,80×86處理器釋出了大約20種異常,每一種異常都有其對應的異常向量和異常處理程式。比如向量0對應除法故障,向量15對應頁故障。當15號異常發生時,異常處理程式就會向觸發該異常的行程傳送SIGSEGV訊號。所以一句話總結什麼是訊號:訊號是用於通知行程特定事件的發生的一個整數。
訊號有兩種狀態:未決阻塞(pending and blocked)和未決未阻塞(pending and nonblocked)。
當一個訊號產生時,核心通常在行程表中以某種形式設定一個標誌。當對訊號採取某些動作時(比如執行訊號處理程式),稱遞送(delivery)了一個訊號。在訊號產生(特定事件發生)和遞送的間隙,稱訊號時未決的。
行程可以選擇阻塞訊號的遞送。如果為行程產生了一個阻塞的訊號,而且對該訊號的動作是系統預設動作和捕捉該訊號,則為該行程將此訊號保持為未決狀態,直到對該訊號解除了阻塞,或者將該訊號的動作改為忽略。行程可以透過呼叫sigpending函式判斷那些訊號是設定為未決並阻塞的。
每個行程都有一個訊號遮蔽字(signal mask),它規定了行程要阻塞的訊號。函式sigprocmask可以設定行程的訊號遮蔽字。
核心中共有64種訊號,前32種是普通訊號(regular signal 0 – 31)後32種是實時訊號(rt-signal 32-63),一個普通訊號被髮送多次,只有一個被髮送到接收行程。實時訊號可以被排隊傳送。核心中並不使用實時訊號。
2 與訊號有關的資料結構
2.1 task_struct和訊號
本節敘述行程描述符和訊號的關係,在task_struct中存在幾個與訊號有關的資料結構:
signal中包含一個存放共享訊號的掛起佇列,該結構的型別是struct signal_struct。
sighand指向一個含有64個訊號處理程式的結構,結構的型別是struct sighand_struct
blocked是行程的訊號遮蔽字,是sigset_t型別。而sigset_t 是一個包含兩個元素的無符號長整型陣列
sas_ss_sp是訊號處理程式的備用棧
sas_ss_size是訊號處理程式的備用棧地址
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
typedef struct {
unsigned long sig[2];
}sigset_t;
2.2 未決訊號佇列(pending signal queue)
和未決訊號佇列有關的結構由兩個:sigpending結構和sigqueue結構。sigpending是佇列的頭節點儲存有關於訊號佇列的資訊。一個訊號點陣圖signal,signal的每一位表示一種訊號,透過signal就可以得到佇列中到底有哪些訊號在排隊。
每個行程有兩種訊號佇列,一個是私有未決訊號佇列pending,另一個是共享未決訊號佇列shared_pending。在訊號的傳遞時,優先處理私有佇列中的訊號。
truct sigpending {
struct list_head list;
sigset_t signal;
};
struct sigqueue {
struct list_head list;
spinlock_t *lock;
int flags;
siginfo_t info;
struct user_struct *user;
};
2.3 sigaction結構
sigacition有兩個主要欄位,sa_handler表明如何處理訊號。sa_flags則提供了為訊號處理添加了額外的行為,如SA_RESTART(自動重新啟動被訊號中斷的系統呼叫)。
sa_handler有三種值,SIG_DFL(執行預設操作),SIG_IGN(忽略訊號),或者訊號處理程式的一個指標。
struct k_sigaction {
struct sigaction sa;
};
struct sigaction {
__sighandler_t sa_handler;
unsigned long sa_flags;
__sigrestore_t sa_restorer;
sigset_t sa_mask; /* mask last for extensibility */
};
3 訊號的產生(generate)
訊號的產生從核心角度來說,就是將訊號插入行程的未決訊號佇列。
3.1 specific_send_sig_info
函式specific_send_sig_info負責向指定的行程傳送訊號,函式有三個引數,
sig:是要產生的訊號型別
info:等於0表示訊號由使用者行程產生,1表示由核心產生,2表示訊號是由核心產生的SIGSTOP或SIGKILL
t:訊號的接收行程
tatic 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))
ret = info->si_sys_private;
//判斷訊號是否滿足被忽略的條件
if (sig_ignored(t, sig))
goto out;
if (LEGACY_QUEUE(&t->pending, sig))
goto out;
//呼叫send_signal將訊號加入佇列
ret = send_signal(sig, info, t, &t->pending);
if (!ret && !sigismember(&t->blocked, sig))
signal_wake_up(t, sig == SIGKILL);
out:
return ret;
}
3.2 send_signal
send_signal負責將訊號插入到標的行程的pending佇列中,該函式接收四個引數;
sig: 表示傳送的訊號
info: struct siginfo型別,含有此次傳送的訊號的資訊
t : 指向接收行程
signals: 指向行程的未決訊號佇列(pending signal queue)
tatic int send_signal(int sig, struct siginfo *info, struct task_struct *t,
struct sigpending *signals)
{
struct sigqueue * q = NULL; //pending signal queue
int ret = 0;
/*
* fast-pathed signals for kernel-internal things like SIGSTOP
* or SIGKILL.
*/
if ((unsigned long)info == 2)
goto out_set;
q = __sigqueue_alloc(t, GFP_ATOMIC);
if (q) {
//add signal to queue
list_add_tail(&q->list, &signals->list);
switch ((unsigned long) info) {
//0 represent signal come from kernel ,1 user process
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);
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;
}
4 訊號的傳遞(delivery)
訊號的傳遞就是對訊號的處理過程。
4.1 訊號處理的時機
核心在允許行程恢覆在使用者態的執行之前,檢查thread_info的標誌TIF_SIGPENDING的值。每當核心處理完一個中斷或異常時,就檢查是否存在未決的訊號。
4.2 忽略和預設操作
核心呼叫do_signal()函式處理訊號。前面提過,對於每種訊號內核有三種處理手段, 忽略訊號,執行預設操作,捕捉訊號。
do_signal 接收兩個引數:
struct pt_regs regs : regs是內核的硬體背景關係,儲存一組暫存器值
sigset_t oldset :oldset指向訊號接收行程的訊號遮蔽字
do_signal的核心是重覆呼叫dequeue_signal()函式的死迴圈,直到私有未決訊號佇列和共享未決訊號佇列都沒有非阻塞的未決訊號時,迴圈結束。dequeue_signal首先處理私有未決訊號佇列中的訊號然後處理共享佇列中的訊號。
signr = dequeue_signal(current, mask, info);
et_signal_to_deliver
訊號傳遞的大部分工作由get_signal_to_deliver完成。
nt get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
struct pt_regs *regs, void *cookie)
{
//get the signal mask of current process
sigset_t *mask = ¤t->blocked;
int signr = 0;
relock:
spin_lock_irq(¤t->sighand->siglock);
for (;;) {
struct k_sigaction *ka;
//核心陳述句
signr = dequeue_signal(current, mask, info);
/*exit point 18*/
if (!signr)
break; /* will return 0 */
//獲取對訊號的aciton
ka = ¤t->sighand->action[signr-1];
if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */
continue;
if (ka->sa.sa_handler != SIG_DFL) {
/* Run the handler. */
*return_ka = *ka;
if (ka->sa.sa_flags & SA_ONESHOT)
ka->sa.sa_handler = SIG_DFL;
break; /* will return non-zero "signr" value */
}
if (sig_kernel_ignore(signr)) /* Default is nothing. */
continue;
/* Init gets no signals it doesn't want. */
if (current->pid == 1)
continue;
if (sig_kernel_stop(signr)) {
if (signr != SIGSTOP) {
spin_unlock_irq(¤t->sighand->siglock);
if (is_orphaned_pgrp(process_group(current)))
goto relock;
spin_lock_irq(¤t->sighand->siglock);
}
if (likely(do_signal_stop(signr))) {
/* It released the siglock. */
goto relock;
}
continue;
}
spin_unlock_irq(¤t->sighand->siglock);
/*
* Anything else is fatal, maybe with a core dump.
*/
current->flags |= PF_SIGNALED;
if (sig_kernel_coredump(signr)) {
do_coredump((long)signr, signr, regs);
}
do_group_exit(signr);
}
spin_unlock_irq(¤t->sighand->siglock);
return signr;
}
4.2 捕捉訊號handle_signal
訊號的捕捉由handle_signal完成,函式由5個引數
sig: 訊號編碼
info: 攜帶有描述訊號的資訊
ka: 攜帶有對於訊號的處理函式
oldset: 訊號遮蔽字
regs: 核心棧中的硬體背景關係(暫存器)
關於訊號處理函式的執行有一點需要說明,由於訊號處理函式是在使用者態下執行,而從核心態切換到使用者態時核心棧會被清空。而核心棧中儲存有被訊號中斷的程式的cs和eip。如果核心棧被清空則無法在從核心態中恢復被訊號中斷的程式的執行。
解決辦法是在核心處理訊號時,把儲存在核心態堆疊的硬體背景關係複製到當前行程的使用者態堆疊中。
這樣在訊號處理末期,就可以從核心棧中恢復被中斷程式的CS和EIP,從而恢復程式的正常執行。
tatic void
handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,
sigset_t *oldset, struct pt_regs * regs)
{
/* Are we from a system call? */
if (regs->orig_eax >= 0) {
/* If so, check system call restarting.. */
switch (regs->eax) {
case -ERESTART_RESTARTBLOCK:
case -ERESTARTNOHAND:
regs->eax = -EINTR;
break;
case -ERESTARTSYS:
if (!(ka->sa.sa_flags & SA_RESTART)) {
regs->eax = -EINTR;
break;
}
/* fallthrough */
case -ERESTARTNOINTR:
regs->eax = regs->orig_eax;
regs->eip -= 2;
}
}
/* Set up the stack frame */
if (ka->sa.sa_flags & SA_SIGINFO)
setup_rt_frame(sig, ka, info, oldset, regs);
else
setup_frame(sig, ka, oldset, regs);
if (!(ka->sa.sa_flags & SA_NODEFER)) {
spin_lock_irq(¤t->sighand->siglock);
sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask);
sigaddset(¤t->blocked,sig);
recalc_sigpending();
spin_unlock_irq(¤t->sighand->siglock);
}
}