Optimización del reciclaje de memoria de aplicaciones en Android (1): versión R

Versión basada en: Android R

0. Prefacio

Android Q ha agregado una solución de optimización del reciclaje de memoria de aplicaciones del lado del marco. Cuando el oom adj de la aplicación cambia específicamente, el lado del marco procesará la memoria de la aplicación. A medida que evoluciona la versión, esta parte del trabajo de optimización se ha ido mejorando, el autor analizará en detalle el proceso de optimización de esta parte para Android R y Android S respectivamente.

Este artículo es para Android R.

Nota: La palabra "compresión" mencionada en este artículo en realidad se refiere a la optimización del reciclaje de memoria, porque solo cuando se revela la lógica exacta queda claro si se trata de un reciclaje de páginas anónimas o de un reciclaje de páginas de archivos. Antes de eso, lo determinamos tentativamente. tan compacto tratar con.

1. Clase CachedAppOptimizer

La gestión de la compresión de memoria en el lado de la aplicación se completa en la clase CachedAppOptimizer.

En publicaciones de blog anteriores, aprendimos "Principio de actualización de oom_adj (1)" y "Principio de actualización de oom_adj (2)" que AMS administra la actualización, el cálculo y la aplicación de oom_adj a través de la clase OomAdjuster. También se crea una instancia de un objeto CachedAppOptimizer en OomAdjuster:

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

    OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
            ServiceThread adjusterThread) {
        mService = service;
        ...
        mCachedAppOptimizer = new CachedAppOptimizer(mService);
        ...
    }

Los parámetros son objetos AMS.

Echemos un vistazo a  la estructura de CachedAppOptimizer :

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

    public CachedAppOptimizer(ActivityManagerService am) {
        this(am, null, new DefaultProcessDependencies());
    }

    @VisibleForTesting
    CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback,
            ProcessDependencies processDependencies) {
        mAm = am;
        mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
            Process.THREAD_GROUP_SYSTEM, true);
        mProcStateThrottle = new HashSet<>();
        mProcessDependencies = processDependencies;
        mTestCallback = callback;
    }

Se crea un ServiceThread en la construcción con el nombre CachedAppOptimizerThread y la prioridad THREAD_GROUP_SYSTEM .

mProcessDependencies es un objeto de tipo DefaultProcessDependencies, utilizado para el procesamiento de compresión final.

2. inicio()

La función init() se activa llamando a la función installSystemProviders() en AMS cuando se llama a SystemServer.startOtherServices().mOomAdjuster.initSettings() en installSystemProviders():

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

    void initSettings() {
        mCachedAppOptimizer.init();
        ...
    }

Echemos un vistazo a la función init():

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

    public void init() {
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
        synchronized (mPhenotypeFlagLock) {
            updateUseCompaction();
            updateCompactionActions();
            updateCompactionThrottles();
            updateCompactStatsdSampleRate();
            updateFreezerStatsdSampleRate();
            updateFullRssThrottle();
            updateFullDeltaRssThrottle();
            updateProcStateThrottle();
            updateUseFreezer();
        }
    }

La función primero establece un oyente de escucha para la variable de atributo de DeviceConfig. Cuando el atributo cuyo espacio de nombres es Activity_Manager en DeviceConfig cambia, se volverá a llamar a la función mOnFlagsChangedListener().

Luego hay un montón de funciones de actualización para inicializar algunas variables miembro. Escojamos algunas para el análisis.

2.1 actualizarUseCompaction()

    private void updateUseCompaction() {
        mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);

        if (mUseCompaction && mCompactionHandler == null) {
            if (!mCachedAppOptimizerThread.isAlive()) {
                mCachedAppOptimizerThread.start();
            }

            mCompactionHandler = new MemCompactionHandler();

            Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
                    Process.THREAD_GROUP_SYSTEM);
        }
    }

Primero obtenga el atributo use_compaction, el valor predeterminado es DEFAULT_USE_COMPACTION ( falso ):

    @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;

Si user_compation está habilitado, se creará mCompactionHandler para el procesamiento de mensajes asincrónicos y se iniciará el ServiceThread creado en el constructor.

Echemos un vistazo a la clase MemCompactionHandler:

    private final class MemCompactionHandler extends Handler {
        private MemCompactionHandler() {
            super(mCachedAppOptimizerThread.getLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case COMPACT_PROCESS_MSG: {
                    ...
                    break;
                }
                case COMPACT_SYSTEM_MSG: {
                    ...
                    break;
                }
            }
        }

Looper utiliza Looper de ServiceThread, que se utiliza principalmente para procesar memoria de proceso comprimida o memoria de proceso del sistema.

2.2 actualizarCompactionActions()

    private void updateCompactionActions() {
        int compactAction1 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);

        int compactAction2 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);

        mCompactActionSome = compactActionIntToString(compactAction1);
        mCompactActionFull = compactActionIntToString(compactAction2);
    }

Principalmente para confirmar las acciones ejecutadas durante una compresión parcial o total. Por defecto:

  • algunos toman DEFAULT_COMPACT_ACTION_1 ( archivo );
  • tomas completas DEFAULT_COMPACT_ACTION_2 ( todos );
    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;

Posteriormente nos basaremos en estas dos variables en la elección de la compresión, la estrategia de compresión final es:

  • La acción pendiente es ALGUNA, comprimida usando mCompactActionSome;
  • La acción pendiente es COMPLETA / PERSISTENTE  BFGS, comprimida usando mCompactActionFull;
  • el sistema está comprimido en todos los modos;

Consulte la Sección 4 para obtener una lógica detallada .

2.3 actualizarCompactionThrottles()

    private void updateCompactionThrottles() {
        boolean useThrottleDefaults = false;
        String throttleSomeSomeFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_1);
        String throttleSomeFullFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_2);
        String throttleFullSomeFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_3);
        String throttleFullFullFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_4);
        String throttleBFGSFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_5);
        String throttlePersistentFlag =
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                    KEY_COMPACT_THROTTLE_6);

        if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
                || TextUtils.isEmpty(throttleFullSomeFlag)
                || TextUtils.isEmpty(throttleFullFullFlag)
                || TextUtils.isEmpty(throttleBFGSFlag)
                || TextUtils.isEmpty(throttlePersistentFlag)) {
            // Set defaults for all if any are not set.
            useThrottleDefaults = true;
        } else {
            try {
                mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
                mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
                mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
                mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
                mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag);
                mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag);
            } catch (NumberFormatException e) {
                useThrottleDefaults = true;
            }
        }

        if (useThrottleDefaults) {
            mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
            mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
            mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
            mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
            mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
            mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
        }
    }

El código es relativamente simple, obtenga el atributo DeviceConfig KEY_COMPACT_THROTTLE_ 1 ~ 6 ;

Si una de las propiedades no está configurada, se utilizan todos los valores predeterminados:

    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_5 = 10 * 60 * 1000;
    @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000;

Se utiliza para comparar la diferencia de tiempo entre el último tiempo de compresión y esta compresión.Para evitar solicitudes de compresión continuas , si las solicitudes de compresión continua se realizan dentro de un tiempo limitado, el sistema no lo permite y el código regresa directamente.

2.4 actualizarFullRssThrottle()

    private void updateFullRssThrottle() {
        mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);

        // Don't allow negative values. 0 means don't apply the throttle.
        if (mFullAnonRssThrottleKb < 0) {
            mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
        }
    }

Cuando se trata de una solicitud anónima o una solicitud completa, debe considerar el límite restante de la página anónima . Cuando es inferior a este límite, la compresión y el reciclaje no continuarán.

Además, se puede ver lógicamente que este valor límite no puede ser un número negativo. Si DeviceConfig establece KEY_COMPACT_FULL_RSS_THROTTLE_KB en un número negativo, significa que el valor predeterminado de 12M se utiliza como valor límite.

2.5 actualizarFullDeltaRssThrottle()

    private void updateFullDeltaRssThrottle() {
        mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);

        if (mFullDeltaRssThrottleKb < 0) {
            mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
        }
    }

Igual que 2.4, cuando se trata de una solicitud anónima o una solicitud completa, habrá otra restricción. Es decir, comparar el RSS después de la última compresión con el RSS solicitado esta vez. Si solo hay un pequeño cambio de memoria entre ellos, significa que la demanda de memoria no es grande ni urgente, y el proceso de compresión de memoria no continuará.

De manera similar, este valor no puede ser un número negativo. Si DeviceConfig establece KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB  en un número negativo, significa usar el valor predeterminado de 8M como valor límite.

2.6 actualizarProcStateThrottle()

    private void updateProcStateThrottle() {
        String procStateThrottleString = DeviceConfig.getString(
                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE,
                DEFAULT_COMPACT_PROC_STATE_THROTTLE);
        if (!parseProcStateThrottle(procStateThrottleString)) {
            ...
            if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) {
                ...
            }
        }
    }

    private boolean parseProcStateThrottle(String procStateThrottleString) {
        String[] procStates = TextUtils.split(procStateThrottleString, ",");
        mProcStateThrottle.clear();
        for (String procState : procStates) {
            try {
                mProcStateThrottle.add(Integer.parseInt(procState));
            } catch (NumberFormatException e) {
                Slog.e(TAG_AM, "Failed to parse default app compaction proc state: "
                        + procState);
                return false;
            }
        }
        return true;
    }

Restricciones del estado del proceso, cuando el proceso está en el estado de mProcStateThrottle , no se realiza ningún reciclaje.

La colección de mProcStateThrottle proviene del atributo KEY_COMPACT_PROC_STATE_THROTTLE en DeviceConfig. El valor de este atributo especifica el estado límite del proceso, que puede ser múltiple, separado por comas. Si el valor del atributo no se puede analizar, se utilizará el valor de estado predeterminado y el valor límite del estado de proceso predeterminado es  PROCESS_STATE_RECEIVER .

2.7 actualizarUseFreezer()

El estado de congelación del proceso se inicializa y la optimización de la congelación se analizará por separado más adelante.

3. Iniciador de optimización de la compresión.

La optimización de la compresión se activa en varias situaciones. En CachedAppOptimizer, los iniciadores de optimización de la compresión incluyen principalmente:

  • compactAppSome()
  • aplicación compactaCompleta()
  • compactAppPersistent()
  • compactAppBfgs()
  • compactoTodoSistema()

Esta sección analiza el uso de cada función y los escenarios de activación.

3.1 compactAppSome()

Activado en OomAdjuster.ApplyOomAdjLocked () :

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

    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
            long nowElapsed) {
        ...
 
        // 在bootup 阶段不压缩
        // useCompaction() 是app compact是否使能
        if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
            // 如果跟上次的adj 不一样,有变化
            if (app.curAdj != app.setAdj) {
                // 当app从perceptible变为home/previous,执行some等级内存压缩
                // 当app变成cached,执行full等级内存压缩
                if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
                        (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
                                app.curAdj == ProcessList.HOME_APP_ADJ)) {
                    mCachedAppOptimizer.compactAppSome(app);
                } else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
                                || app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
                        && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
                        && app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
                    mCachedAppOptimizer.compactAppFull(app);
                }
            } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE  //非唤醒状态
                    && app.setAdj < ProcessList.FOREGROUND_APP_ADJ
                    && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
                //处于非唤醒状态,且上一次重要性高于前台进程adj,
                //  且上一次压缩的时间已经尝过10min或者上次没压缩都返回true,
                //  进行persistent 级别压缩
                mCachedAppOptimizer.compactAppPersistent(app);
            } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
                    && app.getCurProcState()
                        == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
                    && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
                // 非唤醒状态,且当前进程状态为绑定一个前台service进程
                //   且上一次压缩的时间已经超过10min或上一次没压缩都返回true
                mCachedAppOptimizer.compactAppBfgs(app);
            }
        }

        ...
 
        return success;
    }

Cuando la función de compactación de la aplicación está habilitada y el sistema completa el inicio:

  • Cuando cambia el ajuste de habitación:
    • Cuando el último adj (setAdj) <= PERCEPTIBLE_APP_ADJ, se convierte en  PREVIOUS_APP_ADJHOME_APP_ADJ, use  compactAppSome() para optimizar la compresión;
    • Cuando el último adj (setAdj) no es adj almacenado en caché, se convierte en adj almacenado en caché y  compactAppFull() se usa para optimizar la compresión;
  • Cuando esté en el estado de no despertar, y cuando el último ajuste (setAdj) es más importante que el ajuste de primer plano ( FOEGROUND_APP_ADJ ), y el intervalo desde la última compresión es de más de 10 minutos o nunca se ha comprimido, use la compresión compactAppPersistent() mejoramiento;
  • Cuando esté en el estado no activado y el estado del proceso de la aplicación sea  PROCESS_STATE_BOUND_FOREGROUND_SERVICE y el intervalo desde la última compresión sea superior a 10 minutos o nunca se haya comprimido, utilice la  optimización de la compresión compactAppBfgs() ;

Echemos un vistazo a lo que hace compactAppSome() :

    void compactAppSome(ProcessRecord app) {
        synchronized (this) {
            app.reqCompactAction = COMPACT_PROCESS_SOME;
            if (!app.mPendingCompact) {
                app.mPendingCompact = true;
                mPendingCompactionProcesses.add(app);
                mCompactionHandler.sendMessage(
                        mCompactionHandler.obtainMessage(
                        COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
            }
        }
    }
  • Especifique la acción de compresión solicitada por el proceso como  COMPACT_PROCESS_SOME ;
  • Si el proceso no está comprimido, prepárese para ingresar:
    • Especifique mPendingCompact como verdadero para marcar la entrada al estado de compresión;
    • Agregue el proceso a mPendingCompactionProcesses y, después de recibir los mensajes posteriores, los procesos que deben comprimirse se recuperarán desde aquí;
    • Envíe el mensaje COMPACT_PROCESS_MSG al hilo donde se encuentra mCompactionHandler, con los parámetros app.setAdj y app.setProcState;

3.2 aplicación compactaCompleta()

Igual que la Sección 3.1 , también se activa en OomAdjuster.ApplyOomAdjLocked () .

    void compactAppFull(ProcessRecord app) {
        synchronized (this) {
            app.reqCompactAction = COMPACT_PROCESS_FULL;
            if (!app.mPendingCompact) {
                app.mPendingCompact = true;
                mPendingCompactionProcesses.add(app);
                mCompactionHandler.sendMessage(
                        mCompactionHandler.obtainMessage(
                        COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
            }
        }
    }

El código es similar a compactAppSome(), aquí la acción compacta se reemplaza por  COMPACT_PROCESS_FULL .

3.3 compactAppPersistent()

Igual que la Sección 3.1 , también se activa en OomAdjuster.ApplyOomAdjLocked () .

    void compactAppPersistent(ProcessRecord app) {
        synchronized (this) {
            app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
            if (!app.mPendingCompact) {
                app.mPendingCompact = true;
                mPendingCompactionProcesses.add(app);
                mCompactionHandler.sendMessage(
                        mCompactionHandler.obtainMessage(
                        COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
            }
        }
    }

El código es similar a compactAppSome(), aquí la acción compacta se reemplaza por  COMPACT_PROCESS_PERSISTENT .

3.4 compactaAppBfgs()

Igual que la Sección 3.1 , también se activa en OomAdjuster.ApplyOomAdjLocked () .

    void compactAppBfgs(ProcessRecord app) {
        synchronized (this) {
            app.reqCompactAction = COMPACT_PROCESS_BFGS;
            if (!app.mPendingCompact) {
                app.mPendingCompact = true;
                mPendingCompactionProcesses.add(app);
                mCompactionHandler.sendMessage(
                        mCompactionHandler.obtainMessage(
                        COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
            }
        }
    }

El código es similar a compactAppSome(), aquí la acción compacta se reemplaza por  COMPACT_PROCESS_BFGS .

3.5 compactoTodoSistema()

La activación de esta función es diferente de las cuatro anteriores y se divide en dos partes:

  • Cuando se llama a FinishBooting () en AMS, todos los procesos en el sistema se comprimen completamente después de que se completa el inicio del sistema ;
  • MountServiceIdler llama a AMS.performIdleMaintenance() cada vez que startJob() para realizar la compresión del sistema;
    void compactAllSystem() {
        if (mUseCompaction) {
            mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
                                              COMPACT_SYSTEM_MSG));
        }
    }

Envíe  el mensaje COMPACT_SYSTEM_MSG al hilo donde se encuentra mCompactionHandler.

4. Procesamiento de mensajes comprimidos

En la sección anterior, se analizó el proceso de activación de la optimización de la compresión. Al final, las instrucciones de optimización de la compresión se completan enviando mensajes a  mCompactionHandler . Esta sección analiza el flujo de procesamiento de diferentes mensajes por  mCompactionHandler .

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case COMPACT_PROCESS_MSG: {
                    // 记录此次压缩处理的起始时间
                    long start = SystemClock.uptimeMillis();
                    ProcessRecord proc;
                    int pid;
                    String action;
                    final String name;
                    int pendingAction, lastCompactAction;
                    long lastCompactTime;
                    LastCompactionStats lastCompactionStats;
                    // 获取消息带过来的参数
                    int lastOomAdj = msg.arg1;
                    int procState = msg.arg2;
                    synchronized (CachedAppOptimizer.this) {
                        // 取出队列中的第一个进程
                        proc = mPendingCompactionProcesses.remove(0);

                        // 获取压缩 action
                        pendingAction = proc.reqCompactAction;
                        // 获取进程pid
                        pid = proc.mPidForCompact;
                        // 获取进程名
                        name = proc.processName;
                        // 进程进入压缩,该标记可以置回false,等待下一次压缩
                        proc.mPendingCompact = false;

                        // 对于 SOME 和 FULL 的action,如果进程的setAdj 小于可感知,不做处理
                        // 我们从applyOomAdjLocked() 得知,SOME 只针对HOME/PREVIOUS adj,
                        //   而FULL 只针对 cached adj 的进程
                        if ((pendingAction == COMPACT_PROCESS_SOME
                                || pendingAction == COMPACT_PROCESS_FULL)
                                && (proc.mSetAdjForCompact <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
                            return;
                        }

                        // 获取上一次压缩的信息
                        lastCompactAction = proc.lastCompactAction;
                        lastCompactTime = proc.lastCompactTime;
                        lastCompactionStats = mLastCompactionStats.get(pid);
                    }

                    // 不接受pid 为0的进程
                    if (pid == 0) {
                        // not a real process, either one being launched or one being killed
                        return;
                    }

                    // basic throttling
                    // use the Phenotype flag knobs to determine whether current/prevous
                    // compaction combo should be throtted or not

                    // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
                    // should very seldom change, and taking the risk of using the wrong action is
                    // preferable to taking the lock for every single compaction action.

                    // 开始一些限制条件的过滤
                    // 限制条件1,应用已经执行过 compaction,确认与上一次压缩的时间间隔是否在限制内
                    if (lastCompactTime != 0) {
                        if (pendingAction == COMPACT_PROCESS_SOME) {
                            if ((lastCompactAction == COMPACT_PROCESS_SOME
                                    && (start - lastCompactTime < mCompactThrottleSomeSome))
                                    || (lastCompactAction == COMPACT_PROCESS_FULL
                                        && (start - lastCompactTime
                                                < mCompactThrottleSomeFull))) {
                                return;
                            }
                        } else if (pendingAction == COMPACT_PROCESS_FULL) {
                            if ((lastCompactAction == COMPACT_PROCESS_SOME
                                    && (start - lastCompactTime < mCompactThrottleFullSome))
                                    || (lastCompactAction == COMPACT_PROCESS_FULL
                                        && (start - lastCompactTime
                                                < mCompactThrottleFullFull))) {
                                return;
                            }
                        } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
                            if (start - lastCompactTime < mCompactThrottlePersistent) {
                                return;
                            }
                        } else if (pendingAction == COMPACT_PROCESS_BFGS) {
                            if (start - lastCompactTime < mCompactThrottleBFGS) {
                                return;
                            }
                        }
                    }

                    switch (pendingAction) {
                        case COMPACT_PROCESS_SOME:
                            action = mCompactActionSome;
                            break;
                        // For the time being, treat these as equivalent.
                        case COMPACT_PROCESS_FULL:
                        case COMPACT_PROCESS_PERSISTENT:
                        case COMPACT_PROCESS_BFGS:
                            action = mCompactActionFull;
                            break;
                        default:
                            action = COMPACT_ACTION_NONE;
                            break;
                    }

                    // 限制条件2,action 不能为 NONE
                    if (COMPACT_ACTION_NONE.equals(action)) {
                        return;
                    }

                    // 限制条件3,进程的procState 不能在限制的state 集合内
                    if (mProcStateThrottle.contains(procState)) {
                        return;
                    }

                    long[] rssBefore = mProcessDependencies.getRss(pid);
                    long anonRssBefore = rssBefore[2];

                    // 限制条件4,确定RSS是否不正常
                    if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
                            && rssBefore[3] == 0) {
                        return;
                    }

                    // 限制条件5,对于FULL 压缩,或者匿名页压缩
                    // 对于RSS 使用小于 12MB 的进程,不做压缩处理;
                    // 对于上一次压缩时 RSS 与此次压缩时RSS,相差小于 8MB 的进程,不做压缩处理
                    if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
                        if (mFullAnonRssThrottleKb > 0L
                                && anonRssBefore < mFullAnonRssThrottleKb) {
                            return;
                        }

                        if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
                            long[] lastRss = lastCompactionStats.getRssAfterCompaction();
                            long absDelta = Math.abs(rssBefore[1] - lastRss[1])
                                    + Math.abs(rssBefore[2] - lastRss[2])
                                    + Math.abs(rssBefore[3] - lastRss[3]);
                            if (absDelta <= mFullDeltaRssThrottleKb) {
                                return;
                            }
                        }
                    }

                    /*************限制条件全部通过,进入compact 流程**********************/

                    // 更新压缩计数
                    switch (pendingAction) {
                        case COMPACT_PROCESS_SOME:
                            mSomeCompactionCount++;
                            break;
                        case COMPACT_PROCESS_FULL:
                            mFullCompactionCount++;
                            break;
                        case COMPACT_PROCESS_PERSISTENT:
                            mPersistentCompactionCount++;
                            break;
                        case COMPACT_PROCESS_BFGS:
                            mBfgsCompactionCount++;
                            break;
                        default:
                            break;
                    }
                    try {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
                                + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
                                + ": " + name);
                        long zramFreeKbBefore = Debug.getZramFreeKb();

                        // 压缩处理的核心函数 performCompaction()
                        mProcessDependencies.performCompaction(action, pid);

                        // 压缩完成之后,进行一些数据的记录
                        long[] rssAfter = mProcessDependencies.getRss(pid);
                        long end = SystemClock.uptimeMillis();
                        long time = end - start;
                        long zramFreeKbAfter = Debug.getZramFreeKb();
                        EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
                                rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
                                rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
                                rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
                                lastCompactAction, lastCompactTime, lastOomAdj, procState,
                                zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
                        // Note that as above not taking mPhenoTypeFlagLock here to avoid locking
                        // on every single compaction for a flag that will seldom change and the
                        // impact of reading the wrong value here is low.
                        if (mRandom.nextFloat() < mCompactStatsdSampleRate) {
                            FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPACTED, pid, name,
                                    pendingAction, rssBefore[0], rssBefore[1], rssBefore[2],
                                    rssBefore[3], rssAfter[0], rssAfter[1], rssAfter[2],
                                    rssAfter[3], time, lastCompactAction, lastCompactTime,
                                    lastOomAdj, ActivityManager.processStateAmToProto(procState),
                                    zramFreeKbBefore, zramFreeKbAfter);
                        }
                        synchronized (CachedAppOptimizer.this) {
                            proc.lastCompactTime = end;
                            proc.lastCompactAction = pendingAction;
                        }
                        if (action.equals(COMPACT_ACTION_FULL)
                                || action.equals(COMPACT_ACTION_ANON)) {
                            // Remove entry and insert again to update insertion order.
                            mLastCompactionStats.remove(pid);
                            mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
                        }
                    } catch (Exception e) {
                        // nothing to do, presumably the process died
                    } finally {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    }
                    break;
                }
                case COMPACT_SYSTEM_MSG: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
                    compactSystem();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                }
            }
        }
    }

Aunque hay mucho código, la lógica es relativamente clara. Consulte los comentarios en el código para conocer el proceso detallado. Hay dos puntos principales a tener en cuenta aquí.

Primer punto, la selección final de la estrategia de compresión es:

  • La acción pendiente es ALGUNA, comprimida usando mCompactActionSome;
  • La acción pendiente es COMPLETA / PERSISTENTE  BFGS, comprimida usando mCompactActionFull;
  • el sistema está comprimido en todos los modos;

Segundo punto, hay dos formas principales de procesar la compresión final:

  • mProcessDependencies.performCompaction();
  • sistema compacto();

4.1 realizarCompactación()

        public void performCompaction(String action, int pid) throws IOException {
            try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) {
                fos.write(action.getBytes());
            }
        }

Como puede ver desde aquí, el núcleo de la compresión es el controlador Process_reclaim, que escribe la acción correspondiente en el nodo /proc/PID/reclaim y el controlador realiza el procesamiento de compresión más importante.

4.2 sistema compacto()

Esta es una función nativa:

frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp

static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) {
    std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
    struct dirent* current;
    while ((current = readdir(proc.get()))) {
        if (current->d_type != DT_DIR) {
            continue;
        }

        // don't compact system_server, rely on persistent compaction during screen off
        // in order to avoid mmap_sem-related stalls
        if (atoi(current->d_name) == getpid()) {
            continue;
        }

        std::string status_name = StringPrintf("/proc/%s/status", current->d_name);
        struct stat status_info;

        if (stat(status_name.c_str(), &status_info) != 0) {
            // must be some other directory that isn't a pid
            continue;
        }

        // android.os.Process.FIRST_APPLICATION_UID
        if (status_info.st_uid >= 10000) {
            continue;
        }

        std::string reclaim_path = StringPrintf("/proc/%s/reclaim", current->d_name);
        WriteStringToFile(std::string("all"), reclaim_path);
    }
}

Hay mucha lógica y, al final, todo se escribe en /proc/PID/reclaim para todos los nodos y se comprime de acuerdo con el método all;

5. controlador Process_Reclaim

fs/proc/internal.h

extern const struct file_operations proc_reclaim_operations;
fs/proc/base.c

#ifdef CONFIG_PROCESS_RECLAIM
	REG("reclaim", 0222, proc_reclaim_operations),
#endif

El sistema de archivos proc agrega un nodo más y la operación correspondiente es proc_reclaim_operaciones

fs/proc/task_mmu.c

const struct file_operations proc_reclaim_operations = {
	.write		= reclaim_write,
	.llseek		= noop_llseek,
};

función de procesamiento de escritura reclaim_write():

fs/proc/task_mmu.c

num reclaim_type {
	RECLAIM_FILE,
	RECLAIM_ANON,
	RECLAIM_ALL,
	RECLAIM_RANGE,
};

static ssize_t reclaim_write(struct file *file, const char __user *buf,
				size_t count, loff_t *ppos)
{
	struct task_struct *task;
	char buffer[200];
	struct mm_struct *mm;
	struct vm_area_struct *vma;
	enum reclaim_type type;
	char *type_buf;
	unsigned long start = 0;
	unsigned long end = 0;
	const struct mm_walk_ops reclaim_walk_ops = {
		.pmd_entry = reclaim_pte_range,
	};

	memset(buffer, 0, sizeof(buffer));
	if (count > sizeof(buffer) - 1)
		count = sizeof(buffer) - 1;

	if (copy_from_user(buffer, buf, count))
		return -EFAULT;

	type_buf = strstrip(buffer);
	if (!strcmp(type_buf, "file"))
		type = RECLAIM_FILE;
	else if (!strcmp(type_buf, "anon"))
		type = RECLAIM_ANON;
	else if (!strcmp(type_buf, "all"))
		type = RECLAIM_ALL;
	else if (isdigit(*type_buf))
		type = RECLAIM_RANGE;
	else
		goto out_err;

	if (type == RECLAIM_RANGE) {
		char *token;
		unsigned long long len, len_in, tmp;

		token = strsep(&type_buf, " ");
		if (!token)
			goto out_err;
		tmp = memparse(token, &token);
		if (tmp & ~PAGE_MASK || tmp > ULONG_MAX)
			goto out_err;
		start = tmp;

		token = strsep(&type_buf, " ");
		if (!token)
			goto out_err;
		len_in = memparse(token, &token);
		len = (len_in + ~PAGE_MASK) & PAGE_MASK;
		if (len > ULONG_MAX)
			goto out_err;
		/*
		 * Check to see whether len was rounded up from small -ve
		 * to zero.
		 */
		if (len_in && !len)
			goto out_err;

		end = start + len;
		if (end < start)
			goto out_err;
	}

	task = get_proc_task(file->f_path.dentry->d_inode);
	if (!task)
		return -ESRCH;

	mm = get_task_mm(task);
	if (!mm)
		goto out;

	down_read(&mm->mmap_sem);
	if (type == RECLAIM_RANGE) {
		vma = find_vma(mm, start);
		while (vma) {
			if (vma->vm_start > end)
				break;
			if (is_vm_hugetlb_page(vma))
				continue;

			walk_page_range(mm, max(vma->vm_start, start),
					min(vma->vm_end, end),
					&reclaim_walk_ops, vma);
			vma = vma->vm_next;
		}
	} else {
        //遍历当前进程所占用的虚拟地址
		for (vma = mm->mmap; vma; vma = vma->vm_next) {
			if (is_vm_hugetlb_page(vma))
				continue;

            // anon 压缩的,只关心匿名页
			if (type == RECLAIM_ANON && vma->vm_file)
				continue;

            //file 压缩的,只关心文件页
			if (type == RECLAIM_FILE && !vma->vm_file)
				continue;

            // 遍历页表,并调用回调函数 reclaim_walk_ops() 处理
			walk_page_range(mm, vma->vm_start, vma->vm_end,
					&reclaim_walk_ops, vma);
		}
	}

	flush_tlb_mm(mm);
	up_read(&mm->mmap_sem);
	mmput(mm);
out:
	put_task_struct(task);
	return count;

out_err:
	return -EINVAL;
}

La lógica es relativamente simple: el valor pasado al nodo desde la capa superior puede ser:

  • archivo
  • luego
  • todo
  • otro

Los tipos de reclamo correspondientes en el controlador son:

  • RECLAIM_FILE
  • RECLAIM_ANON
  • RECLAIM_ALL
  • RECLAIM_RANGE

Obtenga la task_struct del proceso a través de get_proc_task(), y luego obtenga la mm_struct del proceso, y luego obtenga el vma del proceso. Al atravesar el vma, procese el archivo y anon del proceso en consecuencia, y finalmente llame a walk_page_range() .Los parámetros de la función:

  • Se especifica el proceso mm_struct;
  • inicio y fin de vma;
  • mm_walk_ops y reclaim_walk_ops;

walk_page_range() eventualmente volverá a llamar a reclaim_walk_ops.pmd_entry, es decir, reclaim_pte_range() :

fs/proc/task_mmu.c

#ifdef CONFIG_PROCESS_RECLAIM
static int reclaim_pte_range(pmd_t *pmd, unsigned long addr,
				unsigned long end, struct mm_walk *walk)
{
	struct vm_area_struct *vma = walk->private;
	pte_t *pte, ptent;
	spinlock_t *ptl;
	struct page *page;
	LIST_HEAD(page_list);
	int isolated;

	split_huge_pmd(vma, addr, pmd);
	if (pmd_trans_unstable(pmd))
		return 0;
cont:
	isolated = 0;
	pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
	for (; addr != end; pte++, addr += PAGE_SIZE) {
		ptent = *pte;
		if (!pte_present(ptent))
			continue;

		page = vm_normal_page(vma, addr, ptent);
		if (!page)
			continue;

		if (isolate_lru_page(compound_head(page)))
			continue;

		/* MADV_FREE clears pte dirty bit and then marks the page
		 * lazyfree (clear SwapBacked). Inbetween if this lazyfreed page
		 * is touched by user then it becomes dirty.  PPR in
		 * shrink_page_list in try_to_unmap finds the page dirty, marks
		 * it back as PageSwapBacked and skips reclaim. This can cause
		 * isolated count mismatch.
		 */
		if (PageAnon(page) && !PageSwapBacked(page)) {
			putback_lru_page(page);
			continue;
		}

		list_add(&page->lru, &page_list);
		inc_node_page_state(page, NR_ISOLATED_ANON +
				page_is_file_lru(page));
		isolated++;
		if (isolated >= SWAP_CLUSTER_MAX)
			break;
	}
	pte_unmap_unlock(pte - 1, ptl);
	reclaim_pages_from_list(&page_list, vma);
	if (addr != end)
		goto cont;

	cond_resched();
	return 0;
}

Finalmente llame a la función reclaim_pages_from_list():

mm/vmscan.c

#ifdef CONFIG_PROCESS_RECLAIM
unsigned long reclaim_pages_from_list(struct list_head *page_list,
			struct vm_area_struct *vma)
{
	struct scan_control sc = {
		.gfp_mask = GFP_KERNEL,
		.priority = DEF_PRIORITY,
		.may_writepage = 1,
		.may_unmap = 1,
		.may_swap = 1,
		.target_vma = vma,
	};

	unsigned long nr_reclaimed;
	struct reclaim_stat stat;
	struct page *page;

	list_for_each_entry(page, page_list, lru)
		ClearPageActive(page);

	nr_reclaimed = shrink_page_list(page_list, NULL, &sc,
			TTU_IGNORE_ACCESS, &stat, true);

	while (!list_empty(page_list)) {
		page = lru_to_page(page_list);
		list_del(&page->lru);
		dec_node_page_state(page, NR_ISOLATED_ANON +
				page_is_file_lru(page));
		putback_lru_page(page);
	}

	return nr_reclaimed;
}
#endif

Para obtener información detallada sobre Shrink_page_list (), consulte la Sección 4 del artículo "Explicación detallada de Shrink_list" .

Otros parches de recuperación de procesos no se enumeran aquí. Para obtener más información, puede hacer clic aquí para descargarlos.

 

Hasta ahora, se ha completado todo el análisis de la optimización del reciclaje de aplicaciones en la versión de Android R. Aquí hay un resumen:

  • Utilice CachedAppOptimizer para administrar la compactación de aplicaciones y activar la interfaz compacta de las siguientes maneras principales:
    • Cuando adj cambia, si el último adj <= PERCEPTIBLE_APP_ADJ se convierte en ANTERIOR o INICIO, use ALGUNA optimización de compresión;
    • Cuando adj cambia, si no estaba en CACHÉ la última vez, se convierte en CACHÉ y se utiliza la optimización de compresión COMPLETA;
    • Cuando esté en el estado de no despertar, y cuando el último ajuste (setAdj) es más importante que el ajuste de primer plano ( FOEGROUND_APP_ADJ ), y el intervalo desde la última compresión es de más de 10 minutos o nunca se ha comprimido, use la   compresión compactAppPersistent() mejoramiento;
    • Cuando esté en el estado no activado y el estado del proceso de la aplicación sea  PROCESS_STATE_BOUND_FOREGROUND_SERVICE y el intervalo desde la última compresión sea superior a 10 minutos o nunca se haya comprimido, utilice la  optimización de la compresión compactAppBfgs()  ;
    • Cuando se llama a FinishBooting () en AMS, es decir, una vez que se completa el inicio del sistema, todos los procesos en el sistema se comprimen completamente y se llama a compactAllSystem () ;
    • MountServiceIdler llama a AMS , performIdleMaintenance() y compactAllSystem() cada vez que startJob() realiza la compresión del sistema;
  • CachedAppOptimizer mantiene un ServiceThread para procesar los mensajes enviados después de llamar a compact;
  • La compresión que no es del sistema requiere muchas restricciones antes de poder ingresar oficialmente a la función de procesamiento de compresión;
  • Las acciones compactas se fusionarán antes de la compresión. ALGUNAS usan el método de compresión mCompactActionSome y FULL / PERSISTENT / BFGS usan el método de compresión mCompactActionFull;
  • Puntos de función de procesamiento de compresión:
    • performCompaction(), escribe el nodo /proc/PID/reclaim, requiere soporte de controlador;
    • compactSystem(), esta es una llamada nativa y, en última instancia, escribe el nodo /proc/PID/reclaim;
    • Android S comenzó a implementar funciones de procesamiento de compresión de forma nativa;

Con respecto a la optimización de la compresión de la versión Andoid S, hay muchos cambios, consulte la siguiente publicación del blog .

Supongo que te gusta

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