应用进程kill

目录

应用进程

进程状态与生命周期

进程被杀的原因

进程被杀的过程

杀死进程

Android 杀前台进程

Android 杀后台进程

Android 杀其它进程

进程杀死的实现原理

用户态Kill

killProcess

killProcess

android_os_Process_sendSignal

killProcessQuiet

killProcessQuiet

android_os_Process_sendSignalQuiet

killProcessGroup

killProcessGroup

android_os_Process_killProcessGroup

killProcessGroup

killProcessGroupOnce

小结

内核态kill

sys_kill

kill_something_info

kill_pid_info

group_send_sig_info

do_send_sig_info

send_signal

__send_signal

complete_signal

小结


  1. 应用进程

  1. 进程状态与生命周期

在Android系统中,进程有五种状态:创建、就绪、运行、阻塞和死亡,进程的生命周期包括创建、运行、暂停、终止等阶段,当一个进程不再需要时,系统会将其杀死以回收资源。

  1. 进程被杀的原因

Android系统会根据一定的策略来决定是否杀死一个进程,以下是一些常见的原因:

  • 系统内存不足:当系统的可用内存不足以满足新的进程需求时,系统会选择杀死一些正在运行的进程以回收内存资源。

  • 优先级进程:当系统需要为高优先级进程腾出CPU时间时,可能会杀死一些低优先级的进程。

  • 用户主动操作:用户可以通过任务管理器或第三方应用来杀死不需要的进程。

  • 系统优化:为了提高系统性能,系统会定期检查并杀死一些长时间不运行的进程。

  1. 进程被杀的过程

当系统决定杀死一个进程时,会执行以下步骤:

  1. 发送SIGTERM信号:系统首先会向目标进程发送SIGTERM信号,通知其即将被杀死,通常情况下,接收到SIGTERM信号的进程会进行清理工作,如释放资源、保存数据等,然后正常退出。

  2. 等待子进程结束:如果目标进程有子进程,系统会等待子进程结束后再杀死父进程,这是为了避免子进程成为孤儿进程,影响系统的稳定运行。

  3. 杀死进程:当目标进程没有子进程或者子进程已经结束时,系统会向目标进程发送SIGKILL信号,强制杀死进程,此时,目标进程无法进行任何清理工作,可能会导致数据丢失或其他问题。

  1. 杀死进程

  1. Android 杀前台进程

关闭了所有的 Activity 界面窗口 , 应用进程自然就被杀死了 !

activity.finishAffinity();

上述代码可以关闭所有的本任务栈内的 Activity 窗口界面 , 自然就可以杀死本进程 ; 如果想要杀得更彻底 , 使用该方法与其它的两种杀进程的方法结合使用 ;

杀死前台进程 , 将所有的 Activity 关闭 , 然后调用 android.os.Process.killProcess(android.os.Process.myPid())java.lang.System.exit(0) 中的一种方法即可 ;

关闭 Activity 建议使用 Activity 类的 finishAffinity() 方法 , 该方法可以关闭当前前台任务栈中的所有 Activity , 之后再调用上述两个杀进程的方法 , 即可成功关闭进程 , 应用不会重启 ;

// 1. 调用 Activity 类的 finishAffinity() 方法关闭任务栈中所有 Activity 界面
activity.finishAffinity();

// 2. 使用 Android 的进程 api 类 `android.os.Process` 杀死进程
android.os.Process.killProcess(android.os.Process.myPid());

// 3. 使用 Java 的进程 api 类 `java.lang.System` 杀死进程
java.lang.System.exit(0);
  1. Android 杀后台进程

使用 Android 的进程 api 类 android.os.Process 杀死进程 :

android.os.Process.killProcess(android.os.Process.myPid());

如果当前进程处于前台 , 杀死该进程后 , Android 系统会重启该进程 , 这是 Android 系统本身的机制 ;

注意 : 调用该方法杀死的应用 , 不能处于前台任务栈中 ;

使用 Java 的进程 apijava.lang.System 杀死进程 : 参数中传入 0 表示正常退出 , 传入其它值表示非正常退出 ;

java.lang.System.exit(0);

ActivityManager 会监听前台进程 , 一旦发现应用不是正常结束 , 就会重启应用 , 使用上述两种方法杀死的进程就属于非正常杀死的进程 , 应用进程会重新启动

  1. Android 杀其它进程

获取 ActivityManager , 然后使用 killBackgroundProcesses 方法杀死后台的其它进程 ;

ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
activityManager.killBackgroundProcesses("com.google.example");

需要使用 android.permission.KILL_BACKGROUND_PROCESSES 权限 ;

在清单文件中配置该权限 :

<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
  1. 进程杀死的实现原理

kill进程其实是通过发送signal信号的方式来完成的。创建进程从Process.start开始说起,那么杀进程则相应从Process.killProcess开始讲起。

  1. 用户态Kill

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

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

[-> Process.java]

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

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

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

  1. 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. killProcessQuiet
  1. killProcessQuiet

[-> Process.java]

public static final void killProcessQuiet(int pid) {
    sendSignalQuiet(pid, SIGNAL_KILL); //【见小节3.1.2.2】
}
  1. 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. killProcessGroup
  1. killProcessGroup

[-> Process.java]

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

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

  1. 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. 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. 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)方法。

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. 小结

流程图:

说明:

  • 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> 效果基本一致。

  1. 内核态kill

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

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

关于系统调用流程比较复杂,还涉及汇编语言,只需要知道 用户空间的kill()最终调用到内核空间signal.c的kill_something_info()方法就可以。

  1. 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 &&
                    !samethreadgroup(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所对应的进程。

  1. 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;
}
  1. 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;
}
  1. 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;
}
  1. 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】
}
  1. __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;
            q->info.si_pid = task_tgid_nr_ns(current,
                            task_active_pid_ns(t));
            q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
            break;
        case (unsigned long) SEND_SIG_PRIV:
            q->info.si_signo = sig;
            q->info.si_errno = 0;
            q->info.si_code = SI_KERNEL;
            q->info.si_pid = 0;
            q->info.si_uid = 0;
            break;
        default:
            copy_siginfo(&q->info, info);
            if (from_ancestor_ns)
                q->info.si_pid = 0;
            break;
        }
        userns_fixup_signal_uid(&q->info, t);
    } else if (!is_si_special(info)) {
        if (sig >= SIGRTMIN && info->si_code != SI_USER) {
            result = TRACE_SIGNAL_OVERFLOW_FAIL;
            ret = -EAGAIN;
            goto ret;
        } else {
            result = TRACE_SIGNAL_LOSE_INFO;
        }
    }
out_set:
    //将信号sig传递给正处于监听状态的signalfd
    signalfd_notify(t, sig);
    //向信号集中加入信号sig
    sigaddset(&pending->signal, sig);
    //完成信号过程,【见流程2.8】
    complete_signal(sig, t, group);
ret:
    trace_signal_generate(sig, info, t, group, result);
    return ret;
}
  1. complete_signal

[-> signal.c]

static void complete_signal(int sig, struct task_struct *p, int group)
{
    struct signal_struct *signal = p->signal;
    struct task_struct *t;
 
    //查找能处理该信号的线程
    if (wants_signal(sig, p))
        t = p;
    else if (!group || thread_group_empty(p))
        return;
    else {
        // 递归查找适合的线程
        t = signal->curr_target;
        while (!wants_signal(sig, t)) {
            t = next_thread(t);
            if (t == signal->curr_target)
                return;
        }
        signal->curr_target = t;
    }
 
    //找到一个能被杀掉的线程,如果这个信号是SIGKILL,则立刻干掉整个线程组
    if (sig_fatal(p, sig) &&
        !(signal->flags & (SIGNAL_UNKILLABLE | SIGNAL_GROUP_EXIT)) &&
        !sigismember(&t->real_blocked, sig) &&
        (sig == SIGKILL || !t->ptrace)) {
        //信号将终结整个线程组
        if (!sig_kernel_coredump(sig)) {
            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);
                //向信号集中加入信号SIGKILL
                sigaddset(&t->pending.signal, SIGKILL);
                signal_wake_up(t, 1);
            } while_each_thread(p, t);
            return;
        }
    }
 
    //该信号处于共享队列里(即将要处理的)。唤醒已选中的目标线程,并将该信号移出队列。
    signal_wake_up(t, sig == SIGKILL);
    return;
}
  1. 小结

到此Signal信号已发送给目标线程,先用一幅图来小结一下上述流程:

流程分为用户空间(User Space)和内核空间(Kernel Space)。从用户空间进入内核空间需要向内核发出syscall,用户空间的程序通过各种syscall来调用用内核空间相应的服务。系统调用是为了让用户空间的程序陷入内核,该陷入动作是由软中断来完成的。用户态的进程进行系统调用后,CPU切换到内核态,开始执行内核函数。unistd.h文件中定义了所有的系统中断号,用户态程序通过不同的系统调用号来调用不同的内核服务,通过系统调用号从系统调用表中查看到相应的内核服务。

再回到信号,在Process.java中定义了如下3个信号:

public static final int SIGNAL_QUIT = 3;  //用于输出线程trace
public static final int SIGNAL_KILL = 9;  //用于杀进程/线程
public static final int SIGNAL_USR1 = 10; //用于强制执行GC

对于kill -9,信号SIGKILL的处理过程,这是因为SIGKILL是不能被忽略同时也不能被捕获,故不会由目标线程的signal Catcher线程来处理,而是由内核直接处理,到此便完成。

但对于信号3和10,则是交由目标进程(art虚拟机)的SignalCatcher线程来捕获完成相应操作的,接下来进入目标线程来处理相应的信号。

### 如何在 Hadoop 环境中安全地终止进程 #### 使用 `hadoop job` 命令终止 MapReduce Job 为了终止一个特定的MapReduce作业,可以使用 `hadoop job -kill` 命令加上对应的Job ID来完成操作。例如: ```bash [root@bigdata01-test sqoop-1.4.7]# hadoop job -kill job_1526097883376_0059 DEPRECATED: Use of this script to execute mapred command is deprecated. Instead use the mapred command for it. 18/05/25 14:35:13 INFO client.ConfiguredRMFailoverProxyProvider: Failing over to rm2 Killed job job_1526097883376_0059 ``` 需要注意的是该命令已经被标记为过时,建议改用 `mapred job -kill` 来替代[^1]。 #### 使用 YARN Application ID 终止任务 除了传统的 `hadoop job` 方法外,还可以利用YARN框架下的Application ID来进行更精确的任务控制。这可以通过执行如下指令实现: ```bash yarn application -kill <application_id> ``` 这种方法不仅适用于MapReduce作业,也支持其他类型的分布式应用程序运行于YARN之上[^2]。 #### 获取当前正在运行的应用程序列表及其ID 要获取所有活动应用的信息以及它们各自的Appliction ID, 可以输入以下命令查询: ```bash yarn application -list ``` 此命令会返回一系列有关活跃中的应用程序详情,包括名称、用户、队列位置等重要参数。 #### 日常管理和维护所需的基本命令集 对于常规性的集群管理任务而言,了解一些基础却实用的操作命令是非常必要的。比如查看文件系统的健康状况或者调整副本数量等功能都可以借助专门设计好的工具轻松达成目标[^3]。 当遇到更为复杂的场景如节点失效等问题,则需遵循一套标准化排查步骤逐步定位并解决问题根源所在[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值