This is the third Android background kill series, the first two have been to the background kill notes, kill recovery mechanism to do analysis, this is the main explanation of the Android background kill principle. The principle of LowMemoryKiller is relatively simple compared to background kill recovery, and you can find a lot of information on the Internet. However, due to the different versions of Android in the framework layer implementation is somewhat different, the online analysis is also for a version of Android, this article simply made the following differences and comparisons. LowMemoryKiller is a multi-level oomKiller extended by Andorid based on oomKiller principle. OomKiller (Out Of MemoryKiller) is a multi-level oomKiller extended by Andorid based on oomKiller principle. OomKiller (Out Of MemoryKiller) is a multi-level oomKiller extended by Andorid based on oomKiller principle. LowMemoryKiller is a memory reclamation mechanism triggered by the memory threshold. When the available memory is low, LowMemoryKiller will selectively kill processes, which is more flexible than OOMKiller. How would you design a LowMemoryKiller? What functional modules would such a system require?

  • Process priority definition: Only with priority can you decide who to kill first and who to kill later
  • Dynamic process priority management: the priority of a process should not be fixed, but should change dynamically according to its changes. For example, the priority of a foreground process must be reduced when it switches to the background
  • When do processes need to be killed? When do processes need to be killed? When do processes need to be killed
  • How to kill

The above questions are the basic functions required by a MemoryKiller module. Android is based on the Linux kernel, and its process management is based on the Linux kernel. LowMemoryKiller is also located in the kernel module accordingly, which means that the user space is not visible to the background killing. AMS has no idea whether an APP has been killed by the background, and only when AMS wakes up the APP does it know whether the APP has been killed by LowMemoryKiller. In fact, the principle of LowmemoryKiller is very clear, first take a look at the overall flow chart, and then analyze step by step:

App operations affect the process priority

Two things to keep in mind:

  1. LowMemoryKiller is a passive kill process
  2. Android applications use AMS to update process information using the Proc file system

Android application process priority and oomAdj

Android tries to keep applications alive as long as possible, but in order to build or run more important processes, it may be necessary to remove older processes to reclaim memory. When selecting a process to Kill, the system evaluates the “importance” of the process based on its running status. The trade-off is based on four main components. If memory needs to be reduced, the system first eliminates the least important processes, then the less important ones, and so on, to reclaim system resources. In Android, there are 5 levels of APP progression (from Google Docs) : There are 5 levels of APP importance in Android:

  • Foreground Process
  • Visible Process
  • Service Process
  • Background Processes
  • Empty Processes

Foreground process

Processes that are necessary for the user’s current operation. A process is considered a foreground process if it meets any of the following criteria:

  • Contains activities that are interacting (resumed
  • Contains services bound to the Activity you are interacting with
  • Contains Service running “foreground” (Service called startForeground())
  • Contains a Service (onCreate(), onStart(), or onDestroy()) that is performing a lifecycle callback
  • Contains a BroadcastReceiver that is executing its onReceive() method

Typically, there are not many foreground processes at any given time. The system terminates them only when it is absolutely necessary that there is not enough memory to support them while still running. At this point, the device is usually paging out of memory, so some foreground processes need to be terminated to ensure that the user interface responds properly.

Visible process

A process that does not have any foreground components but still affects what the user sees on the screen. A process is considered visible if it meets any of the following criteria:

  • Contains activities (whose onPause() method has been called) that are not in the foreground but are still visible to the user. For example, this might happen if the foreground Activity starts a dialog box that allows the previous Activity to be displayed after it.
  • Contains services bound to visible (or foreground) activities.

Visible processes are considered extremely important and will not be terminated unless necessary to keep all foreground processes running at the same time.

Service process

A process that is running a service started with the startService() method and does not belong to either of the higher categories of processes described above. Although server processes are not directly related to what the user sees, they are usually performing some action that the user cares about (for example, playing music in the background or downloading data from the network). Therefore, unless there is not enough memory to keep all foreground and visible processes running at the same time, the system will leave the server processes running.

Background processes

The process that contains an Activity that is currently invisible to the user (the Activity’s onStop() method has been called). These processes have no direct impact on the user experience, and the system may terminate them at any time to reclaim memory for foreground, visible, or server processes. There are usually many background processes running, so they are saved in the LRU (Least Recently used) list to ensure that the process containing the Activity the user recently viewed is the last to terminate. If an Activity implements the lifecycle method correctly and saves its current state, terminating its process does not have a significant impact on the user experience, because the Activity resumes all its visible states when the user navigates back to the Activity. You can refer to the previous two articles on save and restore state, or abnormal kill recovery.

An empty process

A process that does not contain any active application components. The only purpose of keeping such processes is to be used as a cache to reduce the startup time needed to run components in them the next time. This is known as a hot start. To balance system resources between the process cache and the underlying kernel cache, systems often kill these processes.

Android rates a process as the highest level it can be based on the importance of the currently active components in the process. For example, if a process hosts services and visible activities, it is rated as a visible process, not a service process. In addition, the level of a process may be increased by the dependence of other processes on it, that is, a process that serves another process is never ranked lower than the process it serves. For example, if A content provider in process A provides A service to A client in process B, or if A service in process A is bound to A component in process B, process A is always considered at least as important as process B.

Here, we further subdivide the importance of different processes, define the importance level of ADJ, and store the priority in the process structure of kernel space. For LowmemoryKiller’s reference:

ADJ priority priority Corresponding to the scene
UNKNOWN_ADJ 16 It usually means that the process will be cached and cannot obtain a definite value
CACHED_APP_MAX_ADJ 15 Maximum ADJ for invisible processes (invisible processes can be killed at any time)
CACHED_APP_MIN_ADJ 9 Minimum ADJ for invisible processes (Invisible processes can be killed at any time)
SERVICE_B_AD 8 Services in B List (older, less likely to be used)
PREVIOUS_APP_ADJ 7 APP_A jumps to APP_B. If APP_A is not visible,A is PREVIOUS_APP_ADJ.
HOME_APP_ADJ 6 The Home process
SERVICE_ADJ 5 Service Process
HEAVY_WEIGHT_APP_ADJ 4 Background heavyweight process, set in the system/rootdir/init.rc file
BACKUP_APP_ADJ 3 Backup process (unknown)
PERCEPTIBLE_APP_ADJ 2 Perceivable processes, such as background music playback
>VISIBLE_APP_ADJ 1 Visible process (Visible, but does not get focus, e.g. the new process has only one floating Activity, Visible Process)
FOREGROUND_APP_ADJ 0 Foreground process (APP being displayed, has interactive interface, Foreground Process)
PERSISTENT_SERVICE_ADJ – 11 Associated with systems or persistent processes
PERSISTENT_PROC_ADJ – 12 System persistent processes, such as phone calls
SYSTEM_ADJ – 16 System processes
NATIVE_ADJ – 17 Native processes (not managed by the system)

The purpose of this introduction is only one point: The Android application process has a priority, and its priority depends on whether there is a display interface and whether it can be perceived by the user. The more perceived by the user, the higher the priority of the application (system process is not considered).

How are Android app priorities updated

Lowmemorykiller traverses the process structure queue of the kernel and selects the one with the lower priority to kill. How does the APP operation write to the kernel space? Linxu has a distinction between users and kernel space. Both apps and system services run in user space. Strictly speaking, the operation of user controls cannot directly affect the kernel space, let alone change the priority of the process. Actually here is passed a proc file system of Linux, the/proc file system is mapped to the kernel space can simply to see many user can operate on the file system, is not, of course, all processes have the right operation, through the proc file system, user space will be able to modify the process of kernel space data, such as modifying process priority, In Android family, before 5.0, the system is directly modified by AMS process. After 5.0, the operation of modifying priority is encapsulated into an independent service – LMKD. LMKD service is located in user space, and its function level is similar to AMS and WMS, which is a common system service. Let’s take a look at the code before 5.0. Here we still use 4.3 source code to look at the simulation of a scenario, the APP only has one Activity, we actively finish the Activity, the APP is back to the background, remember that although there is no Activity available, However, the APP itself is not dead, which is called hot launch. Let’s take a look at the general process:

App operations affect the process priority

Now go directly to AMS source code:

ActivityManagerService

public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) {
     ...
    synchronized(this) {

        final long origId = Binder.clearCallingIdentity();
        boolean res = mMainStack.requestFinishActivityLocked(token, resultCode,
                resultData, "app-request".true); . }}Copy the code

The initial process is similar to startActivity. It starts by suspending the Activity that is currently resumed, which is itself.

  final boolean finishActivityLocked(ActivityRecord r, int index, int resultCode,
            Intent resultData, String reason, boolean immediate, boolean oomAdj) {
         ...
            if (mPausingActivity == null) {
                if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r);
                if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false");
                startPausingLocked(false.false); }... }Copy the code

If the current APP’s Activity stack is empty, you can return to the previous APP or desktop application. If the current APP’s Activity stack is empty, you can return to the previous APP or desktop application. Note the difference between startActivity and waking up the APP that is about to return to the background.

ActivityStack

 private final void completePauseLocked() {
    ActivityRecord prev = mPausingActivity;

    if(prev ! =null) {
        if (prev.finishing) {
        1, different points <! - take the initiative to finish the walk is the branch of state transform details, please contact your code - > prev = finishCurrentActivityLocked (prev FINISH_AFTER_VISIBLE,false); }...2And the sameif(! mService.isSleeping()) { resumeTopActivityLocked(prev); }Copy the code

1 is different from startActivity’s completePauseLocked, and active finish’s prev.finishing is true. Therefore performs finishCurrentActivityLocked branch, add the Activity of the current pause to mStoppingActivities queue, and wake up the next need to go to the front desk of the Activity, after wake up, will continue to perform the stop:

    private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r,
            int index, int mode, boolean oomAdj) {
        if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) {
            if(! mStoppingActivities.contains(r)) { mStoppingActivities.add(r); . }... return r; }... }Copy the code

So let’s go back to resumeTopActivityLocked, and if resume calls back completeResumeLocked, stop, This function calls the activityIdleInternal function back and forth by sending an IDLE_TIMEOUT_MSG message to Handler. Finally, destroyActivityLocked destroys the ActivityRecord.

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { ... if (next.app ! =null&& next.app.thread ! =null) {... Try {... next.app.thread.scheduleResumeActivity(next.appToken, mService.isNextTransitionForward()); . .try {
                next.visible = true; completeResumeLocked(next); }... }Copy the code

When destroying an Activity, if the current APP’s Activity stack is empty, it means that the current Activity has no visible interface. In this case, you need to dynamically update the APP’s priority.

 final boolean destroyActivityLocked(ActivityRecord r,
            boolean removeFromApp, boolean oomAdj, String reason) {
            ...
       if (hadApp) {
            if (removeFromApp) {
                // Delete from ProcessRecord but not from history
                int idx = r.app.activities.indexOf(r);
                if (idx >= 0) { r.app.activities.remove(idx); }... if (r.app.activities.size() ==0) {
                    // No longer have activities, so update oom adj.mService.updateOomAdjLocked(); . }Copy the code

Finally, AMS’s updateOomAdjLocked function is called to update the Process’s priority. In the 4.3 source code, the priority is set primarily through the setOomAdj function of the Process class:

ActivityManagerService

private final boolean updateOomAdjLocked(ProcessRecord app, int hiddenAdj, int clientHiddenAdj, int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) { ... ComputeOomAdjLocked (APP, hiddenAdj, clientHiddenAdj, emptyAdj, TOP_APP,false, doingAll); . <! -- If not, set a new OomAdj-->if(app.curAdj ! = app.setAdj) {if (Process.setOomAdj(app.pid, app.curAdj)) {
        ...
}Copy the code

SetOomAdj in Process is a native method, prototype in android_util_process.cpp

android_util_Process.cpp

jboolean android_os_Process_setOomAdj(JNIEnv* env, jobject clazz,
                                      jint pid, jint adj)
{
#ifdef HAVE_OOM_ADJ
    char text[64];
    sprintf(text, "/proc/%d/oom_adj", pid);
    int fd = open(text, O_WRONLY);
    if (fd >= 0) {
        sprintf(text, "%d", adj);
        write(fd, text, strlen(text));
        close(fd);
    }
    return true;
#endif
    return false;
}Copy the code

In native code, the proc file system is used to modify the kernel information. In this case, the proc file system is used to dynamically update the priority of the process. Here is the flow chart of 4.3 updating oomAdj, note the red execution points:

LowMemoryKiller Update process oomAdj

Android5.0 after the framework layer implementation: LMKD service

Android5.0 encapsulates the entry for setting the process priority as a separate service, the LMKD service. Instead of accessing the proc file system directly, AMS does this through the LMKD service, seeing the configuration of the service from the init.rc file.

service lmkd /system/bin/lmkd
    class core
    critical
    socket lmkd seqpacket 0660 system systemCopy the code

As can be seen from the configuration, the service communicates with other processes through socket. In fact, AMS sends a request to LMKD service through socket, asking LMKD to update the priority of the process. After receiving the request, LMKD updates the priority of the process in the kernel through /proc file system. Let’s take a look at what changes have been made to AMS in 5.0. In fact, most of the flow is similar to the previous 4.3 source code

ActivityManagerService

private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP, boolean doingAll, long now) { ... computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now); . applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime()); } private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now, long nowElapsed) { boolean success =true;

    if(app.curRawAdj ! = app.setRawAdj) { app.setRawAdj = app.curRawAdj; } int changes =0; The difference between1
    if(app.curAdj ! = app.setAdj) { ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
                "Set " + app.pid + "" + app.processName + " adj " + app.curAdj + ":"
                + app.adjType);
        app.setAdj = app.curAdj;
        app.verifiedAdj = ProcessList.INVALID_ADJ;
    }Copy the code

The ProcessList class is used to set oomAdj. The socket is used to communicate with the LMKD service, and the LMKD service is passed the LMK_PROCPRIO command to update the process priority.

public static final void setOomAdj(int pid, int uid, int amt) {
    if (amt == UNKNOWN_ADJ)
        return;
   long start = SystemClock.elapsedRealtime();
    ByteBuffer buf = ByteBuffer.allocate(4 * 4);
    buf.putInt(LMK_PROCPRIO);
    buf.putInt(pid);
    buf.putInt(uid);
    buf.putInt(amt);
    writeLmkd(buf);
    long now = SystemClock.elapsedRealtime();
      }    

private static void writeLmkd(ByteBuffer buf) {
         for (int i = 0; i < 3; i++) {
        if (sLmkdSocket == null) {
          if (openLmkdSocket() == false) {... try { sLmkdOutputStream.write(buf.array(),0, buf.position());
            return; . }Copy the code

Is actually openLmkdSocket open native socket port and send priority information in the past, so how LMKD server-side processing, the init. Rc configuration service is in start when the phone is switched on, take a look at LMKD service entry: the main function

LMKD. C functions

int main(int argc __unused, char **argv __unused) {
    struct sched_param param = {
            .sched_priority = 1}; mlockall(MCL_FUTURE); sched_setscheduler(0, SCHED_FIFO, &param);
    if(! init()) mainloop(); ALOGI("exiting");
    return 0;
}Copy the code

If a request is received, the command is parsed and executed. Cmd_procprio is used to update the oomAdj file system. Look at the following code:

static voidcmd_procprio(int pid, int uid, int oomadj) { struct proc *procp; . Snprintf (path, sizeof(path),"/proc/%d/oom_score_adj", pid);
    snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj)); writefilestring(path, val); . }Copy the code

The simple flow chart is as follows, different from 4.3

LMKD service after Android5.0

That concludes the analysis of how user-space operations affect process priorities and write new priorities into the kernel. Finally, take a look at when and how LomemoryKiller kills processes based on priority:

LomemoryKiller Kernel part: How to Kill

LomemoryKiller is a kernel driver module that scans the process queue when the system is running low on memory, finds low-priority (or more cost-effective) processes and kills them. For drivers, the entry is the __init function. Let’s look at the entry of the driver module:

static int __init lowmem_init(void)
{
    register_shrinker(&lowmem_shrinker);
    return 0;
}Copy the code

As you can see, LomemoryKiller registers its lowmem_shrinker entry with the system’s memory detection module during init, register_shrinker is a function that belongs to another memory management module. If you have to root it, you can look at its definition, which is to add to a callback queue:

void register_shrinker(struct shrinker *shrinker)
{
    shrinker->nr = 0;
    down_write(&shrinker_rwsem);
    list_add_tail(&shrinker->list, &shrinker_list);
    up_write(&shrinker_rwsem);
}Copy the code

Finally, take a look at how LomemoryKiller finds and kills low-priority processes when an out-of-memory callback is triggered: The entry function is the lowmem_shrink function registered during init (4.3), but the principle is similar:

staticint lowmem_shrink(int nr_to_scan, gfp_t gfp_mask) { struct task_struct *p; . The key point1Find the threshold for the current memoryfor(i = 0; i < array_size; i++) {
        if (other_free < lowmem_minfree[i] &&
            other_file < lowmem_minfree[i]) {
            min_adj = lowmem_adj[i];
            break; }}... The key point2Find processes with priorities lower than this threshold and kill read_lock(& tasklist_Lock); for_each_process(p) {if(p->oomkilladj < min_adj || ! p->mm)continue;
        tasksize = get_mm_rss(p->mm);
        if (tasksize <= 0)
            continue;
        if (selected) {
            if (p->oomkilladj < selected->oomkilladj)
                continue;
            if (p->oomkilladj == selected->oomkilladj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;

    }
    if(selected ! = NULL) { force_sig(SIGKILL, selected); rem -= selected_tasksize; } lowmem_print(4."lowmem_shrink %d, %x, return %d\n", nr_to_scan, gfp_mask, rem);
    read_unlock(&tasklist_lock);
    return rem;
}Copy the code

Let’s look at key point 1: in fact, it is to determine the threshold corresponding to the current low memory; Key point 2: Find a process whose priority is lower than or equal to this threshold and which consumes a lot of memory (tasksize = get_mm_rss(p->mm)) and kill it. How was it killed? Very straightforward, through the Linux semaphore, send SIGKILL signal directly to kill the process. This concludes the analysis of how the LomemoryKiller kernel part works. It’s simple, in a word: passively scan, find low-priority processes, and kill them.

conclusion

Through this article, I hope you can have the following cognition:

  • Android APP processes have priority, which is directly related to whether the process is perceived by the user
  • Activities such as APP switching, which can cause process priority changes, take advantage of AMS and are set to the kernel via proc files
  • LowmemoryKiller runs in the kernel and selects low-priority processes to kill when memory needs to be reduced

More detailed memory reduction and priority calculation may be covered in a separate article in the future. The purpose of this article is to give you a brief understanding of LowmemoryKiller’s concept and how it works.

Android background Kill (FragmentActivity, PhoneWindow) LowMemoryKiller (4.3-6.0) LowMemoryKiller (4.3-6.0) Binder Obituary Principle Android background kill series 5: Android process alive – “cut” or rogue

Reference documentation

Android application startup process source code analysis Android Framework architecture analysis Android Low Memory Killer introduction to Android development InstanceState details Android LowMemoryKiller is one of the most important memory retrieval mechanisms in the Android operating system Process management in The Android operating system: Process creation – Google documentation – processes and threads