众所周知,在Android 5.0+,系统安全有了比较大的提升。守护进程也不像4.4之前那么容易了。比如通过fork等创建的子进程也会同父进程一样被Kill.
那么到底在5.0+上发生了什么变化呢?带着好奇,决定走进去看一看究竟....
初探
查看ActivityManagerService.java源码,发现在M上kill进程时多出了一句Process.killProcessGroup(app.info.uid, pid);
也就是这一句,保证了在杀死APP的同时,也结束掉它fork/exec创建出来的所有子进程。
final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) {
..............
Process.killProcessQuiet(pid);
Process.killProcessGroup(app.info.uid, pid);
app.killed = true;
那么事实真的是这样吗?
Demo1: fork一个子进程
示列代码如下:
pid_t pid = fork();
if(pid < 0){
return;
}else if(pid > 0){
return;
}else{
}
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创建出的子进程。
在5.0+上只要kill 16992,17011也会被kill.而在4.4上,则不会。
Demo2: fork出的子进程再fork子进程
这么做的目的是为了测试子进程被init进程所领养后的情况是否也如此。
示列代码如下:
pid_t pid = fork();
if(pid < 0){
return;
}else if(pid > 0){
return;
}else{
}
pid = fork();
if(pid < 0){
return;
}else if(pid > 0){
return;
}else{
}
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
第二个进程的父进程变为了1,也就是init进程。
测试发现若kill 18602,18650也会一同被kill。
这究竟是怎么回事呢?
原来这是因为在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
具体到android中,相关的代码文件:
system/core/libprocessgroup/processgroup.cpp
对代码进行分析:
在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;
}
}
..........
}
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_%d/pid_%d/cgroup.procs
再通过adb进入手机看看这个目录:
root@cancro:/acct/uid_10064 # cd pid_18602/
root@cancro:/acct/uid_10064/pid_18602 # ls
cgroup.clone_children
cgroup.event_control
cgroup.procs
cpuacct.stat
cpuacct.usage
cpuacct.usage_percpu
notify_on_release
tasks
root@cancro:/acct/uid_10064/pid_18602 # cat cgroup.procs
18602
18650
看到jni中fork创建出的子进程18650也确实被记录在了该文件中,这是cgroup在fork,exec等的回调中完成的。
此时也就解释了为什么kill APP时,所有fork出的子进程也会被kill.
总结:
这样要在5.0上做自己的卸载提示等,看样子就不可能了。
fork出的进程生命周期与App生命周期是绑定在一起的。
重启的解决方案:
通过研究几款App,能够在被kill后还能重启的方案可能就只有利用service或者广播了。
查看工商银行的客户端,重启的机制是在Service的onDestory()函数中重新启动自己,demo code如下:
虽然这种方式不是特别的靠谱,但在5.0+上,可能这就是目前能够做的了吧。
public class MyService1 extends Service {
private MyReceiver1 a;
private MyReceiver2 b;
public IBinder onBind(Intent paramIntent)
{
return null;
}
public void onDestroy()
{
startService(new Intent(this, MyService1.class));
super.onDestroy();
}
}