Explicação detalhada do princípio do app freezer no Android (1): versão R

Baseado na versão: Android R

0. Prefácio

Nas duas postagens anteriores do blog, "Otimização de reciclagem de memória de aplicativos no Android (1)" e  "Otimização de reciclagem de memória de aplicativos no Android (2)", o processo de otimização de memória de aplicativos no Android foi analisado detalhadamente. O gerenciamento deste mecanismo é gerenciado através da classe CachedAppOptimizer . Por que é chamado assim em vez de AppCompact? Também foi mencionado nos dois posts anteriores do blog, pois esta classe também gerencia uma função importante: freezer , uma otimização para processos de aplicativos que ficam no estado Cache há muito tempo.

Este artigo continuará analisando outra função da classe CachedAppOptimizer, o freezer.

1. Gatilho do congelador

A função OomAdjuster.applyOomAdjLocked () é analisada em detalhes em " Princípio de atualização do Android oom_adj (2)" . Após as alterações de oom_adj, ele será recalculado e aplicado. Nesta função, updateAppFreezeStateLocked(app) é chamado nesta função para confirmar se o processo está congelado.

1.1 atualizaçãoAppFreezeStateLocked()

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

Antes de começar a analisar o freezer, primeiro entenda o estado do freezer através do código acionado, bem como a ocorrência de congelamento e descongelamento.

Existem três partes principais aqui:

  • Confirme se o freezer está habilitado;
  • Se o status do processo mudou e foi congelado, mas o status do processo mudou para shouldNotFreeze e precisa ser descongelado neste momento. Mas a lógica aqui é obviamente um pouco difícil de entender. O descongelamento foi executado, então por que continuar com o processo a seguir? Felizmente, esse problema lógico foi resolvido no Android S;
  • Dependendo se o ajuste do aplicativo está atualmente em cache e congelado, determine se deseja entrar no processo de congelamento ou no processo de descongelamento;

2.CachedAppOptimizer.init()

Para a chamada de construção de CachedAppOptimizer e o processo de acionamento da função init(), você pode consultar as Seções 1 e 2  do artigo "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 atualizaçãoUseFreezer()

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

Primeiro, confirme se a função freezer está habilitada. Ficará mais claro usar um fluxograma para ilustrar:

Quando o freezer está ativado:

  • Chame  enableFreezer() para habilitá-lo. Para o processo detalhado, consulte a Seção 4 ;
  • Se o ServiceThread no CachedAppOptimizer não for iniciado, inicie-o;
  • Crie um manipulador gratuito para lidar com mensagens relacionadas ao freezer;
  • Defina a prioridade de ServiceThread como THREAD_GROUP_SYSTEM;

3. Introdução aos cgroups

cgroups (nome completo: grupos de controle ) é um mecanismo fornecido pelo kernel do Linux que pode limitar os recursos usados ​​​​por um único processo ou vários processos e pode obter controle refinado de recursos como CPU e memória. Docker, um contêiner leve que está se tornando cada vez mais popular atualmente, usa a capacidade de restrição de recursos fornecida por cgroups para completar o controle de recursos de CPU, memória e outros departamentos.

cgroups define um subsistema para cada recurso controlável.Os subsistemas típicos são os seguintes:

  • CPU: limita principalmente o uso da CPU do processo;
  • cpuaat: pode contar relatórios de uso de CPU de processos em cgroups;
  • cpuset: Você pode alocar nós de CPU ou nós de memória separados para processos em cgroups;
  • memória: você pode limitar o uso de memória do processo;
  • blkio: dispositivo de bloqueio io que pode limitar processos;
  • dispositivos: você pode controlar a capacidade do processo de acessar determinados dispositivos;
  • freezer: pode suspender ou retomar processos em cgroups;
  • net_cls: Pacotes de rede de processos em cgroups podem ser marcados e então o módulo tc (controle de tráfego) pode ser usado para controlar os pacotes;
  • ns: permite que processos em diferentes cgroups usem diferentes namespaces;

No Android Q ou superior, a camada de abstração cgroup é usada por meio de perfis de tarefas. Os perfis de tarefas podem ser usados ​​para descrever as restrições de um conjunto ou conjuntos que se aplicam a um thread ou processo. O sistema seleciona um ou mais cgroups apropriados conforme especificado nos perfis de tarefas. Esta restrição permite que alterações no conjunto de recursos do cgroup subjacente sejam feitas sem afetar camadas superiores de software.

Para obter detalhes específicos, verifique a postagem do blog: "Explicação detalhada da camada de abstração cgroup no Android"

Muitos recursos importantes deste artigo são realizados por meio de perfis.

4.  ativarFreezer()

O principal é chamar a função enableFreezerInternal():

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

Escreva um valor específico para o nó correspondente  por meio da interface SetTaskProfiles() . Para obter detalhes, você pode ver a postagem do blog: "Explicação detalhada da camada de abstração cgroup no Android"

5. congelarAppAsync()

O parâmetro é do tipo ProcessRecord, que é o processo correspondente.

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

Perceber,

  • Caso o processo já tenha enviado uma solicitação de congelamento, ao enviar novamente a solicitação, a mensagem original será cancelada primeiro;
  • Enviar mensagem SET_FROZEN_PROCESS_MSG para solicitar congelamento;
  • O atraso no processamento da mensagem é FREEZE_TIMEOUT_MS (10 min), caso a mensagem que congela o processo não tenha sido cancelada após 10 minutos, será iniciado o processo de congelamento do processo;

5.1 congelar processamento de mensagens

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

Existem duas mensagens para o freezer: A mensagem REPORT_UNFREEZE_MSG é gravada após o descongelamento.

Este artigo se concentra no processamento de mensagens de congelamento. Aqui vemos que a função freezeProcess() é finalmente chamada . Consulte a próxima seção para obter detalhes.

5.2 congelarProcesso()

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

A chamada aqui foi analisada na  postagem do blog "cgroup Abstraction Layer" . Através da interface SetProcessProfiles(), é especificamente um perfil do tipo SetCgroupAction  e finalmente chama 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;
}

Através da função, primeiro obtenha o caminho que precisa ser modificado para o perfil através da interface GetProcsFilePath() do Controller. O parâmetro é o Caminho configurado para o perfil:

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

O arquivo final gravado é CGROUP_PROCS_FILE, que é  o arquivo cgroup.procs  .

6. descongelarAppLocked()

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

A função principal de processamento é setProcessFrozen(), e o processo detalhado foi analisado na Seção 5.2.1 acima. A principal coisa a observar é o descongelamento em task_profiles.json:

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

Diferente de Frozen, o Caminho do Unfrozen é o diretório de nível superior, ou seja, a modificação final é sys/fs/cgroup/cgroup.procs , enquanto frozen modifica sys/fs/cgroup/freezer/cgroup.procs

Neste ponto, tudo sobre o freezer na versão Android R está completo.

A partir da análise lógica deste artigo, podemos ver que ainda existem alguns problemas lógicos no Android R. Através deste artigo, temos uma compreensão aproximada do processo geral do freezer e da interface para usar a camada de abstração do cgroup. No entanto, como a versão é instável, não analisaremos muito a implementação no Kernel aqui.

Finalmente, aqui está um mapa da jornada do aplicativo congelado:

 

 

Postagens de blog relacionadas:

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

Acho que você gosta

Origin blog.csdn.net/jingerppp/article/details/131845360
Recomendado
Clasificación