• 给一个进程发送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)   )

     

     

  • 相关阅读:
    Node自动重启工具 nodemon
    centos 集群
    kettle操作数据库增删改
    Dinic
    vim
    mermaid简介
    联赛模拟测试32
    检讨书模板
    博客园如何添加看板娘!
    手机浏览器如何调试
  • 原文地址:https://www.cnblogs.com/aspirs/p/15850713.html
Copyright © 2020-2023  润新知