Detailed explanation of the principle of app freezer in Android (1): R version

Based on version: Android R

0. Preface

In the previous two blog posts, "App Memory Recycling Optimization in Android (1)" and  "App Memory Recycling Optimization in Android (2)", the process of app memory optimization in Android was analyzed in detail. The management of this mechanism is managed through the CachedAppOptimizer class. Why is it called this name instead of AppCompact? It was also mentioned in the previous two blog posts, because this class also manages an important function: freezer , an optimization for application processes that are in the Cached state for a long time.

This article will continue to analyze another function of the CachedAppOptimizer class, the freezer.

1. Freezer trigger

The OomAdjuster . applyOomAdjLocked() function is analyzed in detail in " Android oom_adj Update Principle (2)" . After oom_adj changes, it will be recomputed and then applied. In this function, updateAppFreezeStateLocked(app) is called in this function to confirm whether the process is frozen.

1.1 updateAppFreezeStateLocked()

frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

    void updateAppFreezeStateLocked(ProcessRecord app) {
        // 确定该功能是使能的
        if (!mCachedAppOptimizer.useFreezer()) {
            return;
        }

        // 如果该进程处于 frozen状态,但sholudNoFreeze变为true,需要解冻
        if (app.frozen && app.shouldNotFreeze) {
            mCachedAppOptimizer.unfreezeAppLocked(app);
        }

        // 如果该进程的 adj处于 CACHED,并且可以冻结,则调用 freezeAppAsync() 冻结
        // 如果该进程的 adj离开 CACHED,则解冻
        if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen && !app.shouldNotFreeze) {
            mCachedAppOptimizer.freezeAppAsync(app);
        } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.frozen) {
            mCachedAppOptimizer.unfreezeAppLocked(app);
        }
    }

Before starting to analyze the freezer, first understand the status of the freezer through the triggered code, as well as the occurrence of freeze and unfreeze.

There are three main parts here:

  • Confirm whether the freezer is enabled;
  • Whether the process status has changed and has been frozen, but the process status has changed to shouldNotFreeze, and needs to be unfrozen at this time. But the logic here is obviously a bit difficult to understand. Unfreeze has been performed, so why continue with the following process? Fortunately, this logical problem is solved in Android S;
  • According to whether the adj of the app is currently in Cached and frozen, determine whether to enter the freeze process or the unfreeze process;

2. CachedAppOptimizer.init()

For the construction call of CachedAppOptimizer and the triggering process of the init() function, you can refer to Sections 1 and 2  of the article "App Memory Recycling Optimization in Android (1)" .

frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java

    public void init() {
        ...
        synchronized (mPhenotypeFlagLock) {
            ...
            updateUseFreezer();
        }
    }

2.1 updateUseFreezer()

    private void updateUseFreezer() {
        // 获取settings 中属性 cached_apps_freezer 的值,根据属性值初始化变量mUseFreezer
        final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),
                Settings.Global.CACHED_APPS_FREEZER_ENABLED);

        if ("disabled".equals(configOverride)) {
            mUseFreezer = false;
        } else if ("enabled".equals(configOverride)
                || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                    KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
            mUseFreezer = isFreezerSupported();
        }

        if (mUseFreezer && mFreezeHandler == null) {
            Slog.d(TAG_AM, "Freezer enabled");
            enableFreezer(true);

            if (!mCachedAppOptimizerThread.isAlive()) {
                mCachedAppOptimizerThread.start();
            }

            mFreezeHandler = new FreezeHandler();

            Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
                    Process.THREAD_GROUP_SYSTEM);
        } else {
            enableFreezer(false);
        }
    }

First, confirm whether the freezer function is enabled. It will be clearer to use a flow chart to illustrate:

When the freezer is enabled:

  • Call  enableFreezer() to enable it. For detailed process, please see Section 4 ;
  • If the ServiceThread in CachedAppOptimizer is not started, start it;
  • Create a free handler to handle freezer-related messages;
  • Set the priority of ServiceThread to THREAD_GROUP_SYSTEM;

3. Introduction to cgroups

cgroups (full name: control groups ) is a mechanism provided by the Linux kernel that can limit the resources used by a single process or multiple processes, and can achieve refined control of resources such as CPU and memory. Docker, a lightweight container that is becoming more and more popular at present, uses the resource restriction capability provided by cgroups to complete resource control of CPU, memory and other departments.

cgroups defines a subsystem for each controllable resource. Typical subsystems are as follows:

  • cpu: mainly limits the cpu usage of the process;
  • cpuaat: can count cpu usage reports of processes in cgroups;
  • cpuset: You can allocate separate cpu nodes or memory nodes to processes in cgroups;
  • memory: You can limit the memory usage of the process;
  • blkio: block device io that can limit processes;
  • devices: You can control the process' ability to access certain devices;
  • freezer: can suspend or resume processes in cgroups;
  • net_cls: Network packets of processes in cgroups can be marked, and then the tc (traffic control) module can be used to control the packets;
  • ns: allows processes under different cgroups to use different namespaces;

In Android Q or higher, the cgroup abstraction layer is used through task profiles. Task profiles can be used to describe the restrictions of a set or sets that apply to a thread or process. The system selects one or more appropriate cgroups as specified in the task profiles. This restriction allows changes to the underlying cgroup feature set to be made without affecting higher software layers.

For specific details, please check the blog post: "Detailed Explanation of the cgroup Abstraction Layer in Android"

Many important features of this article are accomplished through profiles.

4. enableFreezer()

The main thing is to call the enableFreezerInternal() function:

    private static native void enableFreezerInternal(boolean enable);
frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp

static void com_android_server_am_CachedAppOptimizer_enableFreezerInternal(
        JNIEnv *env, jobject clazz, jboolean enable) {
    bool success = true;

    if (enable) {
        success = SetTaskProfiles(0, {"FreezerEnabled"}, true);
    } else {
        success = SetTaskProfiles(0, {"FreezerDisabled"}, true);
    }

    if (!success) {
        jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
    }
}

Write a specific value to the corresponding node  through the interface SetTaskProfiles() . For details, you can view the blog post: "Detailed Explanation of the cgroup Abstraction Layer in Android"

5. freezeAppAsync()

The parameter is of ProcessRecord type, which is the corresponding process.

    void freezeAppAsync(ProcessRecord app) {
        mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);

        mFreezeHandler.sendMessageDelayed(
                mFreezeHandler.obtainMessage(
                    SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
                FREEZE_TIMEOUT_MS);
    }

Notice,

  • If the process has already sent a freeze request, when sending the request again, the original message will be canceled first;
  • Send message SET_FROZEN_PROCESS_MSG to request freeze;
  • The delay length of message processing is FREEZE_TIMEOUT_MS (10 min). If the message that freezes the process has not been canceled after 10 minutes, the process of freezing the process will be entered;

5.1 freeze message processing

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SET_FROZEN_PROCESS_MSG:
                    freezeProcess((ProcessRecord) msg.obj);
                    break;
                case REPORT_UNFREEZE_MSG:
                    int pid = msg.arg1;
                    int frozenDuration = msg.arg2;
                    String processName = (String) msg.obj;

                    reportUnfreeze(pid, frozenDuration, processName);
                    break;
                default:
                    return;
            }
        }

There are two messages for the freezer. The REPORT_UNFREEZE_MSG message is recorded after the unfreeze.

This article focuses on the message processing of freeze. Here we see that the freezeProcess() function is finally called . See the next section for details.

5.2 freezeProcess()

        private void freezeProcess(ProcessRecord proc) {
            final int pid = proc.pid;
            final String name = proc.processName;
            final long unfrozenDuration;
            final boolean frozen;

            try {
                // 确认进程是否存在任意的文件锁,避免不必要的 free/unfreeze操作
                //   这是为了防止冻结进程持有文件锁,从而引起死锁
                //   冻结成功之后,还会再次确认文件锁,如果有锁,则立即解冻
                if (Process.hasFileLocks(pid)) {
                    if (DEBUG_FREEZER) {
                        Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
                    }
                    return;
                }
            } catch (Exception e) {
                Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
                        + "): " + e);
                return;
            }

            // 使用ASM做锁,在S 版本中换成专一的锁
            synchronized (mAm) {
                // 如果进程没有变成Cached或者不能freeze,则退出此次freeze操作
                if (proc.curAdj < ProcessList.CACHED_APP_MIN_ADJ
                        || proc.shouldNotFreeze) {
                    return;
                }

                // 已经处于frozen,或者不是一个应用进程,则退出此次 freeze操作
                //   pid为0,有可能进程还没有launch完成,或者进程被kill了
                if (pid == 0 || proc.frozen) {
                    return;
                }

                long unfreezeTime = proc.freezeUnfreezeTime;

                // 核心函数setProcessFrozen(),同步冻结进程
                try {
                    Process.setProcessFrozen(pid, proc.uid, true);

                    proc.freezeUnfreezeTime = SystemClock.uptimeMillis();
                    proc.frozen = true;
                } catch (Exception e) {
                    Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
                }

                unfrozenDuration = proc.freezeUnfreezeTime - unfreezeTime;
                frozen = proc.frozen;
            }

            if (!frozen) {
                return;
            }

            //此次freeze记录到event log
            EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);

            // 这里逻辑应该是颠倒了,先冻结进程,再去冻结binder,逻辑上不通
            //   在 S 版本中已经将freeBinder提前到 process冻结之前
            try {
                freezeBinder(pid, true);
            } catch (RuntimeException e) {
                Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                proc.kill("Unable to freeze binder interface",
                        ApplicationExitInfo.REASON_OTHER,
                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
            }

            // See above for why we're not taking mPhenotypeFlagLock here
            if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
                FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
                        FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
                        pid,
                        name,
                        unfrozenDuration);
            }

            try {
                // 再次check文件锁,如果该冻结进程持有文件锁,立即unfreeze
                if (Process.hasFileLocks(pid)) {
                    synchronized (mAm) {
                        unfreezeAppLocked(proc);
                    }
                }
            } catch (Exception e) {
                Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
                synchronized (mAm) {
                    unfreezeAppLocked(proc);
                }
            }
        }

5.2.1 setProcessFrozen()

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

    public static final native void setProcessFrozen(int pid, int uid, boolean frozen);
frameworks/base/core/jni/android_util_Process.cpp

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

The call here has been analyzed in the  blog post "cgroup Abstraction Layer" . Through the interface SetProcessProfiles(), it is specifically a profile of SetCgroupAction  type, and finally calls ExecuteForProcess():

system/core/libprocessgroup/task_profiles.cpp
 
bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
    std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
    if (tmp_fd < 0) {
        PLOG(WARNING) << "Failed to open " << procs_path;
        return false;
    }
    if (!AddTidToCgroup(pid, tmp_fd)) {
        LOG(ERROR) << "Failed to add task into cgroup";
        return false;
    }
 
    return true;
}

Through the function, first obtain the path that needs to be modified for the profile through the GetProcsFilePath() interface of the Controller. The parameter is the Path configured for the profile:

system/core/libprocessgroup/cgroup_map.cpp
std::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid,
                                               pid_t pid) const {
    std::string proc_path(path());
    proc_path.append("/").append(rel_path);
    proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
    proc_path = regex_replace(proc_path, std::regex("<pid>"), std::to_string(pid));
 
    return proc_path.append(CGROUP_PROCS_FILE);
}

The final file written is CGROUP_PROCS_FILE, which is  the cgroup.procs  file.

6. unfreezeAppLocked()

    void unfreezeAppLocked(ProcessRecord app) {
        // 首先,取消之前该进程的冻结请求
        mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);

        // 如果进程还没有冻结,则无需做解冻处理
        if (!app.frozen) {
            return;
        }

        /********进程处于冻结,进行解冻处理*********/
        boolean processKilled = false;

        // 冻住的进程可以接收异步binder请求,但是不会处理,只是放入binder buffer, 过多的请求会导致buffer耗尽;
        // 这里需要确认下该进程在解冻之前,进程是否在冰冻期间收到同步的binder 请求,有则kill该进程
        try {
            int freezeInfo = getBinderFreezeInfo(app.pid);

            if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
                        + " received sync transactions while frozen, killing");
                app.kill("Sync transaction while in frozen state",
                        ApplicationExitInfo.REASON_OTHER,
                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
                processKilled = true;
            }

            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
                        + " received async transactions while frozen");
            }
        } catch (Exception e) {
            Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " "
                    + app.processName + ". Killing it. Exception: " + e);
            app.kill("Unable to query binder frozen stats",
                    ApplicationExitInfo.REASON_OTHER,
                    ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
            processKilled = true;
        }

        //进程被kill 了,退出unfreeze
        if (processKilled) {
            return;
        }

        // app.freezeUnfreezeTime记录的是上次free、unfreeze的时间
        long freezeTime = app.freezeUnfreezeTime;

        try {
            freezeBinder(app.pid, false);
        } catch (RuntimeException e) {
            Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName
                    + ". Killing it");
            app.kill("Unable to unfreeze",
                    ApplicationExitInfo.REASON_OTHER,
                    ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
            return;
        }

        try {
            Process.setProcessFrozen(app.pid, app.uid, false);

            app.freezeUnfreezeTime = SystemClock.uptimeMillis();
            app.frozen = false;
        } catch (Exception e) {
            Slog.e(TAG_AM, "Unable to unfreeze " + app.pid + " " + app.processName
                    + ". This might cause inconsistency or UI hangs.");
        }

        if (!app.frozen) {
            if (DEBUG_FREEZER) {
                Slog.d(TAG_AM, "sync unfroze " + app.pid + " " + app.processName);
            }

            mFreezeHandler.sendMessage(
                    mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
                        app.pid,
                        (int) Math.min(app.freezeUnfreezeTime - freezeTime, Integer.MAX_VALUE),
                        app.processName));
        }
    }

The core processing function is setProcessFrozen(), and the detailed process has been analyzed in Section 5.2.1 above. The main thing to note is unfrozen in task_profiles.json:

    {
      "Name": "Frozen",
      "Actions": [
        {
          "Name": "JoinCgroup",
          "Params":
          {
            "Controller": "freezer",
            "Path": ""
          }
        }
      ]
    },
    {
      "Name": "Unfrozen",
      "Actions": [
        {
          "Name": "JoinCgroup",
          "Params":
          {
            "Controller": "freezer",
            "Path": "../"
          }
        }
      ]
    },

Different from Frozen, Unfrozen's Path is the upper-level directory, that is, the final modification is sys/fs/cgroup/cgroup.procs , while frozen modifies sys/fs/cgroup/freezer/cgroup.procs

At this point, everything about the freezer in the Android R version is complete.

From the logical analysis of this article, we can see that there are still some logical problems on Android R. Through this article, we have a rough understanding of the general process of the freezer and the interface for using the cgroup abstraction layer. However, because the version is unstable, we will not analyze the implementation in Kernel too much here. .

Finally, here is a journey map of the frozen app:

 

 

Related blog posts:

https://justinwei.blog.csdn.net/article/details/131974011

https://justinwei.blog.csdn.net/article/details/131854291 

https://justinwei.blog.csdn.net/article/details/131769953

https://justinwei.blog.csdn.net/article/details/131591931

https://justinwei.blog.csdn.net/article/details/131717028

https://justinwei.blog.csdn.net/article/details/131685304

https://justinwei.blog.csdn.net/article/details/131595511

Guess you like

Origin blog.csdn.net/jingerppp/article/details/131845360