Android11上通过直接调用Process.setProcessFrozen
来冻结进程,而 在Android 13上通过 ActivityManagerService
拿到进程更多的信息来做细分的冻结,而不是之前版本上的直接调用API,
通过 @GuardedBy({"mAm"})
注释指明了这个函数在被调用时需要持有mAm对象的锁, 确保在多线程环境下访问freezeProcess函数的安全性。
Android 13 针对binder 调用进行了优化,对进程的 binder 先进行冻结 freezeBinder
,在此之前,如果随意的冻结应用,会导致一些应用后台的跨进程行为异常。
Google 最近几年逐步将 cgroup 上的功能,在上层做了一层封装,这样就可以通过上层的调用来实现CPU、内存资源的控制。
CPU冻结这一块,上层主要封装在是通 Process.java
类的两个 native 函数中, 如下是这块主要的两个函数:
/** * Freeze or unfreeze the specified process. * * @param pid Identifier of the process to freeze or unfreeze. * @param uid Identifier of the user the process is running under. * @param frozen Specify whether to free (true) or unfreeze (false). * * @hide */ public static final native void setProcessFrozen(int pid, int uid, boolean frozen); /** * Enable or disable the freezer. When enable == false all frozen processes are unfrozen, * but aren't removed from the freezer. While in this state, processes can be added or removed * by using setProcessFrozen, but they won't actually be frozen until the freezer is enabled * again. If enable == true the freezer is enabled again, and all processes * in the freezer (including the ones added while the freezer was disabled) are frozen. * * @param enable Specify whether to enable (true) or disable (false) the freezer. * * @hide */ public static final native void enableFreezer(boolean enable);
/dev/src/frameworks/base/core/java/android/os/Process.java?#L1097-L1119
setProcessFrozen, 这个函数是针对单个进程的冻结。
enableFreezer(Android 13 已经废弃), 针对所有应用的解冻,之前的实现是将整个freezer 功能开关控制,在 Android13 上 enableFreezer
方法放在了 CachedAppOptimizer
类中,上层通过 AM
实现读取应用状态批量处理进程。
Native函数 setProcessFrozen
下面以 native 函数 setProcessFrozen
介绍冻结的流程, 因为 Android 系统所规划的路线,我的理解是尽量将内核上的变化,然后规整到 AOSP 的架构里面,上层通过简单的接口调用,实现对整个设备资源进行划分。
通过简单的配置一个
void android_os_Process_setProcessFrozen( JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze){ bool success = true; if (freeze) { success = SetProcessProfiles(uid, pid, {"Frozen"}); } else { success = SetProcessProfiles(uid, pid, {"Unfrozen"}); } if (!success) { signalExceptionForGroupError(env, EINVAL, pid); }}
/dev/src/frameworks/base/core/jni/android_util_Process.cpp?#L282-L296
cgroup 抽象层
Android 10 及更高版本将对照组 (cgroup) 抽象层和任务配置文件搭配使用,让开发者能够使用它们来描述应用于某个线程或进程的一组(或多组)限制。
然后,系统按照任务配置文件的规定操作来选择一个或多个适当的 cgroup;
通过这种方式,系统可以应用各种限制,并对底层 cgroup 功能集进行更改,而不会影响较高的软件层。
cgroup 提供一种机制,可将任务集(包括进程、线程及其所有未来的子级)聚合并分区到具有专门行为的层级组中。Android 使用 cgroup 控制及考量 CPU 和内存等系统资源的使用和分配情况,并支持 Linux 内核 cgroup v1 和 cgroup v2。
libprocessgroup 中的 SetProcessProfiles
函数,这个函数是通过 TaskProfiles
类来实现的,这个类主要是通过 TaskProfiles::GetInstance()
来获取单例对象,然后调用 SetProcessProfiles
函数来实现进程的冻结。
通过传输三个参数 UID, PID, “Frozen” or “Unfrozen”,来实现进程的冻结和解冻。
下面通过 SetProcessProfiles
函数来介绍冻结的流程,通过传输下去的 “Frozen” 字符如何最终写入到 cgroup 中。
bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector& profiles) { return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles, false);}
/dev/src/system/core/libprocessgroup/processgroup.cpp?#L150-L152
TaskProfiles 类中的 SetProcessProfiles
函数,这个函数主要是通过 TaskProfile
类来实现的,
这个类主要是通过 TaskProfile::EnableResourceCaching
函数读取配置文件,通过 TaskProfile::ExecuteForProcess
函数来实现进程的冻结。
bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid, const std::vector& profiles, bool use_fd_cache) { bool success = true; for (const auto& name : profiles) { TaskProfile* profile = GetProfile(name); if (profile != nullptr) { if (use_fd_cache) { profile->EnableResourceCaching(ProfileAction::RCT_PROCESS); } if (!profile->ExecuteForProcess(uid, pid)) { PLOG(WARNING) << "Failed to apply " << name << " process profile"; success = false; } } else { PLOG(WARNING) << "Failed to find " << name << " process profile"; success = false; } } return success;}
/dev/src/system/core/libprocessgroup/task_profiles.cpp?#L807-L826
对于新的架构之下,TaskProfile
类中的 EnableResourceCaching
函数,这个函数主要是通过 FdCacheHelper
类来实现的,这个类主要是通过 FdCacheHelper::Cache
函数来实现进程的冻结, 这里主要是对文件做一个缓存,这里文件这块的流程就不深入分解了。
void SetCgroupAction::EnableResourceCaching(ResourceCacheType cache_type) { std::lock_guard lock(fd_mutex_); // Return early to prevent unnecessary calls to controller_.Get{Tasks|Procs}FilePath() which // include regex evaluations if (fd_[cache_type] != FdCacheHelper::FDS_NOT_CACHED) { return; } switch (cache_type) { case (ProfileAction::RCT_TASK): FdCacheHelper::Cache(controller_.GetTasksFilePath(path_), fd_[cache_type]); break; case (ProfileAction::RCT_PROCESS): // uid and pid don't matter because IsAppDependentPath ensures the path doesn't use them FdCacheHelper::Cache(controller_.GetProcsFilePath(path_, 0, 0), fd_[cache_type]); break; default: LOG(ERROR) << "Invalid cache type is specified!"; break; }}
/dev/src/system/core/libprocessgroup/task_profiles.cpp?#L339-L358
task_profiles.json
task_profiles.json
文件位于 /system/core/libprocessgroup/profiles/
下。
该文件用于描述要应用于进程或线程的一组特定操作。这组操作与一个配置文件名称相关联,后者在 SetTaskProfiles
和 SetProcessProfiles
调用中用于调用配置文件操作。
cpuset
memory
cpu
blkio
等,这些也可以新增自定义)task_profiles.json
文件中,在 Attributes
属性下, 对应冻结的属性为:”FreezerState”,对应的控制器为freezer
,然后最终写的节点为 cgroup.freeze
.
{ "Name": "FreezerState", "Controller": "freezer", "File": "cgroup.freeze" }
/dev/src/system/core/libprocessgroup/profiles/task_profiles.json?#L75-L79
“Frozen” 关键词最终定义的地方在:
{ "Name": "Frozen", "Actions": [ { "Name": "SetAttribute", "Params": { "Name": "FreezerState", "Value": "1" } } ] }, { "Name": "Unfrozen", "Actions": [ { "Name": "SetAttribute", "Params": { "Name": "FreezerState", "Value": "0" } } ] },
/dev/src/system/core/libprocessgroup/profiles/task_profiles.json?#L96-L121
cgroup 节点
如上通过配置的最终状态,实现对 cgroup 文件节点的控制。
最终体现到的节点状态如下:
0 12806 0 I< 39 fg [kworker/4:0H] kworker/4:0H worker_thread10135 12821 0 S 19 bg com.duowan.kiwi com.duowan.kiwi do_freezer_trap 0 12869 0 I< 39 fg [kbase_event] kbase_event rescuer_thread
通过UID 找到对应的cgroup:
λ adb shell ls /sys/fs/cgroup/uid_10135cgroup.controllers cgroup.max.descendants cgroup.threads io.pressurecgroup.events cgroup.procs cgroup.type memory.pressurecgroup.freeze cgroup.stat cpu.pressure pid_12821cgroup.max.depth cgroup.subtree_control cpu.stat
冻结跨进程行为
在Android中,跨进程交互是指不同进程之间进行通信和数据交换的方式。Android提供了多种方法来实现跨进程交互,以下是其中一些常见的方法:
不同的跨进程交互方法适用于不同的场景和需求,模块的开发可能选择不同的方式实现进程间通信,冻结进程如果发生在跨进程的交互行为上,那就可能导致进程间通信的阻塞,从而导致ANR。
ContentProvider 交互异常
如果是常规的一个异步的调用,没有强绑定,不会让调用阻塞,这样的调用不会有什么问题,但是如果是一个同步调用,比如进程 A 调用进程 B 的 ContentProvider接口,进程 B 被冻结了,这个时候进程 A 就会被挂起等待,如果没有处理这种场景,就会有如下类似的 trace:
"main" prio=5 tid=1 Native | group="main" sCount=1 dsCount=0 flags=1 obj=0x7209f5f8 self=0xb40000701e2cd010 | sysTid=7766 nice=-10 cgrp=default sched=0/0 handle=0x71451a54f8 | state=S schedstat=( 2529213544 46180857 3336 ) utm=208 stm=44 core=6 HZ=100 | stack=0x7fdff81000-0x7fdff83000 stackSize=8192KB | held mutexes= native: #00 pc 000000000009a9d4 /apex/com.android.runtime/lib64/bionic/libc.so (__ioctl+4) native: #01 pc 0000000000057ac4 /apex/com.android.runtime/lib64/bionic/libc.so (ioctl+160) native: #02 pc 00000000000519d4 /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+296) native: #03 pc 0000000000052a08 /system/lib64/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+128) native: #04 pc 0000000000052734 /system/lib64/libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+184) native: #05 pc 000000000004b00c /system/lib64/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+152) native: #06 pc 0000000000123d44 /system/lib64/libandroid_runtime.so (android_os_BinderProxy_transact(_JNIEnv*, _jobject*, int, _jobject*, _jobject*, int)+152) at android.os.BinderProxy.transactNative(Native method) at android.os.BinderProxy.transact(BinderProxy.java:540) at android.content.ContentProviderProxy.query(ContentProviderNative.java:470) at android.content.ContentResolver.query(ContentResolver.java:1183) at android.content.ContentResolver.query(ContentResolver.java:1115) at android.content.ContentResolver.query(ContentResolver.java:1071) at com.tencent.open.utils.h.c(ProGuard:5) at com.tencent.open.utils.h.b(ProGuard:1) at com.tencent.open.utils.h.a(ProGuard:2) at com.tencent.open.utils.k.a(ProGuard:3) at com.tencent.open.utils.k.c(ProGuard:1) at com.tencent.tauth.Tencent.isSupportSSOLogin(ProGuard:5) at com.umeng.socialize.handler.UMQQSsoHandler.isInstall(UMQQSsoHandler.java:1) at com.umeng.socialize.a.a.a(SocialRouter.java:34) at com.umeng.socialize.UMShareAPI.isInstall(UMShareAPI.java:2) at com.toscl.*.ui.common.switchplay.SwitchVideo.initViewAndListener(SwitchVideo.java:3) at com.toscl.*.ui.common.switchplay.SwitchVideo.init(SwitchVideo.java:3)
onServiceConnected 交互异常
在比如下述的 ANR 阻塞在 onServiceConnected, Service 绑定传输信息但是对方进程被冻结导致的ANR。
"main" prio=5 tid=1 Native | group="main" sCount=1 dsCount=0 flags=1 obj=0x72c702e0 self=0xdf185410 | sysTid=8513 nice=-10 cgrp=default sched=0/0 handle=0xecb40470 | state=S schedstat=( 3322230460 257625780 4174 ) utm=294 stm=37 core=4 HZ=100 | stack=0xff5c4000-0xff5c6000 stackSize=8192KB | held mutexes= native: #00 pc 00070ac0 /apex/com.android.runtime/lib/bionic/libc.so (__ioctl+8) native: #01 pc 0003f02f /apex/com.android.runtime/lib/bionic/libc.so (ioctl+26) native: #02 pc 00039a13 /system/lib/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+238) native: #03 pc 0003a635 /system/lib/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+32) native: #04 pc 0003a40f /system/lib/libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+122) native: #05 pc 00035267 /system/lib/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+98) native: #06 pc 000c6ae3 /system/lib/libandroid_runtime.so (android_os_BinderProxy_transact(_JNIEnv*, _jobject*, int, _jobject*, _jobject*, int)+82) at android.os.BinderProxy.transactNative(Native method) at android.os.BinderProxy.transact(BinderProxy.java:540) at com.jht.engine.platsign.IKypcService$Stub$Proxy.registerListener(IKypcService.java:266) at com.*.*.util.AiSingSound$1.onServiceConnected(AiSingSound.java:52) - locked (a com.*.*.util.AiSingSound$1) at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1983) at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:2015) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7745) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1009)
这种例子很多,从应用开发的角度而言,如果了解更多的Linux的状态,这样才能更好的定位问题。
对于系统开发,需要找出这些问题的根源,从而解决问题, 比如跨进程会有哪些行为,跨进程的行为状态,通用的特征,比如不管是 ContentProvider 还是 Service 的绑定,都是通过 binder 来实现跨进程传输。
如果通过 binder 直接处理好是不是能解决好问题,在Android 11 之前,内核版本上,binder 驱动还没有直接 FREEZER 状态,
到了 Android 13 之后,binder 驱动已经支持 FREEZER 状态,当应用调用一个被 冻结 binder 进程后,会直接返回一个错误, 这就不会阻塞调用方的进程。
新的API BINDER_FREEZE
相比Android 11, Android 13上会首先会冻结与进程关联的Binder接口,以刷新可能在排队中的Binder事务,如果有 binder 的事务需要处理,将会重新延时(默认10分钟)在执行冻结进程逻辑。
如果在冻结 binder 接口过程中异常就会直接将应用查杀。
如果冻结成功,就会继续执行冻结进程的逻辑,因此不会阻塞 binder 调用方的进程,这样就不会出现上述的 ANR 问题。
// Freeze binder interface before the process, to flush any // transactions that might be pending. try { if (freezeBinder(pid, true) != 0) { rescheduleFreeze(proc, "outstanding txns"); // outstanding_txns 表示是否有需要处理的事务,在下面内核的函数分析中会有涉及。 return; } } catch (RuntimeException e) { Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name); mFreezeHandler.post(() -> { synchronized (mAm) { proc.killLocked("Unable to freeze binder interface", ApplicationExitInfo.REASON_FREEZER, ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true); } }); } long unfreezeTime = opt.getFreezeUnfreezeTime(); try { Process.setProcessFrozen(pid, proc.uid, true); opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis()); opt.setFrozen(true); mFrozenProcesses.put(pid, proc); } catch (Exception e) { Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name); }
/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L1705-L1733
freezeBinder
是一个 native 函数,这个函数会调用 Framework上兼容 BINDER_FREEZE 的接口上。
/** * Informs binder that a process is about to be frozen. If freezer is enabled on a process via * this method, this method will synchronously dispatch all pending transactions to the * specified pid. This method will not add significant latencies when unfreezing. * After freezing binder calls, binder will block all transaction to the frozen pid, and return * an error to the sending process. * * @param pid the target pid for which binder transactions are to be frozen * @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze * binder for the specificed pid. * * @throws RuntimeException in case a flush/freeze operation could not complete successfully. * @return 0 if success, or -EAGAIN indicating there's pending transaction. */ private static native int freezeBinder(int pid, boolean freeze);
/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L739-L753
Framework兼容 BINDER_FREEZE api
Framework 通过 IPCThreadState::freeze
调用到 libbinder 内:
static jint com_android_server_am_CachedAppOptimizer_freezeBinder( JNIEnv *env, jobject clazz, jint pid, jboolean freeze) { jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */); if (retVal != 0 && retVal != -EAGAIN) { jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder"); } return retVal;}
/dev/src/frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp?#L475-L485
这部分 native API 的设计,在2020年提交,用来解决上述的问题,调用方调用被冻结的进程后,会直接给返回异常 , 而不是持续的阻塞。
这个提交的内容如下:
::: tip
binder: adopt BINDER_FREEZE api
binder: adopt BINDER_FREEZE api
The BINDER_FREEZE ioctl and related structures have been added to the
driver to notify binder of the frozen state of a process. This patch
introduces low-level user space handling for libbinder.
Bug: 143717177
Test: verified that binder transactions to a frozen app return ERR_FROZEN
Test: verified that transactions are correctly received by non-frozen apps
Co-developed-by: Todd Kjos tkjos@google.com
Change-Id: I31fed5ecb040f5ba5b8e27ab6a20c441964f32b4
binder: adopt BINDER_FREEZE api
:::
这是一个新增的 ioctl 用来,标记为 BINDER_FREEZE
用来操作 binder 的冻结功能。
在计算机中,ioctl(input/output control)是一个专用于设备输入输出操作的系统调用,该调用传入一个跟设备有关的请求码,系统调用的功能完全取决于请求码。
status_t IPCThreadState::freeze(pid_t pid, bool enable, uint32_t timeout_ms) { struct binder_freeze_info info; int ret = 0; info.pid = pid; info.enable = enable; info.timeout_ms = timeout_ms;#if defined(__ANDROID__) if (ioctl(self()->mProcess->mDriverFD, BINDER_FREEZE, &info) < 0) ret = -errno;#endif // // ret==-EAGAIN indicates that transactions have not drained. // Call again to poll for completion. // return ret;}
/dev/src/frameworks/native/libs/binder/IPCThreadState.cpp?#L1425-L1445
最终这个 native api 通过 ioctrl
会走到内核 binder
的驱动上。
内核新增的 BINDER_FREEZE ioctl特性
::: tip
binder: BINDER_FREEZE ioctl
binder: BINDER_FREEZE ioctl
Frozen tasks can’t process binder transactions, so a way is required to
inform transmitting ends of communication failures due to the frozen
state of their receiving counterparts. Additionally, races are possible
between transitions to frozen state and binder transactions enqueued to
a specific process.
Implement BINDER_FREEZE ioctl for user space to inform the binder driver
about the intention to freeze or unfreeze a process. When the ioctl is
called, block the caller until any pending binder transactions toward
the target process are flushed. Return an error to transactions to
processes marked as frozen.
binder: BINDER_FREEZE ioctl
:::
新增了三个字段
为了实现 binder 的冻结,内核上 binder 的结构体,新增了三个字段。
int outstanding_txns;bool is_dead;bool is_frozen;bool sync_recv;bool async_recv;wait_queue_head_t freeze_wait;
/drivers/android/binder_internal.h?#L414-L419
对调用方的进程冻结
这段代码是一个在 Linux 内核中的 binder 驱动的函数处理部分。这段代码中的函数处理了 BINDER_FREEZE 的 ioctl 请求。
case BINDER_FREEZE: {struct binder_freeze_info info;struct binder_proc **target_procs = NULL, *target_proc;int target_procs_count = 0, i = 0;ret = 0;if (copy_from_user(&info, ubuf, sizeof(info))) {ret = -EFAULT;goto err;}mutex_lock(&binder_procs_lock); // 使用互斥锁 binder_procs_lock 锁住进程列表 binder_procs,以保护其遍历过程。hlist_for_each_entry(target_proc, &binder_procs, proc_node) { // 遍历 binder_procs 链表,对每个进程进行操作。if (target_proc->pid == info.pid) // 如果当前遍历到的进程的 pid 等于传入的 info 结构体中的 pid,则 target_procs_count 增加 1,用于计数符合条件的进程数量。target_procs_count++;}if (target_procs_count == 0) { // 如果没有找到匹配的进程(target_procs_count == 0),则表示传入的 pid 无效,解锁互斥锁并返回 -EINVAL 错误。mutex_unlock(&binder_procs_lock);ret = -EINVAL;goto err;}target_procs = kcalloc(target_procs_count, sizeof(struct binder_proc *), GFP_KERNEL); // 动态分配一个指向 struct binder_proc 指针数组的内存,并将其赋值给 target_procs,用于保存找到的匹配进程的指针。if (!target_procs) {mutex_unlock(&binder_procs_lock);ret = -ENOMEM;goto err;}hlist_for_each_entry(target_proc, &binder_procs, proc_node) { // 第二次遍历if (target_proc->pid != info.pid)continue;binder_inner_proc_lock(target_proc);target_proc->tmp_ref++;binder_inner_proc_unlock(target_proc);target_procs[i++] = target_proc; // 在第二次遍历中,找到匹配进程后,对该进程执行一系列操作:增加 tmp_ref 计数,将进程指针添加到 target_procs 数组中,并对 i 计数器进行递增。}mutex_unlock(&binder_procs_lock);for (i = 0; i = 0)ret = binder_ioctl_freeze(&info, target_procs[i]); // 对 target_procs 数组中的每个进程执行 binder_ioctl_freeze 函数,并将函数的返回值保存在 ret 变量中。同时,对每个进程执行 binder_proc_dec_tmpref 函数,递减其 tmp_ref 计数。binder_proc_dec_tmpref(target_procs[i]);}kfree(target_procs);if (ret < 0)goto err;break;}
/drivers/android/binder.c?#L5439-L5498
以上的核心逻辑,是通过遍历进程,对所有 binder 请求 的target 是当前 pid的进程,进行一个批量的处理。
更新 is_frozen 字段
static int binder_ioctl_freeze(struct binder_freeze_info *info, struct binder_proc *target_proc){int ret = 0;if (!info->enable) { // 如果 info->enable 的值为 0(即禁用冻结),则将目标进程的 is_frozen 字段设置为 false,表示解冻该进程,并直接返回 0。binder_inner_proc_lock(target_proc);target_proc->sync_recv = false;target_proc->async_recv = false;target_proc->is_frozen = false;binder_inner_proc_unlock(target_proc);return 0;}/* * Freezing the target. Prevent new transactions by * setting frozen state. If timeout specified, wait * for transactions to drain. */binder_inner_proc_lock(target_proc);target_proc->sync_recv = false;target_proc->async_recv = false;target_proc->is_frozen = true; // 将目标进程的 is_frozen 字段设置为 true,表示冻结该进程。binder_inner_proc_unlock(target_proc);if (info->timeout_ms > 0) // 如果指定了超时时间,则等待直到该进程的所有事务(transactions)处理完成,或者等待超时。这里使用了 wait_event_interruptible_timeout 函数进行等待ret = wait_event_interruptible_timeout(target_proc->freeze_wait,(!target_proc->outstanding_txns),msecs_to_jiffies(info->timeout_ms));
/drivers/android/binder.c?#L5232-L5261
最终实现将 binder 结构体中的 is_frozen
字段设为 true, 后续的 binder 请求,将通过判断这个字段,实现给调用者返回冻结状态。
冻结的场景
进程冻结是整机性能优化中的关键技术之一,正确应用该技术可以显著提升整机性能。然而,不当使用会带来问题,如应用切换到前台时没有及时解冻可能出现卡顿,影响用户体验,因此在优化时需要慎重考虑。
在国内,应用数量众多,存在优先级和类别的差异。除主流应用外,其他应用占比较小。由于缺乏足够的政策监管,国内开发者采用多种技术手段相互竞争。因此,国内手机厂商针对冻结不仅局限于 CACHE APP,而是对所有应用进行状态模块的拆解。例如,应用在后台需要被冻结,但状态模块的判断会考虑一些特殊情况,如应用中使用桌面小组件、正在播放音频或涉及跨进程行为等。这样极致地使用 CPU 冻结,大幅提升系统性能。
在国内特定环境下,一些主要应用如微信和 QQ 通常被主流厂商直接加入白名单,避免被冻结。因此,国内手机厂商针对冻结场景做了许多优化。
确定何时冻结进程其实是一个复杂的问题,需要维护一个流程图来管理不同的场景。在本文中,我们只是简单介绍了原生的场景。后续我们将发布另一篇文章,专门讲解国内特定场景下的优化策略。
ADJ 更新
在Android Framework中,OomAdjuster
OOM资源管理是Android系统中重要的资源管理机制之一,它负责计算和调整进程的OOM(Out Of Memory)优先级。OOM优先级用于确定在系统内存不足时哪些进程会被杀死以释放内存资源。OomAdjuster
考虑了进程的内存使用情况、可见性、重要性、运行状态和系统角色等因素,计算出每个进程的合适OOM优先级,并将其应用于系统的资源管理。
adj(Adjustment):在Linux内核中,进程的优先级由adj
值来表示,也称为进程优先级值。在Android系统中,OOM优先级通过adj
值来实现。Android系统定义了一系列的adj
值范围,代表不同的OOM优先级。例如,adj
值为-17代表前台进程,adj
值为0代表可见进程,adj
值为6代表服务进程等。系统根据OomAdjuster
计算出的OOM优先级来设置进程的adj
值,从而确定进程在内存不足时被杀死的概率。
调整过程:OomAdjuster
计算出进程的OOM优先级后,会将其映射到对应的adj
值范围。然后,系统根据进程的adj
值来决定哪些进程优先保留,哪些进程应该被终止以释放内存。adj
值越小,表示进程优先级越高,越不容易被杀死。OomAdjuster
的目的就是通过合理的计算和调整,使得系统能够更好地管理资源,提高系统性能和稳定性。
在 OomAdjuster
更新 adj 的时候,会调用 updateAppFreezeStateLSP
函数用来更新应用的冻结状态:
private void updateAppFreezeStateLSP(ProcessRecord app, String oomAdjReason) { if (!mCachedAppOptimizer.useFreezer()) { // 是否使用冻结机制 return; } if (app.mOptRecord.isFreezeExempt()) { // 是否免冻结, 这里追溯过去的话,目前只有判断是否拥有安装器权限 return; } final ProcessCachedOptimizerRecord opt = app.mOptRecord; // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze if (opt.isFrozen() && opt.shouldNotFreeze()) { // 是否已经冻结并且不应该冻结 mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason); return; } final ProcessStateRecord state = app.mState; // Use current adjustment when freezing, set adjustment when unfreezing. if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen( ) && !opt.shouldNotFreeze()) { // 当前 adj 大于最小冻结 adj 并且没有被冻结并且应该被冻结 mCachedAppOptimizer.freezeAppAsyncLSP(app); } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) { mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason); } }
/dev/src/frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java?#L3107-L3131
通过判断冻结功能是否开启、应用是否属于豁免的应用、应用是否已经被冻结、应用是否不应该被冻结。
当做完基础的判断之后,主要通过判断应用当前的 adj 是否大于等于 900 (CACHE_APP) 来决定是否冻结应用,直接然后执行 freezeAppAsyncLSP
走冻结流程。
flush binder transactions 之后
上述如果冻结过程有 binder 事务需要处理的时候哦。
try { if (freezeBinder(pid, true) != 0) { rescheduleFreeze(proc, "outstanding txns"); return; } } catch (RuntimeException e) { Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name); mFreezeHandler.post(() -> { synchronized (mAm) { proc.killLocked("Unable to freeze binder interface", ApplicationExitInfo.REASON_FREEZER, ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true); } }); }
/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L1707-L1721
解冻的场景低内存时内存整理
@GuardedBy({"mService", "mProcLock"}) private void trimMemoryUiHiddenIfNecessaryLSP(ProcessRecord app) { if ((app.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) { // If this application is now in the background and it // had done UI, then give it the special trim level to // have it free UI resources. scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN, "Trimming memory of bg-ui "); app.mProfile.setPendingUiClean(false); } }
/dev/src/frameworks/base/services/core/java/com/android/server/am/AppProfiler.java?#L1095-L1106
在 AppProfiler
中,会在 trimMemoryUiHiddenIfNecessaryLSP
函数中,判断应用是否需要进行内存整理,如果需要的话,会调用 scheduleTrimMemoryLSP
函数来进行内存整理:
@GuardedBy({"mService", "mProcLock"}) private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) { IApplicationThread thread; if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) { try { if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level); } mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app, OOM_ADJ_REASON_NONE); thread.scheduleTrimMemory(level); } catch (RemoteException e) { } } }
/dev/src/frameworks/base/services/core/java/com/android/server/am/AppProfiler.java?#L1108-L1123
在 scheduleTrimMemoryLSP
函数中,会调用 unfreezeTemporarily
函数来解冻应用,然后调用 scheduleTrimMemory
函数来进行内存整理。
// This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout. @GuardedBy("mAm") void unfreezeTemporarily(ProcessRecord app, String reason) { if (mUseFreezer) { synchronized (mProcLock) { if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) { unfreezeAppLSP(app, reason); freezeAppAsyncLSP(app); } } } }
/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L1036-L1047
在 unfreezeTemporarily
函数中,会调用 unfreezeAppLSP
函数来解冻应用,然后调用 freezeAppAsyncLSP
函数来冻结应用, freezeAppAsyncLSP
是一个异步调用,会延迟 10 分钟才执行实际的冻结流程。
dump进程信息时
在 dump 进程信息的时候,会直接调用 enableFreezer
对整个 freezer 进行关闭,这部分逻辑Android11 上是直接调用 native 函数整个关闭,Android 13上就是持有了 mAm
嗯。
在Android 11 的时候,是直接调用 native 的 enableFreezer, 而在Android13上, 在CachedAppOptimizer
内进行了重写,对应用状态的把控更准确,废弃了原来的enableFreezer
native 函数。
public synchronized boolean enableFreezer(boolean enable) { if (!mUseFreezer) { return false; } if (enable) { mFreezerDisableCount--; if (mFreezerDisableCount > 0) { return true; } else if (mFreezerDisableCount 1) { return true; } } // Override is applied immediately, restore is delayed synchronized (mAm) { synchronized (mProcLock) { mFreezerOverride = !enable; Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride); mAm.mProcessList.forEachLruProcessesLOSP(true, process -> { if (process == null) { return; } final ProcessCachedOptimizerRecord opt = process.mOptRecord; if (enable && opt.hasFreezerOverride()) { freezeAppAsyncLSP(process); opt.setFreezerOverride(false); } if (!enable && opt.isFrozen()) { unfreezeAppLSP(process, OomAdjuster.OOM_ADJ_REASON_NONE); // Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag) opt.setFreezerOverride(true); } }); } } return true; }
/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L686-L737
发送和接收广播临时解冻接收广播临时解冻
if (ordered) { r.receiver = filter.receiverList.receiver.asBinder(); r.curFilter = filter; filter.receiverList.curBroadcast = r; r.state = BroadcastRecord.CALL_IN_RECEIVE; if (filter.receiverList.app != null) { // Bump hosting application to no longer be in background // scheduling class. Note that we can't do that if there // isn't an app... but we can only be in that case for // things that directly call the IActivityManager API, which // are already core system stuff so don't matter for this. r.curApp = filter.receiverList.app; filter.receiverList.app.mReceivers.addCurReceiver(r); mService.enqueueOomAdjTargetLocked(r.curApp); mService.updateOomAdjPendingTargetsLocked( OOM_ADJ_REASON_START_RECEIVER); } } else if (filter.receiverList.app != null) { mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app, OOM_ADJ_REASON_START_RECEIVER); }
/dev/src/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java?#L961-L981
发送广播临时解冻
if (sendResult) { if (r.callerApp != null) { mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily( r.callerApp, OOM_ADJ_REASON_FINISH_RECEIVER); } try { if (DEBUG_BROADCAST) { Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] " + r.intent.getAction() + " app=" + r.callerApp); } if (r.dispatchTime == 0) { // The dispatch time here could be 0, in case it's a parallel // broadcast but it has a result receiver. Set it to now. r.dispatchTime = now; }
/dev/src/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java?#L1343-L1357
持有文件锁解冻
@GuardedBy({"mAm"}) @Override public void onBlockingFileLock(int pid) { if (DEBUG_FREEZER) { Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock"); } synchronized (mProcLock) { ProcessRecord app = mFrozenProcesses.get(pid); if (app != null) { Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock"); unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE); } } }
/dev/src/frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java?#L1835-L1848
本文只是从软件开发的角度,简单梳理一下Android freeer 涉及到的模块以及基本概念,本文也在持续的更新中,如果你需要得到最新的更新或者有一些代码高亮,请访问: Android13冻结进程分析:如何提高设备性能和用户体验