總覽
產生
內核或應用程序產生,一份簡短的信息。
傳遞
- 掛起狀態
- 非掛起狀態
信號類型
- 發給進程的信號(所有線程共享)
- 發給線程的信號
處理者
- 進程信號是其中一個沒有屏蔽這個信號的線程處理。
- 線程就是指定線程處理。
處理方式
do_signal
處理- 創建對應的特定棧幀來處理。
信號處理函數
- 整個進程中的線程共享。
- 有默認也有自定義。
- 需要的信息也可以自定義。
信號的作用
簡訊
- 一份簡短的信息。
- 生產者是內核或進程。
- 處理者是具體的線程或者是進程組中符合處理條件的線程。
內容
- 標準的信號就只有一個數字。
32
位的整數,每一位對應一個信號位。- 支持實時信號的系統是
64
位,即兩個long
.信號定義
trap -l
用上面的指令羅列出支持的信號。
在編程中一般是宏定義,然後每個宏對應一個數字。
#define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 #define SIGURG 23 #define SIGXCPU 24 #define SIGXFSZ 25 #define SIGVTALRM 26 #define SIGPROF 27 #define SIGWINCH 28 #define SIGIO 29 #define SIGPOLL SIGIO /* #define SIGLOST 29 */ #define SIGPWR 30 #define SIGSYS 31 #define SIGUNUSED 31 /* These should not be considered constants from userland. */ #define SIGRTMIN 32
目的
讓某個進程或線程知道發生了什麼特殊的事。
每個
signal
對應的有特定的事件。讓這個進程或線程處理這件事。
調用對應的信號處理函數。
信號對應的默認處理類型
終止
進程掛掉
產生
core
文件用於調試
忽略
不處理
繼續執行
如果進程處於停止則繼續執行。
停止
可再執行
信號與處理器架構有關
常規信號
1-31
掛起的時候不允許信號重複。
對於多次產生的信號,內核發現如果已經存在有對應的信號在掛起,則只接受第一個。
實時信號
32-64
不常用。
掛起的時候允許信號重複。
信號發送和接收
內核提供了許多的系統調用來產生信號。
rt_ == realtime
實時信號和普通信號的操作函數基本相同。
分類
- 發送給進程
- 發送給線程
- 發送給在某個特定線程組的進程。
- 修改信號處理函數
- 檢測是否有掛起信號。
- 修改進程阻塞信號集合。
- 等待某個信號。
發送
任何時候都可以發送。
但是這個時候的進程的狀態不確定。可能在任意狀態。
狀態對應處理方式
- 睡眠狀態內核保留等待重新執行再發送。
- 信號阻塞將會被掛起,直到取消阻塞。
信號產生
- 內核直接修改目標進程結構體來表示信號已經發送。
信號交付
- 內核強制要求內核執行信號響應函數來響應這個信號。
- 如果處於睡眠可喚醒狀態,則直接喚醒來響應。
- 交付了之後對對應位進行置位爲0。對於多個相同的信號只遞交一次。
掛起信號
- 生成但是還未交付給進程的信號叫做掛起信號。掛在進程中,掛起的信號分爲私有和公有。
- 在添加信號時會檢測新生成的信號是否已經存在,如果存在則丟掉。如果是實時信號則沒有這種限制。
- 掛起的時長不確定。
交付信號可能出現的情況
信號交付只交付給正在執行的進程。
current
宏所指向的進程就是執行進程。交付的信號可能被進程阻塞掛起,因此內核保存,等到執行並沒有阻塞的時候交付。
執行某個信號的處理函數時,這個信號會被阻塞,直到處理完。
內核處理信號
哪些進程阻塞了哪些信號。
內核到用戶態前,檢測是否有信號已經到達,還沒有處理。每次時鐘中斷都會檢測。也就是說花費一次時間片可能會檢測多次。因爲權重可大可小。
信號是否忽略需要滿足下面的所有條件。
目標進程不是出於調試狀態。
目標進程沒有阻塞信號。
發送的信號被目標進程顯示或者隱式的忽略。
回調函數默認是
ignore
或者修改爲了ignore
.處理信號將會強制的從進程執行代碼轉到執行信號處理函數。函數執行完了之後恢復執行。
進程響應信號的三種情況
顯示忽略
執行默認操作
Terminate
掛掉進程
dump
掛掉並生成
core
ignore
隱式忽略
stop
進程被調試,修改執行狀態爲
TASK_STOPPED
狀態。
continue
進程處於
TASK_STOPPED
狀態,改爲執行狀態。TASK_RUNNING
捕獲信號並調用對應的信號處理函數。
特殊信號
特殊的信號,
SIGKILL|SIGSTOP
不能被忽略,總是執行默認處理及殺死進程,也不能被捕獲,不能被阻塞。擁有權限的用戶可以用這兩個信號執行殺死進程的操作,不管進程做了任務的防禦都沒有用。執行成功的前提是有這個權限。而且這種方式對於進程是致命的。不友好的。
POSIX
標準的信號和多線程進程
- 信號處理器線程間共享。
- 各個線程擁有自己的掛起和阻塞信號集合。
kill | sigqueue
函數發送的信號是給進程的,而不是某個特定線程。包括其他的信號。- 信號傳遞給進程之後,進程任意選擇一個沒有阻塞該信號的線程處理。
- 致命信號發送給多線程進城後,內核會殺死所有的線程。而不是接收到這個信號的線程。
私有信號和共享信號
- 內核支持給某個線程發送信號,發送給特定線程的信號是該線程的特有信號也叫私有信號,有一個私有信號隊列。
- 共享信號則存放在這個線程的共享隊列。
特殊進程
0
號進程不接受任何信號。1
號進程不會接受接收自己以外的信號。即自己造成的信號接受,其他的忽略。0
號永不死亡,1
號在init
程序掛了以後死亡。相關結構體
系統內的進程,內核必須要追中,哪些信號被屏蔽,哪些被掛起。還要知道這些什麼時候執行這些信號。內核將這些信號都放在了對應的結構體裏面了。
進程描述符內
struct task_struct { ... // 共享的信號 struct signal_struct *signal; // 私有的信號 struct sigpending pending; // 信號處理函數 struct sighand_struct *sighand; // 阻塞的信號集合 sigset_t blocked; // 臨時掛起信號 sigset_t real_blocked; sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */ ... };
共享信號
struct signal_struct { ... // 共享的 struct sigpending shared_pending; /* * We don't bother to synchronize most readers of this at all, * because there is no reader checking a limit that actually needs * to get both rlim_cur and rlim_max atomically, and either one * alone is a single word that can safely be read normally. * getrlimit/setrlimit use task_lock(current->group_leader) to * protect this instead of the siglock, because they really * have no need to disable irqs. */ struct rlimit rlim[RLIM_NLIMITS]; ... };
- 結構體包含的不僅僅是和信號相關的,還有其他的比如說每個處理器的資源限制。
- 還有
pgrp
存放的是線程組第一線程的進程號。session
存放着第一session
的進程號。共有和私有掛起信號隊列
struct sigpending { struct list_head list; sigset_t signal; }; struct sigqueue { struct list_head list; int flags; siginfo_t info; struct user_struct *user; };
發送信號的同時可能有多個。發送的對象可能有多個。需要追蹤掛起的信號。
信號有兩種,多進程共享的共有掛起信號,每個線程獨有的私有掛起信號。
list
雙向循環鏈表
lock
指向
sighandler
的lock
.
flags
表示
sigqueue
的數據機構
siginfo_t
信號原因和一些必要信息。
user
進程所有者的用戶信息。
各個信號對應的處理函數
struct sighand_struct { // 用了幾個 atomic_t count; // 響應信號的函數 struct k_sigaction action[_NSIG]; // 保護信號描述符和信號處理函數 spinlock_t siglock; wait_queue_head_t signalfd_wqh; }; 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 */ };
sa_handler
SIGDFL=0
默認處理SIGIGN=1
忽略- 指針 處理函數
sa_flags
應該處理信號
SA_NOCLDSTOP
應用於信號
SIGCHLD
,子進程停止的時候不要發送信號給父進程。
SA_NOCHLDWAIT
應用於信號
SIGCHLD
,子進程停止的時候不要創建殭屍進程,直接死亡。表示父進程對子進程的死亡沒有興趣。
SA_SIGINFO
- 提供詳細信息給信號處理函數。常用的。
SA_ONSTACK
提供一個可選的棧給信號處理器。
SA_RESART
被中斷的系統調用會重啓。
SA_NODEFER|SA_NOMASK
執行處理函數的時候也不屏蔽信號。
丟掉還是直接替換?
SA_RESETHAND|SA_ONESHOT
信號處理函數執行一次後就恢復爲默認的。
sa_mask
執行處理器的時候應該屏蔽那些信號。
信號集結構體
#ifdef __KERNEL__ /* Digital Unix defines 64 signals. Most things should be clean enough to redefine this at will, if care is taken to make libc match. */ #define _NSIG 64 #define _NSIG_BPW 64 #define _NSIG_WORDS (_NSIG / _NSIG_BPW) typedef unsigned long old_sigset_t; /* at least 32 bits */ typedef struct { unsigned long sig[_NSIG_WORDS]; } sigset_t; #else /* Here we must cater to libcs that poke about in kernel headers. */ #define NSIG 32 typedef unsigned long sigset_t; #endif /* __KERNEL__ */
信號發起者信息
/*一般定義在 /usr/include/bits/siginfo.h */ typedef struct siginfo { int si_signo; int si_errno; int si_code; int __pad0; union { int _pad[SI_PAD_SIZE]; /* kill() */ struct { pid_t _pid; /* sender's pid */ uid_t _uid; /* sender's uid */ } _kill; /* POSIX.1b timers */ struct { timer_t _tid; /* timer id */ int _overrun; /* overrun count */ char _pad[sizeof(__ARCH_SI_UID_T) - sizeof(int)]; sigval_t _sigval; /* must overlay ._rt._sigval! */ int _sys_private; /* not to be passed to user */ } _timer; /* POSIX.1b signals */ struct { pid_t _pid; /* sender's pid */ uid_t _uid; /* sender's uid */ sigval_t _sigval; } _rt; /* SIGCHLD */ struct { pid_t _pid; /* which child */ uid_t _uid; /* sender's uid */ int _status; /* exit code */ clock_t _utime; clock_t _stime; } _sigchld; /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ struct { void __user *_addr; /* faulting insn/memory ref. */ int _imm; /* immediate value for "break" */ unsigned int _flags; /* see below */ unsigned long _isr; /* isr */ short _addr_lsb; /* lsb of faulting address */ } _sigfault; /* SIGPOLL */ struct { long _band; /* POLL_IN, POLL_OUT, POLL_MSG (XPG requires a "long") */ int _fd; } _sigpoll; } _sifields; } siginfo_t;
si_signo
中斷編號
si_errno
指令導致信號發起的錯誤代碼。0表示沒有錯誤。
si_code
造成的原因,和信號值有關。
/* Values for `si_code'. Positive values are reserved for kernel-generated signals. */ enum { SI_ASYNCNL = -60, /* Sent by asynch name lookup completion. */ # define SI_ASYNCNL SI_ASYNCNL SI_TKILL = -6, /* Sent by tkill. */ # define SI_TKILL SI_TKILL SI_SIGIO, /* Sent by queued SIGIO. */ # define SI_SIGIO SI_SIGIO SI_ASYNCIO, /* Sent by AIO completion. */ # define SI_ASYNCIO SI_ASYNCIO SI_MESGQ, /* Sent by real time mesq state change. */ # define SI_MESGQ SI_MESGQ SI_TIMER, /* Sent by timer expiration. */ # define SI_TIMER SI_TIMER SI_QUEUE, /* Sent by sigqueue. */ # define SI_QUEUE SI_QUEUE SI_USER, /* Sent by kill, sigsend. */ # define SI_USER SI_USER SI_KERNEL = 0x80 /* Send by kernel. */ #define SI_KERNEL SI_KERNEL }; /* `si_code' values for SIGILL signal. */ enum { ILL_ILLOPC = 1, /* Illegal opcode. */ # define ILL_ILLOPC ILL_ILLOPC ILL_ILLOPN, /* Illegal operand. */ # define ILL_ILLOPN ILL_ILLOPN ILL_ILLADR, /* Illegal addressing mode. */ # define ILL_ILLADR ILL_ILLADR ILL_ILLTRP, /* Illegal trap. */ # define ILL_ILLTRP ILL_ILLTRP ILL_PRVOPC, /* Privileged opcode. */ # define ILL_PRVOPC ILL_PRVOPC ILL_PRVREG, /* Privileged register. */ # define ILL_PRVREG ILL_PRVREG ILL_COPROC, /* Coprocessor error. */ # define ILL_COPROC ILL_COPROC ILL_BADSTK /* Internal stack error. */ # define ILL_BADSTK ILL_BADSTK }; /* `si_code' values for SIGFPE signal. */ enum { FPE_INTDIV = 1, /* Integer divide by zero. */ # define FPE_INTDIV FPE_INTDIV FPE_INTOVF, /* Integer overflow. */ # define FPE_INTOVF FPE_INTOVF FPE_FLTDIV, /* Floating point divide by zero. */ # define FPE_FLTDIV FPE_FLTDIV FPE_FLTOVF, /* Floating point overflow. */ # define FPE_FLTOVF FPE_FLTOVF FPE_FLTUND, /* Floating point underflow. */ # define FPE_FLTUND FPE_FLTUND FPE_FLTRES, /* Floating point inexact result. */ # define FPE_FLTRES FPE_FLTRES FPE_FLTINV, /* Floating point invalid operation. */ # define FPE_FLTINV FPE_FLTINV FPE_FLTSUB /* Subscript out of range. */ # define FPE_FLTSUB FPE_FLTSUB }; /* `si_code' values for SIGSEGV signal. */ enum { SEGV_MAPERR = 1, /* Address not mapped to object. */ # define SEGV_MAPERR SEGV_MAPERR SEGV_ACCERR /* Invalid permissions for mapped object. */ # define SEGV_ACCERR SEGV_ACCERR }; /* `si_code' values for SIGBUS signal. */ enum { BUS_ADRALN = 1, /* Invalid address alignment. */ # define BUS_ADRALN BUS_ADRALN BUS_ADRERR, /* Non-existant physical address. */ # define BUS_ADRERR BUS_ADRERR BUS_OBJERR /* Object specific hardware error. */ # define BUS_OBJERR BUS_OBJERR }; /* `si_code' values for SIGTRAP signal. */ enum { TRAP_BRKPT = 1, /* Process breakpoint. */ # define TRAP_BRKPT TRAP_BRKPT TRAP_TRACE /* Process trace trap. */ # define TRAP_TRACE TRAP_TRACE }; /* `si_code' values for SIGCHLD signal. */ enum { CLD_EXITED = 1, /* Child has exited. */ # define CLD_EXITED CLD_EXITED CLD_KILLED, /* Child was killed. */ # define CLD_KILLED CLD_KILLED CLD_DUMPED, /* Child terminated abnormally. */ # define CLD_DUMPED CLD_DUMPED CLD_TRAPPED, /* Traced child has trapped. */ # define CLD_TRAPPED CLD_TRAPPED CLD_STOPPED, /* Child has stopped. */ # define CLD_STOPPED CLD_STOPPED CLD_CONTINUED /* Stopped child has continued. */ # define CLD_CONTINUED CLD_CONTINUED }; /* `si_code' values for SIGPOLL signal. */ enum { POLL_IN = 1, /* Data input available. */ # define POLL_IN POLL_IN POLL_OUT, /* Output buffers available. */ # define POLL_OUT POLL_OUT POLL_MSG, /* Input message available. */ # define POLL_MSG POLL_MSG POLL_ERR, /* I/O error. */ # define POLL_ERR POLL_ERR POLL_PRI, /* High priority input available. */ # define POLL_PRI POLL_PRI POLL_HUP /* Device disconnected. */ # define POLL_HUP POLL_HUP };
_sifields
一個共享體,根據信號的不同,使用變量不同。
生成信號
生成的兩個步驟
第一步是更新目標進程的值。
第二步傳遞,一般不直接執行,而是根據目標的狀態和信號代碼來決定。可能會喚醒,可能會強制進程接受信號。
內核或函數生成信號都是通過下面的幾種函數生成的
send_sig(): Sends a signal to a single process send_sig_info(): Like send_sig(), with extended information in a siginfo_t structure force_sig(): Sends a signal that cannot be explicitly ignored or blocked by the process force_sig_info(): Like force_sig(), with extended information in a siginfo_t structure force_sig_specific(): Like force_sig(), but optimized for SIGSTOP and SIGKILL signals sys_tkill(): System call handler of tkill() (see the later section “System Calls Related to Signal Handling”) sys_tgkill(): System call handler of tgkill()
上面所有的函數都會調用一個函數
specific_send_sig_info
內核或者應用產生的信號發送給進程都是通過下面幾個函數發送的
send_group_sig_info(): Sends a signal to a single thread group identified by the process descriptor of one of its members kill_pg(): Sends a signal to all thread groups in a process group (see the section “Process Management” in Chapter 1) kill_pg_info(): Like kill_pg(), with extended information in a siginfo_t structure kill_proc(): Sends a signal to a single thread group identified by the PID of one of its members kill_proc_info(): Like kill_proc(), with extended information in a siginfo_t structure sys_kill(): System call handler of kill() (see the later section “System Calls Related to Signal Handling”) sys_rt_sigqueueinfo(): System call handler of rt_sigqueueinfo()
所有的函數都會調用函數
group_send_sig_info
specific_send_sig_info
static int specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t) { return send_signal(sig, info, t, 0); } static int send_signal(int sig, struct siginfo *info, struct task_struct *t, int group) { int from_ancestor_ns = 0; #ifdef CONFIG_PID_NS from_ancestor_ns = si_fromuser(info) && !task_pid_nr_ns(current, task_active_pid_ns(t)); #endif return __send_signal(sig, info, t, group, from_ancestor_ns); } static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, int group, int from_ancestor_ns) { struct sigpending *pending; struct sigqueue *q; int override_rlimit; trace_signal_generate(sig, info, t); assert_spin_locked(&t->sighand->siglock); if (!prepare_signal(sig, t, from_ancestor_ns)) return 0; pending = group ? &t->signal->shared_pending : &t->pending; /* * Short-circuit ignored signals and support queuing * exactly one non-rt signal, so that we can get more * detailed information about the cause of the signal. */ if (legacy_queue(pending, sig)) return 0; /* * 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. */ if (sig < SIGRTMIN) override_rlimit = (is_si_special(info) || info->si_code >= 0); else override_rlimit = 0; q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE, override_rlimit); if (q) { list_add_tail(&q->list, &pending->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_tgid_nr_ns(current, task_active_pid_ns(t)); 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); if (from_ancestor_ns) q->info.si_pid = 0; 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(). */ trace_signal_overflow_fail(sig, group, info); return -EAGAIN; } else { /* * This is a silent loss of information. We still * send the signal, but the *info bits are lost. */ trace_signal_lose_info(sig, group, info); } } out_set: signalfd_notify(t, sig); sigaddset(&pending->signal, sig); complete_signal(sig, t, group); return 0; }
specific_send_sig_info
參數
sig
信號編號
info
0
表示由用戶態用戶發送1
表示由內核發送。2
表示由內核發送,而且信號還是SIGSTOP|SIGKILL
- 指針
t
目標進程
調用前後
/* * Force a signal that the process can't ignore: if necessary * we unblock the signal and change any SIG_IGN to SIG_DFL. * * Note: If we unblock the signal, we always reset it to SIG_DFL, * since we do not want to have a signal handler that was blocked * be invoked when user space had explicitly blocked it. * * We don't want to have recursive SIGSEGV's etc, for example, * that is why we also clear SIGNAL_UNKILLABLE. */ int force_sig_info(int sig, struct siginfo *info, struct task_struct *t) { unsigned long int flags; int ret, blocked, ignored; struct k_sigaction *action; //獲取自旋鎖並禁用中斷 spin_lock_irqsave(&t->sighand->siglock, flags); action = &t->sighand->action[sig-1]; ignored = action->sa.sa_handler == SIG_IGN; blocked = sigismember(&t->blocked, sig); if (blocked || ignored) { action->sa.sa_handler = SIG_DFL; if (blocked) { sigdelset(&t->blocked, sig); recalc_sigpending_and_wake(t); } } if (action->sa.sa_handler == SIG_DFL) t->signal->flags &= ~SIGNAL_UNKILLABLE; ret = specific_send_sig_info(sig, info, t); //釋放自旋鎖並回復中斷 spin_unlock_irqrestore(&t->sighand->siglock, flags); return ret; }
- 調用前需要禁用本地的中斷並且還獲取到了自旋鎖。調用完後釋放自旋鎖並恢復之前的中斷狀態。
大致步驟
檢測目標進程是否忽略了這個信號。忽略返回0。滿足下面所有條件會忽略。
沒有被調試。
調試了gdb會獲取
沒有被阻塞。
阻塞了則會掛起
信號被顯式或隱式的忽略。
沒有被忽略則會處理。
檢測信號是否是實時信號,或者是已經掛起則返回0.
send_signal
添加信號到掛起信號集中。如果不是阻塞信號則調用
signal_wake_up
函數告訴進程信號的到來。
t->thread_info->flags |= TIF_SIGPENDING
表示有信號需要處理。如果是
SIGKILL
,並且目標進程處於TASK_ INTERRUPTIBLE
或者是TASK_STOPPED
調用try_to_wake_up
喚醒進程。如果
try_to_wake_up
返回值爲0,表示進程正在執行。
- 如果進程在其他的處理器執行,則發起處理器間中斷,
- 然後切換該進程到另一個處理器。
- 進程切換後的後檢測是否有信號掛起,如果有就處理信號。
成功在目標進程描述符掛起信號後返回1.
send_signal
作用
插入新的信號到掛起隊列。
參數
信號值
sig
信號信息
info
值或者指針,同前。
目標進程
t
掛起信號隊列。
signals
執行步驟
info
參數爲2表示內核強制殺死,信號爲SIGKILL|SIGSTOP
。則直接跳轉到9。- 掛起信號已達到資源上限
t->user->sigpendding
,用戶進程都有資源上限,則跳轉到9。- 從內存分配一個
sig_queue
結構體,存放信息。- 增長資源計數
t->user->sigpendding
,增長t->user
的引用計數。- 添加信號結構體到掛起隊列中。
- 填充信號信息結構體。
- 添加對應的信號位到信號集合中。
- 成功完成返回0.
- 前面步驟可能出現,掛起太多,內存不夠,信號是強制執行信號。
- 添加信號到信號集。
- 返回0.即使掛起到隊列失敗,對應的位也要設置在
mask
中。即使沒有內存存儲信息也要保證目標進程獲取到信號。這樣有助於恢復系統,保護系統的穩定性。
group_send_sig_info
作用
- 發送信號給一個進程組。
參數
- 信號值
sig
- 信號信息
info
或012
如前。- 目標進程
p
步驟
信號值合法檢測。
檢測信號發送者是否有權限。
信號值不能爲0,且進程沒有正在被殺死。
獲取自旋鎖並禁用當前處理器中斷。
調用
handle_stop_signal
函數,檢查新來的信號是否會讓某些信號無效。
- 如果死進程,則返回。
- 如果是
STOP|TSTP|TTIN|TTOUT
,則會調用函數rm_from_queue
刪除共享和私有隊列中掛起的SIGCONT
。- 如果是
SIGCONT
則會調用rm_from_queue
刪除共享和私有隊列中掛起的STOP|TSTP|TTIN|TTOUT
.並且喚醒進程。如果忽略信號返回0。忽略信號同前的步驟1。
如果是非實時信號且已經被掛起,則返回0.
調用
send_signal
添加信號到隊列中。如果返回是個非0,則同樣返回。調用
__group_complete_signal
函數喚醒其中一個線程來處理。釋放自旋鎖恢復當前處理器之前的中斷狀態。
返回0表示成功處理。
_ _group_complete_signal
選擇處理函數
作用
- 喚醒合適的線程來處理新的信號。
線程滿足條件
線程沒有阻塞這個信號。
線程沒有狀態不是
EXIT_ZOMBIE, EXIT_DEAD, TASK_TRACED, or TASK_STOPPED
如果信號是
SIGKILL
線程可以是TASK_TRACED or TASK_STOPPED
線程沒有被殺死
PF_EXITING
要麼是正在執行的線程,要麼是沒有被設置
TIF_SIGPENDING
.
- 喚醒有掛起信號的進程毫無意義。
- 正在執行的進程也可以處理這個掛起信號。
多個滿足條件的線程中選擇
- 如果傳入的目標進程也滿足,則用這個傳入的
p
- 不然就搜索線程組,從接受到上一個共享信號的線程開始。
成功找到處理線程就可以開始傳遞信號
- 信號檢測是否爲指明信號,如果是則殺死所有。
- 不是則喚醒合適的線程通知其有新的信號需要處理。
傳遞信號
內核處理信號
- 通過系統調用得知有信號傳遞,通過傳遞的信號值和目標進程中的目標進程
pid
來獲取對應進程的進程描述符。- 因爲信號可能在任意時刻發送,所以目標進程處於什麼狀態是未知的。
- 在多處理器主機中,如果目標進程沒有執行內核將會延遲遞交信號。
中斷上下文切換與信號處理
- 從內核中斷返回用戶進程之前,會檢測目標進程是否有信號未處理。即通過成員變量的值是否爲
SIGPENDING
來確認。- 內核每次處理完中斷或者異常
(處理器產生的軟中斷)
都會檢測是否有- 中斷常見的是時鐘中斷,當然也有其他的中斷,而異常就是非法操作,導致處理器產生的異常。比如異常訪問地址,除0操作。
非阻塞信號處理
使用
do_signal
函數處理非阻塞信號。參數
regs
寄存器信息
oldset
非空用於返回阻塞信號掩碼。
主要目的
- 簡單的講講機制,實現很複雜。
do_signal
調用場景
- 從內核中斷返回用戶進程。
- 中斷函數調用,則是簡單的返回。
do_signal
核心
循環,不斷的調用
dequeue_signal
函數,直到公有信號隊列和私有信號隊列都沒有掛起信號爲止。
dequeue_signal
返回值是剛剛刪除的掛起信號值。返回值爲0表示處理完畢。先處理私有再處理公共。返回的信號值會清理對應的掩碼。還會調用
recalc_sigpending();
調整當前的狀態。檢測目標進程
是否被調試
是則調用
do_notify_parent_cldstop and schedule
函數,通知調試進程。
dequeue_signal
返回值signr
- 根據返回值確認目標進程的處理方式。
k_sigaction ka = ¤t->sig->action[signr-1];
- 動作分三類,前面有介紹:忽略,默認,執行處理函數。
if (ka->sa.sa_handler == SIG_IGN) continue;
默認操作
// 處理默認 if(ka->sa.sa_handler == SIG_DFL) do_default_action(); // 1號進程忽略信號 if (current->pid == 1) continue; // 忽略操作 if (signr==SIGCONT || signr==SIGCHLD || signr==SIGWINCH || signr==SIGURG) continue; // 停止操作停止進程,設置進程爲`TASK_STOPPED`。如果是其他三個,且是孤兒進程則繼續。 // SIGSTOP總是停止,其他的三個不在孤兒進程再回執行退出。 if (signr==SIGSTOP || signr==SIGTSTP || signr==SIGTTIN || signr==SIGTTOU) { if (signr != SIGSTOP && is_orphaned_pgrp(current->signal->pgrp)) continue; do_signal_stop(signr); } // 檢測當前進程是否是第一個關閉線程,是則發起組關閉。 // 喚醒迭代然後修改狀態殺死進程,向父進程發起通知。 do_signal_stop();
dump
創建一個
core.pid
文件在進程執行路徑。文件包含當時的及才能拿地址空間和處理器。
以及一些必要的信息。
然後執行殺死進程操作。
自定義函數
- 調用執行。
handle_signal(signr, &info, &ka, oldset, regs); if (ka->sa.sa_flags & SA_ONESHOT) ka->sa.sa_handler = SIG_DFL; return 1;
- 如果是一次性的,改回默認。
- 一次處理一個信號,這樣可以保證實時信號能夠正確的處理。
信號處理函數與上下文切換
中斷是在內核執行,信號處理函數是用戶代碼,在用戶空間執行。
切換涉及到了進程上下文切換。而且還是頻繁的交錯切換。
每次切換到內核態的時候,內核棧都會被清空。
如果是還涉及到了系統調用,就更加複雜,因爲還要切換回執行的調用函數而不是應用程序。
解決方案
- 將內核的上下文,以及內核棧拷貝到函數調用的用戶進程棧中。執行完了之後執行相反的操作恢復之前的上下文。
kenerl mode user Mode continue_work_flow do_signal() handle_signal() setup_frame() signal_handler() system_call() sys_sigreturn() restore_sigcontext() continue_work_flow
中斷返回當前執行進程執行
do_signal
- 如果是可以喚醒就強制喚醒立即執行。
- 不可以喚醒則是在喚醒之後第一時間執行這個函數。
非阻塞信號掛起到進程後。這個進程當從中斷或者異常中返回時,就會檢測這個
do_signal
函數來處理信號。通過setup_frame or setup_rt_frame
創建一個用戶棧幀,一般有多個棧幀(類比gdb,主要是函數嵌套)
。修改了指針計數器到處理函數地址,修改了處理函數代碼。
執行完了處理函數,再執行
setup_frame or setup_rt_frame
添加的額外代碼。這些代碼調用了
sigreturn or rt_sigreturn
,這個函數調用restore_sigcontext
拷貝之前存在用戶進程棧的上下文,棧信息到內核上。執行完了系統調用就返回常規的進程,繼續執行。
(這裏之前的進程就在執行或者是馬上就要執行。)