給一個進程發送SIGTERM信號kernel處理flow
給一個進程發送SIGTERM信號kernel處理flow
以在命令行下給一個進程發送SIGTERM信號為例說明下kernel是怎么處理這個信號的
首先會alloc一個sigqueue,這個sigqueue代表SIGTERM,然后將這個sigqueue插入到目標進程的pending鏈表上;
然后在complete_signal()里因為SIGTERM是sig_fatal信號,所以會遍歷這個進程里的所有線程,將每個線程的的pending信號集上置上SIGKILL,並且將這個進程的task_struct的signal_struct的flag值設置為SIGNAL_GROUP_EXIT
然后是這個進程里的每個線程均去處理這個SIGKILL信號,在get_signal()里,因為進程的task_struct.signal_struct.flag值為SIGNAL_GROUP_EXIT,所以signal_group_exit()條件是成立的,所以會調用do_group_exit(ksig->info.si_signo)直接將自己exit。對於這個exit flow,有兩種case:
1. 對於非主線程,在exit_notify()里線程的exit_state將會被設置為EXIT_DEAD,然后直接call release_task(),這個函數設置一個free task_struct的rcu callback,后面會將這個線程的task_struct結構體free
2. 對於主線程,在exit_notify()里會判斷這個線程的thread_group鏈表是否為空分為兩種情況來說明(這個鏈表是進程中的非主線程的task_struct都插入了這個鏈表):
A. 如果為空,則說明在這個進程里非主線程都exit了,然后call do_notify_parent(),這個函數是給父進程發送SIGCHLD signal,這個函數返回true表示父進程忽略SIGCHLD信號;如果返回false,表示父進程並沒有忽略SIGCHLD信號。如果返回true,主線程的task_struct.exit_state將被設置為EXIT_DEAD,然后會和非主線程那樣直接call release_task();如果返回false,則主線程的task_struct.exit_state將會被設置為EXIT_ZOMBIE,此時將不會call release_task()去free task_struct,而是等父進程的wait系列系統調用將task_struct.exit_state設置為EXIT_DEAD並free task_struct(在wait_task_zombie()里call release_task()來free),即此時主線程已經是exited狀態,但是其task_struct還沒有free,處於ZOMBIE(僵屍)狀態,等待父進程來為其free task_struct(收屍)
B. 如果主線程的thread_group鏈表不為空,表示這個進程里還有其它普通線程(非主線程)還沒有exit,此時同樣主線程的task_struct.exit_state將會被設置為EXIT_ZOMBIE,此時do_notify_parent()的工作交給最后一個退出的非主線程的release_task()里完成,此時這個線程是非主線程(group_leader),同時主線程的thread_group鏈表已經為空,同時主線程的exit_state為EXIT_ZOMBIE,3個條件均滿足,所以此時call do_notify_parent(leader, leader->exit_signal)給parent進程發送SIGCHLD signal,此時非主線程call release_task()路徑應該是在處理給它的SIGKILL signal時。
所以進程exit,調用do_notify_parent()是由這個進程里最后一個exit的thread來完成
* task_struct.exit_signal一般是SIGCHLD,所以上述call do_notify_parent()一般是給parent進程發送SIGCHLD signal
主線程處理SIGKILL信號flow
[ 71.000794] CPU: 1 PID: 2802 Comm: xxx Tainted: P O 4.19.116+ #79 [ 71.000797] Hardware name: xxx (DT) [ 71.000800] Call trace: [ 71.000806] dump_backtrace+0x0/0x4 [ 71.000812] dump_stack+0xf4/0x134 [ 71.000817] get_signal+0xb7c/0xf68 [ 71.000824] do_notify_resume+0x130/0x24a0 [ 71.000829] work_pending+0x8/0x10
非主線程處理SIGKILL信號flow
[ 71.000835] CPU: 0 PID: 2862 Comm: HwBinder:2802_1 Tainted: P O 4.19.116+ #79 [ 71.000840] Hardware name: xxx (DT) [ 71.000843] Call trace: [ 71.000849] dump_backtrace+0x0/0x4 [ 71.000854] dump_stack+0xf4/0x134 [ 71.000859] get_signal+0xb7c/0xf68 [ 71.000865] do_notify_resume+0x130/0x24a0 [ 71.000869] work_pending+0x8/0x10
主線程的task_struct被free的callstack:
[ 71.004888] CPU: 2 PID: 1 Comm: init Tainted: P O 4.19.116+ #79 [ 71.004893] Hardware name: xxx (DT) [ 71.004896] Call trace: [ 71.004906] dump_backtrace+0x0/0x4 [ 71.004915] dump_stack+0xf4/0x134 [ 71.004921] release_task+0xaa0/0xac4 [ 71.004926] wait_consider_task+0x6f0/0xde8 [ 71.004932] do_wait+0x1bc/0x2e0 [ 71.004937] kernel_wait4+0x13c/0x2c8 [ 71.004941] __arm64_sys_wait4+0x44/0xe4 [ 71.004949] el0_svc_common+0xb8/0x1b8 [ 71.004954] el0_svc_handler+0x74/0x90 [ 71.004958] el0_svc+0x8/0x340
非主線程的task_struct被free的callstack:
[ 71.001001] CPU: 0 PID: 2862 Comm: HwBinder:2802_1 Tainted: P O 4.19.116+ #79 [ 71.001004] Hardware name: xxx (DT) [ 71.001007] Call trace: [ 71.001012] dump_backtrace+0x0/0x4 [ 71.001018] dump_stack+0xf4/0x134 [ 71.001023] release_task+0xaa0/0xac4 [ 71.001028] do_exit+0x140c/0x1974 [ 71.001033] do_group_exit+0x5fc/0x640 [ 71.001037] do_signal_stop+0x0/0x45c [ 71.001042] do_notify_resume+0x130/0x24a0 [ 71.001047] work_pending+0x8/0x10
上述進程的主線程的pid是2802,在這個進程里一共有兩個線程,主線程和另外一個普通線程,普通線程的pid是2862。這個進程的parent沒有ignore SIGCHLD signal,所以我們可以看到主線程的task_struct是由parent(init)進程的wait系統調用里free掉的,而非主線程的task_struct在處理SIGKILL signal時即free掉了。
ftrace分析shell下給一個進程發送一個SIGTERM signal的信號處理過程
sh-4632 [001] d..1 10963.771215: signal_generate: sig=15 errno=0 code=0 comm=xxx@1.0-se pid=4924 grp=1 res=0 HwBinder:4924_1-4925 [002] d..1 10963.771308: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0 xxx@1.0-se-4924 [002] d..1 10963.771578: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0 xxx@1.0-se-4924 [002] d..2 10963.773810: signal_generate: sig=17 errno=0 code=2 comm=init pid=1 grp=1 res=0 init-1 [001] d..2 10963.774963: signal_generate: sig=9 errno=0 code=0 comm=xxx@1.0-se pid=4924 grp=1 res=0 ksoftirqd/2-22 [002] ..s. 10963.786982: sched_process_free: comm=HwBinder:4924_1 pid=4925 prio=120 ksoftirqd/2-22 [002] ..s1 10963.786992: <stack trace>
=> run_ksoftirqd => smpboot_thread_fn => kthread => ret_from_fork <idle>-0 [001] ..s1 10963.795023: sched_process_free: comm=xxx@1.0-se pid=4924 prio=120
<idle>-0 [001] ..s2 10963.795033: <stack trace>
=> irq_exit => __handle_domain_irq => gic_handle_irq => el1_irq => arch_cpu_idle => do_idle => cpu_startup_entry => __cpu_disable
sh進程給pid 4924進程發送了一個signo為15(SIGTERM)的signal;
4924進程里的兩個線程(pid號分別為4924和4925,主線程是前者)開始分別處理SIGKILL signal;
因為4925線程先exit,所以主線程調用do_notify_parent()給parent進程發送了一個SIGCHLD signal,parent是init進程,SIGCHLD signo是17;
init進程給4924進程 發送SIGKILL signal;
4924進程里的兩個線程的task_struct被free,sched_process_free event在delayed_put_task_struct()里觸發。
從上面可以看出,init進程給4924進程發送SIGKILL應該沒什么用了,因為后者的兩個線程在處理SIGKILL signal時均已經exit了,並且在do_exit()里調用了do_task_dead()將thread state設置為了TASK_DEAD並調用了__schedule()切到其它thread去執行了,因為thread state已經被設置為了TASK_DEAD,后面也不會再schedule它運行,所以該thread是沒有機會再處理pending signal了的。因為主線程的task_struct是在init給它發送SIGKILL之后才free掉的,所以還能找到它的task_struct,所以發送信號還能成功。
void __noreturn do_task_dead(void) { /* Causes final put_task_struct in finish_task_switch(): */ set_special_state(TASK_DEAD); /* Tell freezer to ignore us: */ current->flags |= PF_NOFREEZE; __schedule(false); BUG(); /* Avoid "noreturn function does return" - but don't continue if BUG() is a NOP: */ for (;;) cpu_relax(); }
和SIGTERM signal處理類似的其它signal
在__send_signal()的最后,會調用complete_signal(),這個函數判斷發送的signal是否是fatal,並且目標進程對此signal的處理方式是SIG_DFL,則會將此signal轉化為SIGKILL,關鍵是將進程的的signal_strcut的flags設置為了SIGNAL_GROUP_EXIT。
看下哪些signal是fatal的,如果不是兩大類signal中的一個,同時目標進程對這個signal的處理方式是SIG_DFL,則這個signal是fatal signal,這兩類signal一類是KERNEL_IGNORE,一類是KERNEL_STOP,可以看到SIGTERM,SIGALRM,SIGUSR1,SIGUSR2,SIGBUS等等信號如果進程沒有安裝這些signal的信號處理函數,並且也沒有ignore這些signal(那signal的處理方式將會是SIG_DFL),那將會導致這些signal轉化為SIGKILL,如果給這個進程發送這些signal中的之一將會導致這個進程exit:
KERNEL_IGNORE: SIGCONT/SIGCHLD/SIGWINCH/SIGURG
KERNEL_STOP: SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU
static void complete_signal(int sig, struct task_struct *p, enum pid_type type) if (sig_fatal(p, sig) && !(signal->flags & SIGNAL_GROUP_EXIT) && !sigismember(&t->real_blocked, sig) && (sig == SIGKILL || !p->ptrace)) { /* * This signal will be fatal to the whole group. */ if (!sig_kernel_coredump(sig)) { /* * Start a group exit and wake everybody up. * This way we don't have other threads * running and doing things after a slower * thread has the fatal signal pending. */ signal->flags = SIGNAL_GROUP_EXIT; signal->group_exit_code = sig; signal->group_stop_count = 0; t = p; do { task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK); sigaddset(&t->pending.signal, SIGKILL); signal_wake_up(t, 1); } while_each_thread(p, t); return; } }
#define sig_fatal(t, signr) \ (!siginmask(signr, SIG_KERNEL_IGNORE_MASK|SIG_KERNEL_STOP_MASK) && \ (t)->sighand->action[(signr)-1].sa.sa_handler == SIG_DFL)
#define SIG_KERNEL_IGNORE_MASK (\ rt_sigmask(SIGCONT) | rt_sigmask(SIGCHLD) | \ rt_sigmask(SIGWINCH) | rt_sigmask(SIGURG) )
#define SIG_KERNEL_STOP_MASK (\ rt_sigmask(SIGSTOP) | rt_sigmask(SIGTSTP) | \ rt_sigmask(SIGTTIN) | rt_sigmask(SIGTTOU) )