Framework篇 - 杀进程和 forceStop 分析

前面介绍了进程创建的过程,今天来说一说 Android 中的杀进程和 forceStop,forceStop 对应中文就是"强行停止"。

 

  1. 杀进程
  2. forceStop 

 

1. 杀进程

 

  • 1.1 Process.killProcess()

杀进程从 Process.killProcess() 开始,那么就以它作为入口。

/base/core/java/android/os/Process.java

    public static final int SIGNAL_KILL = 9;

    // 杀死进程,发送信号
    public static final void killProcess(int pid) {
        /**
         * 其中SIGNAL_KILL = 9,这里的sendSignal是一个Native方法
         */
        sendSignal(pid, SIGNAL_KILL);
    }

    public static final native void sendSignal(int pid, int signal);

 

  • 1.2 android_util_Process.android_os_Process_sendSignal()

/base/core/jni/android_util_Process.cpp

static const JNINativeMethod methods[] = {
    {"sendSignal", "(II)V", (void*)android_os_Process_sendSignal},
    {"sendSignalQuiet", "(II)V", (void*)android_os_Process_sendSignalQuiet}
};

// 进程发送信号
void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig)
{
    // 如果 pid > 0
    if (pid > 0) {
        ALOGI("Sending signal. PID: %" PRId32 " SIG: %" PRId32, pid, sig);
        kill(pid, sig);
    }
}

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

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

 

  • 1.3 内核态 Kill

/Kernel/include/linux/syscalls.h

asmlinkage long sys_kill(int pid, int sig);

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

/kernel/kernel/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】
}

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;
}

 

 

2. forceStop 

 

  • 2.1 force-stop

adb 中有一招强杀某个应用:

am force-stop pkgName
am force-stop --user 2 pkgName //只杀用户userId=2的相关信息

force-stop 命令杀掉所有用户空间下的包名 pkgName 相关的信息,也可以通过 --user 来指定用户 Id。 当执行上述 am 指令时,则会触发调用 Am.java 的 main() 方法,接下来从main() 方法开始说起。

 

  • 2.2 Am

/base/cmds/am/src/com/android/commands/am/Am.java

public class Am extends BaseCommand {
    
    public static void main(String[] args) {
        (new Am()).run(args);
    }
    
    @Override
    public void onRun() throws Exception {
        mAm = ActivityManagerNative.getDefault();
        if (mAm == null) {
            System.err.println(NO_SYSTEM_ERROR_CODE);
            throw new AndroidException("Can't connect to activity manager; is the system running?");
        }

        String op = nextArgRequired();

        if (op.equals("start")) {
            runStart();
            // ...
        } else if (op.equals("force-stop")) {
            runForceStop();
        } 
    }
}

会进入到 runForceStop() 方法:

    private void runForceStop() throws Exception {
        int userId = UserHandle.USER_ALL;

        String opt;
        while ((opt=nextOption()) != null) {
            if (opt.equals("--user")) {
                userId = parseUserArg(nextArgRequired());
            } else {
                System.err.println("Error: Unknown option: " + opt);
                return;
            }
        }
        mAm.forceStopPackage(nextArgRequired(), userId);
    }

最终进入到 ActivityManagerNative 的 forceStopPackage()。

 

  • 2.3 ActivityManagerNative

/base/core/java/android/app/ActivityManagerNative

ActivityManagerProxy.forceStopPackage():

  public void forceStopPackage(String packageName, int userId) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        data.writeString(packageName);
        data.writeInt(userId);
        mRemote.transact(FORCE_STOP_PACKAGE_TRANSACTION, data, reply, 0);
        reply.readException();
        data.recycle();
        reply.recycle();
    }

然后 IPC 进入 system server 进程的 ActivityManangerNative.onTransact():

        case FORCE_STOP_PACKAGE_TRANSACTION: {
            data.enforceInterface(IActivityManager.descriptor);
            String packageName = data.readString();
            int userId = data.readInt();
            forceStopPackage(packageName, userId);
            reply.writeNoException();
            return true;
        }

里面调用了 ActivityManagerService.forceStopPackage()。

 

  • 2.4 ActivityManagerService.forceStopPackage()

/base/services/core/java/com/android/server/am/ActivityManagerService.java

public final class ActivityManagerService extends ActivityManagerNative {
    
    /**
     * 当使用force stop方式来结束进程时, reason一般都是”from pid “ + callingPid.
     * 当然也有另外,那就是AMS.clearApplicationUserData方法调用forceStopPackageLocked的reason为”clear data”.
     *
     * @param packageName
     * @param userId
     */
    @Override
    public void forceStopPackage(final String packageName, int userId) {
        // 需要权限permission.FORCE_STOP_PACKAGES,不能让用户调用
        if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                != PackageManager.PERMISSION_GRANTED) {
            String msg = "Permission Denial: forceStopPackage() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }
        final int callingPid = Binder.getCallingPid();
        userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
                userId, true, ALLOW_FULL_ONLY, "forceStopPackage", null);
        long callingId = Binder.clearCallingIdentity();
        try {
            IPackageManager pm = AppGlobals.getPackageManager();
            synchronized(this) {
                int[] users = userId == UserHandle.USER_ALL
                        ? mUserController.getUsers() : new int[] { userId };
                for (int user : users) {
                    int pkgUid = -1;
                    try {
                        // 根据包名和userId来查询相应的uid
                        pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING,
                                user);
                    } catch (RemoteException e) {
                    }
                    if (pkgUid == -1) {
                        Slog.w(TAG, "Invalid packageName: " + packageName);
                        continue;
                    }
                    try {
                        /**
                         * 这里有一个过程非常重要,那就是setPackageStoppedState()将包的状态设置为stopped,
                         * 那么所有广播都无法接收,除非带有标记FLAG_INCLUDE_STOPPED_PACKAGES的广播,系统默认的广播几乎都是不带有该标志,
                         * 也就意味着被force-stop的应用是无法通过建立手机网络状态或者亮灭的广播来拉起进程。
                         */
                        pm.setPackageStoppedState(packageName, true, user);
                    } catch (RemoteException e) {
                    } catch (IllegalArgumentException e) {
                        Slog.w(TAG, "Failed trying to unstop package "
                                + packageName + ": " + e);
                    }
                    if (mUserController.isUserRunningLocked(user, 0)) {
                        forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
                        finishForceStopPackageLocked(packageName, pkgUid);
                    }
                }
            }
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }
}

这里有一个过程非常重要,那就是 setPackageStoppedState() 将包的状态设置为 stopped,那么所有广播都无法接收,除非带有标记 FLAG_INCLUDE_STOPPED_PACKAGES 的广播,系统默认的广播几乎都是不带有该标志,也就意味着被 force-stop 的应用是无法通过建立手机网络状态或者亮灭的广播来拉起进程。

当使用 force stop 方式来结束进程时,reason 一般都是 "from pid " + callingPid。当然也有另外,那就是AMS.clearApplicationUserData 方法调用 forceStopPackageLocked 的 reason 为 "clear data"。

再来看看 forceStopPackageLocked():

final boolean forceStopPackageLocked(String packageName, int appId,
            boolean callerWillRestart, boolean purgeCache, boolean doit,
            boolean evenPersistent, boolean uninstalling, int userId, String reason) {

        // 清理Process
        boolean didSomething = killPackageProcessesLocked(packageName, appId, userId,
                ProcessList.INVALID_ADJ, callerWillRestart, true, doit, evenPersistent,
                packageName == null ? ("stop user " + userId) : ("stop " + packageName));
        // 结束 Activity
        if (mStackSupervisor.finishDisabledPackageActivitiesLocked(
                packageName, null, doit, evenPersistent, userId)) {
            if (!doit) {
                return true;
            }
            didSomething = true;
        }

        // 结束 Service
        if (mServices.bringDownDisabledPackageServicesLocked(
                packageName, null, userId, evenPersistent, true, doit)) {
            if (!doit) {
                return true;
            }
            didSomething = true;
        }

        // 结束 Broadcast
        if (packageName == null) {
            // Remove all sticky broadcasts from this user.
            mStickyBroadcasts.remove(userId);
        }

        // 结束 ContentProvider
        ArrayList<ContentProviderRecord> providers = new ArrayList<>();
        if (mProviderMap.collectPackageProvidersLocked(packageName, null, doit, evenPersistent,
                userId, providers)) {
            if (!doit) {
                return true;
            }
            didSomething = true;
        }
        for (i = providers.size() - 1; i >= 0; i--) {
            removeDyingProviderLocked(null, providers.get(i), true);
        }
        // 移除已获取的跟该package/user相关的临时权限    
        removeUriPermissionsForPackageLocked(packageName, userId, false);
        // ...
    }

清理跟该包名相关的进程和四大组件,再来看看 finishForceStopPackageLocked():

    private void finishForceStopPackageLocked(final String packageName, int uid) {
        // 发送广播用于停止alarm以及通知
        Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
                Uri.fromParts("package", packageName, null));
        if (!mProcessesReady) {
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
        }
        intent.putExtra(Intent.EXTRA_UID, uid);
        intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
        broadcastIntentLocked(null, null, intent,
                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid));
    }

发送广播 ACTION_PACKAGE_RESTARTED,用于清理已注册的 alarm, notification 信息。

至此,总结一下 force stop 目前做的事:

  • Process: 调用 AMS.killPackageProcessesLocked() 清理该 package 所涉及的进程。
  • Activity: 调用 ASS.finishDisabledPackageActivitiesLocked() 清理该 package 所涉及的 Activity。
  • Service: 调用 AS.bringDownDisabledPackageServicesLocked() 清理该 package 所涉及的 Service。
  • Provider: 调用 AMS.removeDyingProviderLocked() 清理该 package 所涉及的 Provider。
  • BroadcastRecevier: 调用 BQ.cleanupDisabledPackageReceiversLocked() 清理该 package 所涉及的广播。

 

  • 2.5 ActivityManagerService.killPackageProcessesLocked()

/base/services/core/java/com/android/server/am/ActivityManagerService.java

private final boolean killPackageProcessesLocked(String packageName, int appId, int userId, int minOomAdj, 
boolean callerWillRestart, boolean allowRestart, boolean doit, boolean evenPersistent, String reason) {
    ArrayList<ProcessRecord> procs = new ArrayList<>();

    //遍历当前所有运行中的进程
    final int NP = mProcessNames.getMap().size();
    for (int ip=0; ip<NP; ip++) {
        SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
        final int NA = apps.size();
        for (int ia=0; ia<NA; ia++) {
            ProcessRecord app = apps.valueAt(ia);
            if (app.persistent && !evenPersistent) {
                continue; //不杀persistent进程
            }
            if (app.removed) {
                //已标记removed的进程,便是需要被杀的进程,加入procs队列
                if (doit) {
                    procs.add(app);
                }
                continue;
            }

            if (app.setAdj < minOomAdj) {
                continue; //不杀adj低于预期的进程
            }

            if (packageName == null) {
                ...
            //已指定包名的情况
            } else {
                //pkgDeps: 该进程所依赖的包名
                final boolean isDep = app.pkgDeps != null
                        && app.pkgDeps.contains(packageName);
                if (!isDep && UserHandle.getAppId(app.uid) != appId) {
                    continue;
                }
                if (userId != UserHandle.USER_ALL && app.userId != userId) {
                    continue;
                }
                //pkgList: 运行在该进程的所有包名;
                if (!app.pkgList.containsKey(packageName) && !isDep) {
                    continue;
                }
            }

            //通过前面所有条件,则意味着该进程需要被杀, 添加到procs队列
            app.removed = true;
            procs.add(app);
        }
    }

    int N = procs.size();
    for (int i=0; i<N; i++) {
        removeProcessLocked(procs.get(i), callerWillRestart, allowRestart, reason);
    }
    updateOomAdjLocked();
    return N > 0;
}

一般地 force-stop 会指定包名,该方法会遍历当前所有运行中的进程 mProcessNames,以下条件同时都不满足的进程,则会成为被杀的目标进程 (也就是说满足以下任一条件都可以免死):

  • persistent 进程。
  • 进程 setAdj < minOomAdj (默认为-100)。
  • 非 UserHandle.USER_ALL 同时,且进程的 userId 不相等:多用户模型下,不同用户下不能相互杀。
  • 进程没有依赖该 packageName,且进程的 AppId 不相等。
  • 进程没有依赖该 packageName,且该 packageName 没有运行在该进程。

通俗地来说就是:

  • forceStop 不杀系统 persistent 进程。
  • 当指定用户 userId 时,不杀其他用户空间的进程。


除此之外,以下情况则必然会成为被杀进程:

  • 进程已标记 remove=true 的进程,则会被杀。
  • 进程的 pkgDeps 中包含该 packageName,则会被杀。
  • 进程的 pkgList 中包含该 packageName,且该进程与包名所指定的 AppId 相等则会被杀。
  • 进程的 pkgList 是在启动组件或者创建进程的过程向该队列添加的,代表的是该应用下有组件运行在该进程。那么 pkgDeps是指该进程所依赖的包名,调用 ClassLoader 的过程添加。

 

  • 2.6 总结

force stop 所做的工作:

  • Process:调用 AMS.killPackageProcessesLocked() 清理该 package 所涉及的进程。
  • Activity:调用 ASS.finishDisabledPackageActivitiesLocked() 清理该 package 所涉及的 Activity。
  • Service:调用 AS.bringDownDisabledPackageServicesLocked() 清理该 package 所涉及的 Service。
  • Provider:  调用 AMS.removeDyingProviderLocked() 清理该 package 所涉及的 Provider。
  • BroadcastRecevier:调用 BQ.cleanupDisabledPackageReceiversLocked() 清理该 package 所涉及的广播。
  • 发送广播 ACTION_PACKAGE_RESTARTED,用于停止已注册的 alarm,notification。


persistent 进程的特殊待遇:

  • 进程:AMS.killPackageProcessesLocked() 不杀进程。
  • Service:ActiveServices.collectPackageServicesLocked() 不移除不清理 service。
  • Provider:ProviderMap.collectPackageProvidersLocked() 不收集不清理 provider,且不杀该 provider 所连接的 client 的persistent 进程。


功能点归纳:

  • force-stop 并不会杀 persistent 进程。
  • 当 app 被 force-stop 后,无法接收到任何普通广播,那么也就常见的监听手机网络状态的变化或者屏幕亮灭的广播来拉起进程肯定是不可行。
  • 当 app 被 force-stop 后,那么 alarm 闹钟一并被清理,无法实现定时响起的功能。
  • 当 app 被 force-stop 后,四大组件以及相关进程都被一一清理,即便多进程架构的 app 也无法拉起自己。
  • 级联诛杀:当 app 通过 ClassLoader 加载另一个 app,则会在 force-stop 的过程中会被级联诛杀。
  • 生死与共:当 app 与另个 app 使用了 share uid,则会在 force-stop 的过程,任意一方被杀则另一方也被杀,建立起生死与共的强关系。

正确的保活姿态,应该是在用户需要时保证千万别被杀,用户不需要时别强保活,一切以用户为出发点。

 

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值