一文看懂 Linux 信號處理原理與實現(xiàn)
本文轉載自微信公眾號「Linux內核那些事」,作者songsong001。轉載本文請聯(lián)系Linux內核那些事公眾號。
什么是信號
信號本質上是在軟件層次上對中斷機制的一種模擬,其主要有以下幾種來源:
- 程序錯誤:除零,非法內存訪問等。
 - 外部信號:終端 Ctrl-C 產生 SGINT 信號,定時器到期產生SIGALRM等。
 - 顯式請求:kill函數(shù)允許進程發(fā)送任何信號給其他進程或進程組。
 
目前 Linux 支持64種信號。信號分為非實時信號(不可靠信號)和實時信號(可靠信號)兩種類型,對應于 Linux 的信號值為 1-31 和 34-64。
信號是異步的,一個進程不必通過任何操作來等待信號的到達。事實上,進程也不知道信號到底什么時候到達。一般來說,我們只需要在進程中設置信號相應的處理函數(shù),當有信號到達的時候,由系統(tǒng)異步觸發(fā)相應的處理函數(shù)即可。如下代碼:
- #include <signal.h>
 - #include <unistd.h>
 - #include <stdio.h>
 - void sigcb(int signo) {
 - switch (signo) {
 - case SIGHUP:
 - printf("Get a signal -- SIGHUP\n");
 - break;
 - case SIGINT:
 - printf("Get a signal -- SIGINT\n");
 - break;
 - case SIGQUIT:
 - printf("Get a signal -- SIGQUIT\n");
 - break;
 - }
 - return;
 - }
 - int main() {
 - signal(SIGHUP, sigcb);
 - signal(SIGINT, sigcb);
 - signal(SIGQUIT, sigcb);
 - for (;;) {
 - sleep(1);
 - }
 - }
 
運行程序后,當我們按下 Ctrl+C 后,屏幕上將會打印 Get a signal -- SIGINT。當然我們可以使用 kill -s SIGINT pid 命令來發(fā)送一個信號給進程,屏幕同樣打印出 Get a signal -- SIGINT 的信息。
信號實現(xiàn)原理
接下來我們分析一下Linux對信號處理機制的實現(xiàn)原理。
信號處理相關的數(shù)據(jù)結構
在進程管理結構 task_struct 中有幾個與信號處理相關的字段,如下:
- struct task_struct {
 - ...
 - int sigpending;
 - ...
 - struct signal_struct *sig;
 - sigset_t blocked;
 - struct sigpending pending;
 - ...
 - }
 
成員 sigpending 表示進程是否有信號需要處理(1表示有,0表示沒有)。成員 blocked 表示被屏蔽的信息,每個位代表一個被屏蔽的信號。成員 sig 表示信號相應的處理方法,其類型是 struct signal_struct,定義如下:
- #define _NSIG 64
 - struct signal_struct {
 - atomic_t count;
 - struct k_sigaction action[_NSIG];
 - spinlock_t siglock;
 - };
 - typedef void (*__sighandler_t)(int);
 - struct sigaction {
 - __sighandler_t sa_handler;
 - unsigned long sa_flags;
 - void (*sa_restorer)(void);
 - sigset_t sa_mask;
 - };
 - struct k_sigaction {
 - struct sigaction sa;
 - };
 
可以看出,struct signal_struct 是個比較復雜的結構,其 action 成員是個 struct k_sigaction 結構的數(shù)組,數(shù)組中的每個成員代表著相應信號的處理信息,而 struct k_sigaction 結構其實是 struct sigaction 的簡單封裝。
我們再來看看 struct sigaction 這個結構,其中 sa_handler 成員是類型為 __sighandler_t 的函數(shù)指針,代表著信號處理的方法。
最后我們來看看 struct task_struct 結構的 pending 成員,其類型為 struct sigpending,存儲著進程接收到的信號隊列,struct sigpending 的定義如下:
- struct sigqueue {
 - struct sigqueue *next;
 - siginfo_t info;
 - };
 - struct sigpending {
 - struct sigqueue *head, **tail;
 - sigset_t signal;
 - };
 
當進程接收到一個信號時,就需要把接收到的信號添加 pending 這個隊列中。
發(fā)送信號
可以通過 kill() 系統(tǒng)調用發(fā)送一個信號給指定的進程,其原型如下:
- int kill(pid_t pid, int sig);
 
參數(shù) pid 指定要接收信號進程的ID,而參數(shù) sig 是要發(fā)送的信號。kill() 系統(tǒng)調用最終會進入內核態(tài),并且調用內核函數(shù) sys_kill(),代碼如下:
- asmlinkage long
 - sys_kill(int pid, int sig)
 - {
 - struct siginfo info;
 - info.si_signo = sig;
 - info.si_errno = 0;
 - info.si_code = SI_USER;
 - info.si_pid = current->pid;
 - info.si_uid = current->uid;
 - return kill_something_info(sig, &info, pid);
 - }
 
sys_kill() 的代碼比較簡單,首先初始化 info 變量的成員,接著調用 kill_something_info() 函數(shù)來處理發(fā)送信號的操作。kill_something_info() 函數(shù)的代碼如下:
- static int kill_something_info(int sig, struct siginfo *info, int pid)
 - {
 - if (!pid) {
 - return kill_pg_info(sig, info, current->pgrp);
 - } else if (pid == -1) {
 - int retval = 0, count = 0;
 - struct task_struct * p;
 - read_lock(&tasklist_lock);
 - for_each_task(p) {
 - if (p->pid > 1 && p != current) {
 - int err = send_sig_info(sig, info, p);
 - ++count;
 - if (err != -EPERM)
 - retval = err;
 - }
 - }
 - read_unlock(&tasklist_lock);
 - return count ? retval : -ESRCH;
 - } else if (pid < 0) {
 - return kill_pg_info(sig, info, -pid);
 - } else {
 - return kill_proc_info(sig, info, pid);
 - }
 - }
 
kill_something_info() 函數(shù)根據(jù)傳入pid 的不同來進行不同的操作,有如下4種可能:
- pid 等于0時,表示信號將送往所有與調用 kill() 的那個進程屬同一個使用組的進程。
 - pid 大于零時,pid 是信號要送往的進程ID。
 - pid 等于-1時,信號將送往調用進程有權給其發(fā)送信號的所有進程,除了進程1(init)。
 - pid 小于-1時,信號將送往以-pid為組標識的進程。
 
我們這里只分析 pid 大于0的情況,從上面的代碼可以知道,當 pid 大于0時,會調用 kill_proc_info() 函數(shù)來處理信號發(fā)送操作,其代碼如下:
- inline int
 - kill_proc_info(int sig, struct siginfo *info, pid_t pid)
 - {
 - int error;
 - struct task_struct *p;
 - read_lock(&tasklist_lock);
 - p = find_task_by_pid(pid);
 - error = -ESRCH;
 - if (p)
 - error = send_sig_info(sig, info, p);
 - read_unlock(&tasklist_lock);
 - return error;
 - }
 
kill_proc_info() 首先通過調用 find_task_by_pid() 函數(shù)來獲得 pid 對應的進程管理結構,然后通過 send_sig_info() 函數(shù)來發(fā)送信號給此進程,send_sig_info() 函數(shù)代碼如下:
- int
 - send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
 - {
 - unsigned long flags;
 - int ret;
 - ret = -EINVAL;
 - if (sig < 0 || sig > _NSIG)
 - goto out_nolock;
 - ret = -EPERM;
 - if (bad_signal(sig, info, t))
 - goto out_nolock;
 - ret = 0;
 - if (!sig || !t->sig)
 - goto out_nolock;
 - spin_lock_irqsave(&t->sigmask_lock, flags);
 - handle_stop_signal(sig, t);
 - if (ignored_signal(sig, t))
 - goto out;
 - if (sig < SIGRTMIN && sigismember(&t->pending.signal, sig))
 - goto out;
 - ret = deliver_signal(sig, info, t);
 - out:
 - spin_unlock_irqrestore(&t->sigmask_lock, flags);
 - if ((t->state & TASK_INTERRUPTIBLE) && signal_pending(t))
 - wake_up_process(t);
 - out_nolock:
 - return ret;
 - }
 
send_sig_info() 首先調用 bad_signal() 函數(shù)來檢查是否有權發(fā)送信號給進程,然后調用 ignored_signal() 函數(shù)來檢查信號是否被忽略,接著調用 deliver_signal() 函數(shù)開始發(fā)送信號,最后如果進程是睡眠狀態(tài)就喚醒進程。我們接著來分析 deliver_signal() 函數(shù):
- static int deliver_signal(int sig, struct siginfo *info, struct task_struct *t)
 - {
 - int retval = send_signal(sig, info, &t->pending);
 - if (!retval && !sigismember(&t->blocked, sig))
 - signal_wake_up(t);
 - return retval;
 - }
 
deliver_signal() 首先調用 send_signal() 函數(shù)進行信號的發(fā)送,然后調用 signal_wake_up() 函數(shù)喚醒進程。我們來分析一下最重要的函數(shù) send_signal():
- static int send_signal(int sig, struct siginfo *info, struct sigpending *signals)
 - {
 - struct sigqueue * q = NULL;
 - if (atomic_read(&nr_queued_signals) < max_queued_signals) {
 - q = kmem_cache_alloc(sigqueue_cachep, GFP_ATOMIC);
 - }
 - if (q) {
 - atomic_inc(&nr_queued_signals);
 - q->next = NULL;
 - *signals->tail = q;
 - signals->tail = &q->next;
 - 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);
 - break;
 - }
 - } else if (sig >= SIGRTMIN && info && (unsigned long)info != 1
 - && info->si_code != SI_USER) {
 - return -EAGAIN;
 - }
 - sigaddset(&signals->signal, sig);
 - return 0;
 - }
 
send_signal() 函數(shù)雖然比較長,但邏輯還是比較簡單的。在 信號處理相關的數(shù)據(jù)結構 一節(jié)我們介紹過進程管理結構 task_struct 有個 pending 的成員變量,其用于保存接收到的信號隊列。send_signal() 函數(shù)的第三個參數(shù)就是進程管理結構的 pending 成員變量。
send_signal() 首先調用 kmem_cache_alloc() 函數(shù)來申請一個類型為 struct sigqueue 的隊列節(jié)點,然后把節(jié)點添加到 pending 隊列中,接著根據(jù)參數(shù) info 的值來進行不同的操作,最后通過 sigaddset() 函數(shù)來設置信號對應的標志位,表示進程接收到該信號。
signal_wake_up() 函數(shù)會把進程的 sigpending 成員變量設置為1,表示有信號需要處理,如果進程是睡眠可中斷狀態(tài)還會喚醒進程。
至此,發(fā)送信號的流程已經完成,我們可以通過下面的調用鏈來更加直觀的理解此過程:
- kill()
 - | User Space
 - =========================================================
 - | Kernel Space
 - sys_kill()
 - └→ kill_something_info()
 - └→ kill_proc_info()
 - └→ find_task_by_pid()
 - └→ send_sig_info()
 - └→ bad_signal()
 - └→ handle_stop_signal()
 - └→ ignored_signal()
 - └→ deliver_signal()
 - └→ send_signal()
 - | └→ kmem_cache_alloc()
 - | └→ sigaddset()
 - └→ signal_wake_up()
 
內核觸發(fā)信號處理函數(shù)
上面介紹了怎么發(fā)生一個信號給指定的進程,但是什么時候會觸發(fā)信號相應的處理函數(shù)呢?為了盡快讓信號得到處理,Linux把信號處理過程放置在進程從內核態(tài)返回到用戶態(tài)前,也就是在 ret_from_sys_call 處:
- // arch/i386/kernel/entry.S
 - ENTRY(ret_from_sys_call)
 - ...
 - ret_with_reschedule:
 - ...
 - cmpl $0, sigpending(%ebx) // 檢查進程的sigpending成員是否等于1
 - jne signal_return // 如果是就跳轉到 signal_return 處執(zhí)行
 - restore_all:
 - RESTORE_ALL
 - ALIGN
 - signal_return:
 - sti // 開啟硬件中斷
 - testl $(VM_MASK),EFLAGS(%esp)
 - movl %esp,%eax
 - jne v86_signal_return
 - xorl %edx,%edx
 - call SYMBOL_NAME(do_signal) // 調用do_signal()函數(shù)進行處理
 - jmp restore_all
 
由于這是一段匯編代碼,有點不太直觀(大概知道意思就可以了),所以我在代碼中進行了注釋。主要的邏輯就是首先檢查進程的 sigpending 成員是否等于1,如果是調用 do_signal() 函數(shù)進行處理,由于 do_signal() 函數(shù)代碼比較長,所以我們分段來說明,如下:
- int do_signal(struct pt_regs *regs, sigset_t *oldset)
 - {
 - siginfo_t info;
 - struct k_sigaction *ka;
 - if ((regs->xcs & 3) != 3)
 - return 1;
 - if (!oldset)
 - oldset = ¤t->blocked;
 - for (;;) {
 - unsigned long signr;
 - spin_lock_irq(¤t->sigmask_lock);
 - signr = dequeue_signal(¤t->blocked, &info);
 - spin_unlock_irq(¤t->sigmask_lock);
 - if (!signr)
 - break;
 
上面這段代碼的主要邏輯是通過 dequeue_signal() 函數(shù)獲取到進程接收隊列中的一個信號,如果沒有信號,那么就跳出循環(huán)。我們接著來分析:
- ka = ¤t->sig->action[signr-1];
 - if (ka->sa.sa_handler == SIG_IGN) {
 - if (signr != SIGCHLD)
 - continue;
 - /* Check for SIGCHLD: it's special. */
 - while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)
 - /* nothing */;
 - continue;
 - }
 
上面這段代碼首先獲取到信號對應的處理方法,如果對此信號的處理是忽略的話,那么就直接跳過。
- if (ka->sa.sa_handler == SIG_DFL) {
 - int exit_code = signr;
 - /* Init gets no signals it doesn't want. */
 - if (current->pid == 1)
 - continue;
 - switch (signr) {
 - case SIGCONT: case SIGCHLD: case SIGWINCH:
 - continue;
 - case SIGTSTP: case SIGTTIN: case SIGTTOU:
 - if (is_orphaned_pgrp(current->pgrp))
 - continue;
 - /* FALLTHRU */
 - case SIGSTOP:
 - current->state = TASK_STOPPED;
 - current->exit_code = signr;
 - if (!(current->p_pptr->sig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP))
 - notify_parent(current, SIGCHLD);
 - schedule();
 - continue;
 - case SIGQUIT: case SIGILL: case SIGTRAP:
 - case SIGABRT: case SIGFPE: case SIGSEGV:
 - case SIGBUS: case SIGSYS: case SIGXCPU: case SIGXFSZ:
 - if (do_coredump(signr, regs))
 - exit_code |= 0x80;
 - /* FALLTHRU */
 - default:
 - sigaddset(¤t->pending.signal, signr);
 - recalc_sigpending(current);
 - current->flags |= PF_SIGNALED;
 - do_exit(exit_code);
 - /* NOTREACHED */
 - }
 - }
 - ...
 - handle_signal(signr, ka, &info, oldset, regs);
 - return 1;
 - }
 - ...
 - return 0;
 
上面的代碼表示,如果指定為默認的處理方法,那么就使用系統(tǒng)的默認處理方法去處理信號,比如 SIGSEGV 信號的默認處理方法就是使用 do_coredump() 函數(shù)來生成一個 core dump 文件,并且通過調用 do_exit() 函數(shù)退出進程。
如果指定了自定義的處理方法,那么就通過 handle_signal() 函數(shù)去進行處理,handle_signal() 函數(shù)代碼如下:
- static void
 - handle_signal(unsigned long sig, struct k_sigaction *ka,
 - siginfo_t *info, sigset_t *oldset, struct pt_regs * regs)
 - {
 - ...
 - 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_ONESHOT)
 - ka->sa.sa_handler = SIG_DFL;
 - if (!(ka->sa.sa_flags & SA_NODEFER)) {
 - spin_lock_irq(¤t->sigmask_lock);
 - sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask);
 - sigaddset(¤t->blocked,sig);
 - recalc_sigpending(current);
 - spin_unlock_irq(¤t->sigmask_lock);
 - }
 - }
 
由于信號處理程序是由用戶提供的,所以信號處理程序的代碼是在用戶態(tài)的。而從系統(tǒng)調用返回到用戶態(tài)前還是屬于內核態(tài),CPU是禁止內核態(tài)執(zhí)行用戶態(tài)代碼的,那么怎么辦?
答案先返回到用戶態(tài)執(zhí)行信號處理程序,執(zhí)行完信號處理程序后再返回到內核態(tài),再在內核態(tài)完成收尾工作。聽起來有點繞,事實也的確是這樣。下面通過一副圖片來直觀的展示這個過程(圖片來源網絡):
signal
為了達到這個目的,Linux經歷了一個十分崎嶇的過程。我們知道,從內核態(tài)返回到用戶態(tài)時,CPU要從內核棧中找到返回到用戶態(tài)的地址(就是調用系統(tǒng)調用的下一條代碼指令地址),Linux為了先讓信號處理程序執(zhí)行,所以就需要把這個返回地址修改為信號處理程序的入口,這樣當從系統(tǒng)調用返回到用戶態(tài)時,就可以執(zhí)行信號處理程序了。
所以,handle_signal() 調用了 setup_frame() 函數(shù)來構建這個過程的運行環(huán)境(其實就是修改內核棧和用戶棧相應的數(shù)據(jù)來完成)。我們先來看看內核棧的內存布局圖:
signal-kernel-stack
圖中的 eip 就是內核態(tài)返回到用戶態(tài)后開始執(zhí)行的第一條指令地址,所以把 eip 改成信號處理程序的地址就可以在內核態(tài)返回到用戶態(tài)的時候自動執(zhí)行信號處理程序了。我們看看 setup_frame() 函數(shù)其中有一行代碼就是修改 eip 的值,如下:
- static void setup_frame(int sig, struct k_sigaction *ka,
 - sigset_t *set, struct pt_regs * regs)
 - {
 - ...
 - regs->eip = (unsigned long) ka->sa.sa_handler; // regs是內核棧中保存的寄存器集合
 - ...
 - }
 
現(xiàn)在可以在內核態(tài)返回到用戶態(tài)時自動執(zhí)行信號處理程序了,但是當信號處理程序執(zhí)行完怎么返回到內核態(tài)呢?Linux的做法就是在用戶態(tài)??臻g構建一個 Frame(幀)(我也不知道為什么要這樣叫),構建這個幀的目的就是為了執(zhí)行完信號處理程序后返回到內核態(tài),并恢復原來內核棧的內容。返回到內核態(tài)的方式是調用一個名為 sigreturn() 系統(tǒng)調用,然后再 sigreturn() 中恢復原來內核棧的內容。
怎樣能在執(zhí)行完信號處理程序后調用 sigreturn() 系統(tǒng)調用呢?其實跟前面修改內核棧 eip 的值一樣,這里修改的是用戶棧 eip 的值,修改后跳轉到一個執(zhí)行下面代碼的地方(用戶棧的某一處):
- popl %eax
 - movl $__NR_sigreturn,%eax
 - int $0x80
 
從上面的匯編代碼可以知道,這里就是調用了 sigreturn() 系統(tǒng)調用。修改用戶棧的代碼在 setup_frame() 中,代碼如下:
- static void setup_frame(int sig, struct k_sigaction *ka,
 - sigset_t *set, struct pt_regs * regs)
 - {
 - ...
 - err |= __put_user(frame->retcode, &frame->pretcode);
 - /* This is popl %eax ; movl $,%eax ; int $0x80 */
 - err |= __put_user(0xb858, (short *)(frame->retcode+0));
 - err |= __put_user(__NR_sigreturn, (int *)(frame->retcode+2));
 - err |= __put_user(0x80cd, (short *)(frame->retcode+6));
 - ...
 - }
 
這幾行代碼比較難懂,其實就是修改信號程序程序返回后要執(zhí)行代碼的地址。修改后如下圖:
signal-user-stack
這樣執(zhí)行完信號處理程序后就會調用 sigreturn(),而 sigreturn() 要做的工作就是恢復原來內核棧的內容了,我們來看看 sigreturn() 的代碼:
- asmlinkage int sys_sigreturn(unsigned long __unused)
 - {
 - struct pt_regs *regs = (struct pt_regs *) &__unused;
 - struct sigframe *frame = (struct sigframe *)(regs->esp - 8);
 - sigset_t set;
 - int eax;
 - if (verify_area(VERIFY_READ, frame, sizeof(*frame)))
 - goto badframe;
 - if (__get_user(set.sig[0], &frame->sc.oldmask)
 - || (_NSIG_WORDS > 1
 - && __copy_from_user(&set.sig[1], &frame->extramask,
 - sizeof(frame->extramask))))
 - goto badframe;
 - sigdelsetmask(&set, ~_BLOCKABLE);
 - spin_lock_irq(¤t->sigmask_lock);
 - current->blocked = set;
 - recalc_sigpending(current);
 - spin_unlock_irq(¤t->sigmask_lock);
 - if (restore_sigcontext(regs, &frame->sc, &eax))
 - goto badframe;
 - return eax;
 - badframe:
 - force_sig(SIGSEGV, current);
 - return 0;
 - }
 
其中最重要的是調用 restore_sigcontext() 恢復原來內核棧的內容,要恢復原來內核棧的內容首先是要指定原來內核棧的內容,所以先要保存原來內核棧的內容。保存原來內核棧的內容也是在 setup_frame() 函數(shù)中,setup_frame() 函數(shù)把原來內核棧的內容保存到用戶棧中(也就是上面所說的 幀 中)。restore_sigcontext() 函數(shù)就是從用戶棧中讀取原來內核棧的數(shù)據(jù),然后恢復之。保存內核棧內容主要由 setup_sigcontext() 函數(shù)完成,有興趣可以查閱代碼,這里就不做詳細說明了。
這樣,當從 sigreturn() 系統(tǒng)調用返回時,就可以按原來的路徑返回到用戶程序的下一個執(zhí)行點(比如調用系統(tǒng)調用的下一行代碼)。
設置信號處理程序
最后我們來分析一下怎么設置一個信號處理程序。
用戶可以通過 signal() 系統(tǒng)調用設置一個信號處理程序,我們來看看 signal() 系統(tǒng)調用的代碼:
- 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;
 - new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;
 - ret = do_sigaction(sig, &new_sa, &old_sa);
 - return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
 - }
 
代碼比較簡單,就是先設置一個新的 struct k_sigaction 結構,把其 sa.sa_handler 字段設置為用戶自定義的處理程序。然后通過 do_sigaction() 函數(shù)進行設置,代碼如下:
- int
 - do_sigaction(int sig, const struct k_sigaction *act, struct k_sigaction *oact)
 - {
 - struct k_sigaction *k;
 - if (sig < 1 || sig > _NSIG ||
 - (act && (sig == SIGKILL || sig == SIGSTOP)))
 - return -EINVAL;
 - k = ¤t->sig->action[sig-1];
 - spin_lock(¤t->sig->siglock);
 - if (oact)
 - *oact = *k;
 - if (act) {
 - *k = *act;
 - sigdelsetmask(&k->sa.sa_mask, sigmask(SIGKILL) | sigmask(SIGSTOP));
 - if (k->sa.sa_handler == SIG_IGN
 - || (k->sa.sa_handler == SIG_DFL
 - && (sig == SIGCONT ||
 - sig == SIGCHLD ||
 - sig == SIGWINCH))) {
 - spin_lock_irq(¤t->sigmask_lock);
 - if (rm_sig_from_queue(sig, current))
 - recalc_sigpending(current);
 - spin_unlock_irq(¤t->sigmask_lock);
 - }
 - }
 - spin_unlock(¤t->sig->siglock);
 - return 0;
 - }
 
這個函數(shù)也不難,我們上面介紹過,進程管理結構中有個 sig 的字段,它是一個 struct k_sigaction 結構的數(shù)組,每個元素保存著對應信號的處理程序,所以 do_sigaction() 函數(shù)就是修改這個信號處理程序。代碼 k = ¤t->sig->action[sig-1] 就是獲取對應信號的處理程序,然后把其設置為新的信號處理程序即可。


















 
 
 












 
 
 
 