给一个进程发送SIGTERM信号kernel处理flow
给一个进程发送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) )
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析