給一個進程發送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)   )

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM