(4.6.17.6)进程保活(Android的5.0分界线):Android5.0以上版本的force close到底发生了什么改变?
来源:互联网 发布:杀破狼贪狼 知乎 编辑:程序博客网 时间:2024/04/28 06:09
在阅读本篇之前,你首先需要大概清楚一点,无论是系统杀(android机型上长按home键中清除),或者是他杀(第三方管理软件,如360,腾讯等)。其实现方法,基本都是借助ActivityManagerService的removeLruProcessLocked,改变主要也是在这里
一、 先看代码有啥不同
- 5.0以下
我们先来看看Android4.4的源码,ActivityManagerService(源码/frameworks/base/services/core/Java/com/Android/server/am/ActivityManagerService.java)是如何关闭在应用退出后清理内存的:
final void removeLruProcessLocked(ProcessRecord app) { /....... if (lrui >= 0) { if (!app.killed) { Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app); Process.killProcessQuiet(app.pid); } /....... } }
killProcessQuiet我们暂时不向下层深究,从字面看就可以,就是kill了一个pid指向的进程
- 5.0以上
final void removeLruProcessLocked(ProcessRecord app) { /....... if (lrui >= 0) { if (!app.killed) { Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app); Process.killProcessQuiet(app.pid); Process.killProcessGroup(app.info.uid, app.pid); } /....... } }
可以看到,明显多了一句killProcessGroup,也就是把和pid同组的进程全部杀死
最大的机制改变,就是这个“同组杀”,我们接下来研究这个“同组杀”,到底是如何实现的
二、 你不信这个改变? 那测试下吧
注意,本篇的分析,都建立在你基本了解了linux的命令终端、用户、会话、进程组、进程这些概念的基础上,如果你这些词都还没听过,请移步下面这篇文章做个初步了解
从零搞懂守护进程
2.1 fork一个子进程
pid_t pid = fork(); if(pid < 0){ return; }else if(pid > 0){ return; }else{ }
我们在 adb shell中 使用命令 ps| grep test,可以看到进程信息:
USER PID PPID VSIZE RSS WCHAN PC NAME u0_a64 16992 317 1536924 52588 ffffffff 00000000 S com.example.testndk2 u0_a64 17011 16992 1504092 34508 ffffffff 00000000 S com.example.testndk2 //可以看到有两个进程,17011便是在JNI中通过fork创建出的子进程。它的父进程是16992.//在5.0+上只要kill 16992,17011也会被kill.而在4.4上,则不会。//打印出来他们的进程组 getpgrp() 都是317 ps:也就是主进程的父进程的组长
- ps命令参数
- user对应着linux的用户,其实也能看出来uid,例如u0_a64的id就是10164
- pid当前进程id号
- PPID 父进程的id号
- VSIZE : virtual size,进程虚拟地址空间大小;
- RSS : 进程正在使用的物理内存的大小;
- WCHAN :进程如果处于休眠状态的话,在内核中的地址;
- PC : program counter,
- NAME: process name,进程的名称
2.2 init领养了,还算是同组么?
if(pid=fork()>0) { //父进程,让它活着 } else if(pid< 0){ perror("fail to fork1"); exit(1);//fork失败,退出 }else{//第一个子进程 if(pid=fork()>0) exit(0);//【2.1】是第一子进程,结束第一子进程 else if(pid< 0) exit(1);//fork失败,退出 else{//第二个子进程 } }
//可以看到,第二个子进程的ppid为1,证明它在它父进程死亡后,被init收养了// 测试发现若kill 18602,18650也会一同被kill//打印出来他们的进程组 getpgrp() 都是317 ps:也就是主进程的父进程的组长USER PID PPID VSIZE RSS WCHAN PC NAME u0_a64 18602 317 1538796 53848 ffffffff 00000000 S com.example.testndk2 u0_a64 18650 1 1504092 34508 ffffffff 00000000 S com.example.testndk2
2.3 守护进程实现了跨组,跨会话,怎么样?
我们知道setsid(),会让当前的子进程 跳到新的会话里,并成为新的组的组长
pid = fork(); if(pid<0){ LOGI(LOG_TAG, "第一次fork()失败"); } else if (pid == 0) {//第一个子进程 LOGI(LOG_TAG, "第一个子进程: %d",getpid()); if(setsid() == -1){ LOGI(LOG_TAG, "第一个子进程独立到新会话:失败");}//【2】第一子进程成为新的会话组长和进程组长 pid = fork(); if (pid == 0) {//第二个子进程 LOGI(LOG_TAG, "第二个子进程ID:%d,实际用户UID为:%d,有效用户UID为:%d,进程组ID:%d",getpid(),getuid(),geteuid(),getpgrp()); chdir(s_work_dir);//【3】改变工作目录到s_work_dir umask(0);;//【4】重设文件创建掩模 for(i=0;i< 5;++i)//【5】关闭打开的文件描述符 TODO 数目 close(i); //将真正用来实现的子进程写到一个二进制文件中(对应文件源码/MarsDaemon/LibMarsdaemon/jni/daemon.c),这样既解决了内存问题,又可以自己给新的进程命名 //直接运行一个二进制文件,他会占用原进程,于是我们这里仅将fork用作一个启动binary的工具 LOGI(LOG_TAG, "开始运行二进制文件,启动守护进程,当前进程ID:%d,实际用户UID为:%d,有效用户UID为:%d,进程组ID:%d",getpid(),getuid(),geteuid(),getpgrp()); execl(daemon, daemon, workdir, service, (char *)0);//二进制文件启动服务 } exit(0);//【2.1】是第一子进程,结束第一子进程 ,使得会话不会申请控制终端 } else {//主进程 // 等待第一个子进程退出,应该会立即退出,让它继续活着 waitpid(pid, &status, 0); //一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程 //exit(EXIT_SUCCESS); //【1】父进程直接退出,从而实现子进程的脱离控制终端 }
这次我们在一个完整项目里实验:
28653 是在fork后的子进程中,使用execlp()开启了一个新进程,它会替代原进程(kill原进程,执行自己占有原进程),它是一个守护进程
u0_a315 28453 361 2179636 60772 ffffffff 00000000 S com.XXXX.pocketdogu0_a315 28653 1 9248 568 ffffffff 00000000 S /data/data/com.XXXX.pocketdog/files/daemon07-27 15:01:58.670 28453-28651/com.XXXX.pocketdog I/packetdog: 开启守护进程,主进程id为:28453,实际用户UID为:10315,有效用户UID为:10315,进程组ID:361..........07-27 15:01:58.685 28652-28652/? I/packetdog: 第一个子进程: 2865207-27 15:01:58.700 28653-28653/? I/packetdog: 开始运行二进制文件,启动守护进程,当前进程ID:28653,实际用户UID为:10315,有效用户UID为:10315,进程组ID:28652
VSIZE可以看内存大小 主进程有虚拟机 多一二十兆 子进程没有
我擦,好激动呀,真的实现了进程不同组了,子进程和守护进程不在同一个组里
赶紧试一下,forc kill
07-27 15:03:47.227 2545-2667/? W/recents.Proxy: packageName = com.XXXX.pocketdog is other will be forceStopPackage07-27 15:03:47.229 1197-3392/? I/ActivityManager: Force stopping com.XXXX.pocketdog appid=10315 user=0: from pid 254507-27 15:03:47.229 1197-3392/? I/ActivityManager: Killing 28453:com.sangfor.pocketdog/u0a315 (adj 0): stop com.XXXX.pocketdog07-27 15:03:47.230 1197-3392/? W/ActivityManager: Scheduling restart of crashed service com.sangfor.pocketdog/.ForeverService in 1000ms07-27 15:03:47.231 1197-1518/? I/libprocessgroup: Killing pid 28653 in uid 10315 as part of process group 2845307-27 15:03:47.240 1197-3392/? I/ActivityManager: Force stopping service ServiceRecord{e13adf u0 com.sangfor.pocketdog/.ForeverService}
Killing pid 28653 in uid 10315 as part of process group 28453
我去,28653 这个子守护进程已经被我搞成真的“守护进程”,并且移动到28652的新会话里的新进程组了,已经和主进程28453脱离了呀,怎么还是同组?
三、同组杀? 历史同组杀?
上边,我们讲到,如果是fork的子进程,或者是init领养的子进程,ActivityManagerService你给我“组杀”了,我还能理解,毕竟他们的确还在一个 进程组里
但是,守护进程我已经跳出该组了, 你还以“组杀”的名义,把我的守护进程干掉,太不讲理了吧?
你这是什么“组杀”?
3.1 组杀的实现方式
5.0+上开启了Control Group来管理进程
关于cgroup的参考资料:
- http://lxr.free-electrons.com/source/Documentation/cgroups/cgroups.txt
http://www.linux-kongress.org/2010/slides/seyfried-cgroups-linux-kongress-2010-presentation.pdf
它会把进程以及它的所有子进程都绑到了一个组里面管理,这样就能做到将所有子进程都杀了此处,买一个关子,你要知道,这个组的概念和linux的实际进程组是有不同之处的
对代码进行分析:
在AMS中杀App时,会调用到processgroup.cpp的killProcessGroup函数,看下该函数会做什么:
int killProcessGroup(uid_t uid, int initialPid, int signal) { .......... while ((processes = killProcessGroupOnce(uid, initialPid, signal)) > 0) { if (retry > 0) { usleep(sleep_us); --retry; } else { break; } } .......... }
可以看到在killProcessGroup中只是循环调用killProcessGroupOnce,再看看该函数又是做什么的:
static int killProcessGroupOnce(uid_t uid, int initialPid, int signal) { while ((pid = getOneAppProcess(uid, initialPid, &ctx) >= 0) { processes++; ...... int ret = kill(pid, signal); if (ret == -1) { SLOGW("failed to kill pid %d: %s", pid, strerror(errno)); } }
它通过getOneAppProcess循环获取子进程id,再kill掉。
进入getOneAppProcess查看,最终发现子进程是从下面这个函数组成的文件名中读取到的:
static int convertUidPidToPath(char *path, size_t size, uid_t uid, int pid) { return snprintf(path, size, "%s/%s%d/%s%d", PROCESSGROUP_CGROUP_PATH, PROCESSGROUP_UID_PREFIX, uid, PROCESSGROUP_PID_PREFIX, pid); }
上面几个常量的定义如下:
#define PROCESSGROUP_CGROUP_PATH "/acct" #define PROCESSGROUP_UID_PREFIX "uid_" #define PROCESSGROUP_PID_PREFIX "pid_" #define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs"
所以上面的函数组成的文件名(路径)是这样的:
/acct/uid_XX/pid_XX/cgroup.procs
该文件存储了,同组的进程的信息
//使用shell命令去查看//adb shell//su 手机一定要root,不然看不了 uid_XX的目录//cd uid_XX 进入进程所属的用户目录//cat pid_/cgroup.procs pid_是某个进程的目录,cgroup存储该进程的同组进程信息
3.2 cgroup的记录是如何生成的
到现在你应该明白了,5.0的force kill是根据cgroup杀进程的,那么cgroup记录是怎么生成的呢,是linxu的同组进程的id么?
其实,基本是这样子:
- 如果当前进程是非系统进程,非白名单进程。
- 那么这个进程的所fork()或execlp出来的任何进程,都是该进程的同组进程,会在这里记录
- 那我把新创建的进程移到新的进程组呢? 对不起,只要你曾经在这个组里,那我就会在这里记录。哪怕是你移到一个新组,再setsid再开一个新会话进一个新组,我都会记录你
- 如果是系统进程,或者白名单进程呢?
- 系统进程或白名单创建的新进程,会创建一个pid_xx的文件夹,相当于根进程又启动了几个进程,他们不算同组进程,也不会记录在这里边
所以,我们明白了 5.0 实现的是“历史同组杀”
四、微信,qq怎么在5.0 以上的保活的
好啦,此处我们加入讲解下,为什么微信或者qq之类的一些软件能持久保活
看下微信的:
u0_a132 2990 362 2306772 300900 ffffffff 00000000 S com.tencent.mmu0_a132 6550 362 1707116 43600 ffffffff 00000000 S com.tencent.mm:exdeviceu0_a132 6607 362 1715528 53760 ffffffff 00000000 S com.tencent.mm:push
注意到没有,我们自己的应用在 魅族手机上看进程,尼玛全是10000以上的id号,微信的呢?
2990 6550 6607 都是10000以下
我们还知道 进程号里 0-300 是守护进程
那么很有可能在不同定制版本的手机上,区分了 300-XX是系统进程,XX1-XX2是白名单进程
你问白名单有什么好处?
看 3.2的第二项吧 这就是好处
我们现在看看java层启动服务的cgroup文件(5.1中配置AndroidManifest.xml的service中配置android:process):
u0_a179 21068 490 1515144 31516 ffffffff 00000000 S com.marswin89.marsdaemon.democgroup.procs--------21068u0_a179 21098 490 1498412 20400 ffffffff 00000000 S com.yhf.demo1cgroup.procs---------21068u0_a179 21126 490 1498412 20420 ffffffff 00000000 S com.yhf.demo2cgroup.procs----------21126
据此我们可以看出 android层启动的进程 由于是系统启动的 都会创建pid_文件夹
同时 其中的记录文件没有记录同组的其他id(如上文所述,该记录文件记录同组历史,哪怕你跳出去新的进程组,还是会一样在这里显示)
测试记录:\进程保护\MARS\进程保护_魅族A5.1
由此可见,如果想要解决 5.0的保护问题,势必要从android层开始
android层解决“历史同组”问题,native层再修复“同包名杀”和“task杀”的问题,就是整体的解决方向
五、编外:为什么保活大部分都在native层实现?
其实这个很好理解,在android最开始的版本里,就引入了“同包名杀”和“同task杀”
5.1 同包名杀
u0_a324 27615 362 1557944 50628 ffffffff 00000000 S com.marswin89.marsdaemon.demou0_a324 27647 362 1545432 38436 ffffffff 00000000 S com.marswin89.marsdaemon.demo:process1u0_a324 27677 362 1545432 38572 ffffffff 00000000 S com.marswin89.marsdaemon.demo:process207-28 17:20:46.718 1197-1220/? I/ActivityManager: Killing 27615:com.marswin89.marsdaemon.demo/u0a324 (adj 9): remove task07-28 17:20:46.729 2545-2667/? W/recents.Proxy: packageName = com.marswin89.marsdaemon.demo is other will be forceStopPackage07-28 17:20:46.731 1197-3457/? I/ActivityManager: Force stopping com.marswin89.marsdaemon.demo appid=10324 user=0: from pid 254507-28 17:20:46.737 1197-3457/? I/ActivityManager: Killing 27647:com.marswin89.marsdaemon.demo:process1/u0a324 (adj 0): stop com.marswin89.marsdaemon.demo07-28 17:20:46.738 1197-3457/? W/ActivityManager: Scheduling restart of crashed service com.marswin89.marsdaemon.demo/.Service1 in 1000ms07-28 17:20:46.739 1197-3457/? I/ActivityManager: Killing 27677:com.marswin89.marsdaemon.demo:process2/u0a324 (adj 0): stop com.marswin89.marsdaemon.demo07-28 17:20:46.740 1197-3457/? W/ActivityManager: Scheduling restart of crashed service com.marswin89.marsdaemon.demo/.Service2 in 10998ms07-28 17:20:46.743 1197-3457/? I/ActivityManager: Force stopping service ServiceRecord{378476d4 u0 com.marswin89.marsdaemon.demo/.Service1}07-28 17:20:46.743 1197-3457/? I/ActivityManager: Force stopping service ServiceRecord{1e1c2c40 u0 com.marswin89.marsdaemon.demo/.Service2}
packageName = com.marswin89.marsdaemon.demo is other will be forceStopPackage
杀进程的时候,同一个包名下的进程都会被kill掉
咦,那你说我在AndroidManifest.xml的service中配置android:process=”com.yhf.demo1”,这样包名不一样了吧,而且都是系统进程开启的,跟主进程不属“历史同组进程”,能实现保活?
别天真了,你的确实现了避免“历史同组进程”,但是系统还是认为你是同包名的进程的,最重要,还是“同task杀”
u0_a324 27615 362 1557944 50628 ffffffff 00000000 S com.marswin89.marsdaemon.demou0_a324 27647 362 1545432 38436 ffffffff 00000000 S com.yhf.demo1u0_a324 27677 362 1545432 38572 ffffffff 00000000 S com.yhf.demo2//还是被清除了 日志忘了记录 哈哈 不信的自己试试哈
5.2 同task杀
task就不多讲了
不懂
看这里here
- (4.6.17.6)进程保活(Android的5.0分界线):Android5.0以上版本的force close到底发生了什么改变?
- Android5.0以上app进程保活的正确姿势
- Android 进程常驻(4)----native保活5.0以上方案推演过程以及代码详述
- Android 进程常驻(4)----native保活5.0以上方案推演过程以及代码详述
- Android 进程常驻(4)----native保活5.0以上方案推演过程以及代码详述
- 字符串从内存写入到磁盘的过程中到底发生了什么(一)
- (4.6.17.7)进程保活(四:5.0以上):-native保活5.0以上方案推演过程以及代码详述
- Android开发的ANR和Force Close
- Android Force close是什么引起的?
- JobService和JobScheduler机制在Android5.0以上保活
- JobService和JobScheduler机制在Android5.0以上保活
- JobService和JobScheduler机制在Android5.0以上保活
- 不同种类的整型比较,到底发生了什么?
- C/C++的编译过程中到底发生了什么?
- Measure(0,0)到底发生了什么
- C#参数传递时到底发生了什么(ref)
- HBase1.0以上版本的API改变
- 什么让我的梦想发生了改变
- 深度可视化语义表述和图像描述实验
- 用BroadCast来退出应用
- 关于并发访问相同servlet的问题
- iOS应用程序5种常见的存储方式
- HDU 5774 (Where Amazing Happens 简单计算)
- (4.6.17.6)进程保活(Android的5.0分界线):Android5.0以上版本的force close到底发生了什么改变?
- A - Oil Deposits
- Wordpress整站迁移
- cmd命令里的路径包含空格 的解决方法
- java之用FileOutputStream和FileWriter来输出文本信息的区别
- 与Retrofit2的初次相遇
- Java I/O流系统学习心得
- 听说iphone7要上市了?想要,先看看你的肾合格不
- Flink 原理与实现:如何生成 StreamGraph