理解 Android 杀进程的实现原理

基于Android 6.0的源码剖析, 分析kill进程的实现原理,以及讲讲系统调用(syscall)过程,涉及源码:

    /framework/base/core/java/android/os/Process.java
    /framework/base/core/jni/android_util_Process.cpp
    /system/core/libprocessgroup/processgroup.cpp
    /frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

    /kernel/kernel/signal.c
    /Kernel/include/linux/syscalls.h
    /kernel/include/uapi/asm-generic/unistd.h
    /bionic/libc/kernel/uapi/asm-generic/unistd.h

    /art/runtime/Runtime.cc
    /art/runtime/signal_catcher.h
    /art/runtime/signal_catcher.cc

概述

文章理解Android进程创建流程,介绍了Android进程创建过程是如何从framework一步步走到虚拟机。本文正好相反则是说说进程是如何被kill的过程。简单说,kill进程其实是通过发送signal信号的方式来完成的。创建进程从Process.start开始说起,那么杀进程则相应从Process.killProcess开始讲起。

一、用户态Kill

Process.java文件有3个方法用于杀进程,下面说说这3个方法的具体工作

     Process.killProcess(int pid)
     Process.killProcessQuiet(int pid)
     Process.killProcessGroup(int uid, int pid)

1.1 killProcess

1.1.1 killProcess

[-> Process.java]

    public static final void killProcess(int pid) {
        sendSignal(pid, SIGNAL_KILL); //【见小节1.1.2】
    }

其中SIGNAL_KILL = 9,这里的sendSignal是一个Native方法。在Android系统启动过程中,虚拟机会注册各种framework所需的JNI方法,很多时候查询Java层的native方法所对应的native方法,可在路径/framework/base/core/jni中找到,在Zygote篇有介绍JNI方法查看方法。

这里的sendSignal所对应的JNI方法在android_util_Process.cpp文件的android_os_Process_SendSignal方法,接下来进入见流程2.

1.1.2 android_os_Process_sendSignal

[- >android_util_Process.cpp]

    void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig)
    {
        if (pid > 0) {
            //打印Signal信息
            ALOGI("Sending signal. PID: %" PRId32 " SIG: %" PRId32, pid, sig);
            kill(pid, sig);
        }
    }

sendSignalsendSignalQuiet的唯一区别就是在于是否有ALOGI()这一行代码。最终杀进程的实现方法都是调用kill(pid, sig)方法。

1.2 killProcessQuiet

1.2.1 killProcessQuiet

[-> Process.java]

    public static final void killProcessQuiet(int pid) {
        sendSignalQuiet(pid, SIGNAL_KILL); //【见小节1.2.2】
    }

1.2.2 android_os_Process_sendSignalQuiet

[- >android_util_Process.cpp]

    void android_os_Process_sendSignalQuiet(JNIEnv* env, jobject clazz, jint pid, jint sig)
    {
        if (pid > 0) {
            kill(pid, sig);
        }
    }

可见killProcesskillProcessQuiet的唯一区别在于是否输出log。最终杀进程的实现方法都是调用kill(pid, sig)方法。

1.3 killProcessGroup

1.3.1 killProcessGroup

[-> Process.java]

    public static final native int killProcessGroup(int uid, int pid);

该Native方法所对应的Jni方法如下:

1.3.2 android_os_Process_killProcessGroup

[-> android_util_Process.cpp]

    jint android_os_Process_killProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid)
    {
        return killProcessGroup(uid, pid, SIGKILL);  //【见小节1.3.3】
    }

1.3.3 killProcessGroup

[-> processgroup.cpp]

    int killProcessGroup(uid_t uid, int initialPid, int signal)
    {
        int processes;
        const int sleep_us = 5 * 1000;  // 5ms
        int64_t startTime = android::uptimeMillis();
        int retry = 40;
        // 【见Step 1-3-3-1】
        while ((processes = killProcessGroupOnce(uid, initialPid, signal)) > 0) {
            //当还有进程未被杀死,则重试,最多40次
            if (retry > 0) {
                usleep(sleep_us);
                --retry;
            } else {
                break; //重试40次,仍然没有杀死进程,代表杀进程失败
            }
        }
        if (processes == 0) {
            //移除进程组相应的目录 【见Step 1-3-3-2】
            return removeProcessGroup(uid, initialPid);
        } else {
            return -1;
        }
    }

1.3.3.1 killProcessGroupOnce

[-> processgroup.cpp]

    static int killProcessGroupOnce(uid_t uid, int initialPid, int signal)
    {
        int processes = 0;
        struct ctx ctx;
        pid_t pid;
        ctx.initialized = false;
        while ((pid = getOneAppProcess(uid, initialPid, &ctx)) >= 0) {
            processes++;
            if (pid == 0) {
                continue; //不应该进入该分支
            }
            int ret = kill(pid, signal); //杀进程组中的进程pid
        }
        if (ctx.initialized) {
            close(ctx.fd);
        }
        //processes代表总共杀死了进程组中的进程个数
        return processes;
    }

其中getOneAppProcess方法的作用是从节点/acct/uid_<uid>/pid_<pid>/cgroup.procs中获取相应pid,这里是进程,而非线程。

killProcessGroupOnce的功能是杀掉uid下,跟initialPid同一个进程组的所有进程。也就意味着通过kill <pid> ,当pid是某个进程的子线程时,那么最终杀的仍是进程。

最终杀进程的实现方法都是调用kill(pid, sig)方法。

1.3.3.2 removeProcessGroup

[-> processgroup.cpp]

    static int removeProcessGroup(uid_t uid, int pid)
    {
        int ret;
        char path[PROCESSGROUP_MAX_PATH_LEN] = {0};

        //删除目录 /acct/uid_<uid>/pid_<pid>/
        convertUidPidToPath(path, sizeof(path), uid, pid);
        ret = rmdir(path);

        //删除目录 /acct/uid_<uid>/
        convertUidToPath(path, sizeof(path), uid);
        rmdir(path);
        return ret;
    }

1.4 小结

流程图:


说明:

  • Process.killProcess(int pid): 杀pid进程
  • Process.killProcessQuiet(int pid):杀pid进程,且不输出log信息
  • Process.killProcessGroup(int uid, int pid):杀同一个uid下同一进程组下的所有进程

以上3个方法,最终杀进程的实现方法都是调用kill(pid, sig)方法,该方法位于用户空间的Native层,经过系统调用进入到Linux内核的sys_kill方法。对于杀进程此处的sig=9,其实与大家平时在adb里输入的 kill -9 <pid> 效果基本一致。

接下来,进入内核态,看看杀进程的过程。

二、内核态Kill

2.1. sys_kill

[-> syscalls.h]

    asmlinkage long sys_kill(int pid, int sig);

sys_kill()方法在linux内核中没有直接定义,而是通过宏定义SYSCALL_DEFINE2的方式来实现的。Android内核(Linux)会为每个syscall分配唯一的系统调用号,当执行系统调用时会根据系统调用号从系统调用表中来查看目标函数的入口地址,在calls.S文件中声明了入口地址信息(这里已经追溯到汇编语言了,就不再介绍)。另外,其中asmlinkage是gcc标签,表明该函数读取的参数位于栈中,而不是寄存器。

[-> signal.c]

    SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
    {
        struct siginfo info;
        info.si_signo = sig;
        info.si_errno = 0;
        info.si_code = SI_USER;
        info.si_pid = task_tgid_vnr(current);
        info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
        return kill_something_info(sig, &info, pid); //【见流程2.2】
    }

SYSCALL_DEFINE2是系统调用的宏定义,方法在此处经层层展开,等价于asmlinkage long sys_kill(int pid, int sig)。关于宏展开细节就不多说了,就说一点SYSCALL_DEFINE2中的2是指sys_kill方法有两个参数。

关于系统调用流程比较复杂,还涉及汇编语言,只需要知道 用户空间的kill()最终调用到内核空间signal.c的kill_something_info()方法就可以。如果有兴趣,想进一步了解,可查看Linux系统调用原理

2.2 kill_something_info

[-> signal.c]

    static int kill_something_info(int sig, struct siginfo *info, pid_t pid)
    {
        int ret;
        if (pid > 0) {
            rcu_read_lock();
            //当pid>0时,则发送给pid所对应的进程【见流程2.3】
            ret = kill_pid_info(sig, info, find_vpid(pid));
            rcu_read_unlock();
            return ret;
        }
        read_lock(&tasklist_lock);
        if (pid != -1) {
            //当pid=0时,则发送给当前进程组;
            //当pid<-1时,则发送给-pid所对应的进程。
            ret = __kill_pgrp_info(sig, info,
                    pid ? find_vpid(-pid) : task_pgrp(current));
        } else {
            //当pid=-1时,则发送给所有进程
            int retval = 0, count = 0;
            struct task_struct * p;
            for_each_process(p) {
                if (task_pid_vnr(p) > 1 &&
                        !same_thread_group(p, current)) {
                    int err = group_send_sig_info(sig, info, p);
                    ++count;
                    if (err != -EPERM)
                        retval = err;
                }
            }
            ret = count ? retval : -ESRCH;
        }
        read_unlock(&tasklist_lock);
        return ret;
    }

功能:

  • 当pid>0 时,则发送给pid所对应的进程;
  • 当pid=0 时,则发送给当前进程组;
  • 当pid=-1时,则发送给所有进程;
  • 当pid<-1时,则发送给-pid所对应的进程。

2.3 kill_pid_info

[-> signal.c]

    int kill_pid_info(int sig, struct siginfo *info, struct pid *pid)
    {
        int error = -ESRCH;
        struct task_struct *p;
        rcu_read_lock();
    retry:
        //根据pid查询到task结构体
        p = pid_task(pid, PIDTYPE_PID);
        if (p) {
            error = group_send_sig_info(sig, info, p); //【见流程2.4】
            if (unlikely(error == -ESRCH))
                goto retry;
        }
        rcu_read_unlock();
        return error;
    }

2.4 group_send_sig_info

[-> signal.c]

    int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
    {
        int ret;
        rcu_read_lock();
        //检查sig是否合法以及隐私等权限问题
        ret = check_kill_permission(sig, info, p);
        rcu_read_unlock();
        if (!ret && sig)
            ret = do_send_sig_info(sig, info, p, true); //【见流程2.5】
        return ret;
    }

2.5 do_send_sig_info

[-> signal.c]

    int do_send_sig_info(int sig, struct siginfo *info, struct task_struct *p,
                bool group)
    {
        unsigned long flags;
        int ret = -ESRCH;
        if (lock_task_sighand(p, &flags)) {
            ret = send_signal(sig, info, p, group); //【见流程2.6】
            unlock_task_sighand(p, &flags);
        }
        return ret;
    }

2.6 send_signal

[-> signal.c]

    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); //【见流程2.7】
    }

2.7 __send_signal

[-> signal.c]

    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;
        int ret = 0, result;
        assert_spin_locked(&t->sighand->siglock);
        result = TRACE_SIGNAL_IGNORED;
        if (!prepare_signal(sig, t,
                from_ancestor_ns || (info == SEND_SIG_FORCED)))
            goto ret;
        pending = group ? &t->signal->shared_pending : &t->pending;

        result = TRACE_SIGNAL_ALREADY_PENDING;
        if (legacy_queue(pending, sig))
            goto ret;
        result = TRACE_SIGNAL_DELIVERED;

        if (info == SEND_SIG_FORCED)
            goto out_set;

        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;
top Created with Sketch.