Android LMKD(3) 当ro.config.low_ram属性为true时

作者 by adtxl / 2022-03-07 / 暂无评论 / 606 个足迹

打开Android go的设备,或者设置属性ro.config.low_ram为true,此时Android会认为该设备为低内存设备,本文记录代码中(Android12)对低内存设备做的一些改动.

1. low_ram_device & per_app_memcg

在lmkd的属性处理中,当为低内存设备时,per_app_config的默认值为true.
swap_free_low_percentage的值默认为10.

// system/memory/lmkd/lmkd.cpp

/* ro.lmk.swap_free_low_percentage property defaults */
#define DEF_LOW_SWAP 10

static int clamp(int low, int high, int value) {
    return max(min(value, high), low);
}

static void update_props() {
......
    per_app_memcg =
        property_get_bool("ro.config.per_app_memcg", low_ram_device);
    swap_free_low_percentage = clamp(0, 100, property_get_int32("ro.lmk.swap_free_low_percentage",
        DEF_LOW_SWAP));
......
    swap_util_max = clamp(0, 100, property_get_int32("ro.lmk.swap_util_max", 100));
    filecache_min_kb = property_get_int64("ro.lmk.filecache_min_kb", 0);
}

下面分析下当per_app_config属性为true时主要做了什么?

1.1 cmd_procprio

cmd_procprio的主要作用是更新进程的的adj,首先通过lmkd_pack_get_procprio将packet解释为lmk_procprio类型的数据,该数据类型包括pid,uid以及adj,接着检查需要设置的adj是否在范围以内,最后写入到/proc/[pid]/oom_score_adj.至此如果是使用内核逻辑的话,就会返回.否则还会进行进一步处理.
假如是low_ram_device,还会去更新soft_limit_mult/dev/memcg/apps/uid_%d/pid_%d/memory.soft_limit_in_byte.最后还会通过调用pid_lookup在哈希表中是否存在该进程,假如不存在,则将进程加入到双向链表中,否则将该进程移出,并重新加入到双向链表中的头部.

memory.soft_limit_in_byte具体有什么用下面会继续分析

static void cmd_procprio(LMKD_CTRL_PACKET packet, int field_count, struct ucred *cred) {

......

    /* lmkd should not change soft limits for services */
    if (params.ptype == PROC_TYPE_APP && per_app_memcg) {
        if (params.oomadj >= 900) {
            soft_limit_mult = 0;
        } else if (params.oomadj >= 800) {
            soft_limit_mult = 0;
        } else if (params.oomadj >= 700) {
            soft_limit_mult = 0;
        } else if (params.oomadj >= 600) {
            // Launcher should be perceptible, don't kill it.
            params.oomadj = 200;
            soft_limit_mult = 1;
        } else if (params.oomadj >= 500) {
            soft_limit_mult = 0;
        } else if (params.oomadj >= 400) {
            soft_limit_mult = 0;
        } else if (params.oomadj >= 300) {
            soft_limit_mult = 1;
        } else if (params.oomadj >= 200) {
            soft_limit_mult = 8;
        } else if (params.oomadj >= 100) {
            soft_limit_mult = 10;
        } else if (params.oomadj >=   0) {
            soft_limit_mult = 20;
        } else {
            // Persistent processes will have a large
            // soft limit 512MB.
            soft_limit_mult = 64;
        }

        snprintf(path, sizeof(path), MEMCG_SYSFS_PATH
                 "apps/uid_%d/pid_%d/memory.soft_limit_in_bytes",
                 params.uid, params.pid);
        snprintf(val, sizeof(val), "%d", soft_limit_mult * EIGHT_MEGA);

        /*
         * system_server process has no memcg under /dev/memcg/apps but should be
         * registered with lmkd. This is the best way so far to identify it.
         */
        is_system_server = (params.oomadj == SYSTEM_ADJ &&
                            (pwdrec = getpwnam("system")) != NULL &&
                            params.uid == pwdrec->pw_uid);
        writefilestring(path, val, !is_system_server);
    }

......

}

1.2 libprocessgroup

libprocessgroup会根据'ro.config.per_app_memcg'属性判断使用使用per-app-memcg,如果未设置该属性。在低内存设备会默认使用per-app-memcg,否则为false.

然后libprocessgroup会创建相关的节点,

// system/core/libprocessgroup/processgroup.cpp

bool UsePerAppMemcg() {
    bool low_ram_device = GetBoolProperty("ro.config.low_ram", false);
    return GetBoolProperty("ro.config.per_app_memcg", low_ram_device);
}

static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries,
                            int* max_processes) {
    std::string hierarchy_root_path;
    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
    const char* cgroup = hierarchy_root_path.c_str();

    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();

    if (max_processes != nullptr) {
        *max_processes = 0;
    }

    int retry = retries;
    int processes;
    while ((processes = DoKillProcessGroupOnce(cgroup, uid, initialPid, signal)) > 0) {
        if (max_processes != nullptr && processes > *max_processes) {
            *max_processes = processes;
        }
        LOG(VERBOSE) << "Killed " << processes << " processes for processgroup " << initialPid;
        if (retry > 0) {
            std::this_thread::sleep_for(5ms);
            --retry;
        } else {
            break;
        }
    }

    if (processes < 0) {
        PLOG(ERROR) << "Error encountered killing process cgroup uid " << uid << " pid "
                    << initialPid;
        return -1;
    }

    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();

    // We only calculate the number of 'processes' when killing the processes.
    // In the retries == 0 case, we only kill the processes once and therefore
    // will not have waited then recalculated how many processes are remaining
    // after the first signals have been sent.
    // Logging anything regarding the number of 'processes' here does not make sense.

    if (processes == 0) {
        if (retries > 0) {
            LOG(INFO) << "Successfully killed process cgroup uid " << uid << " pid " << initialPid
                      << " in " << static_cast<int>(ms) << "ms";
        }

        int err = RemoveProcessGroup(cgroup, uid, initialPid, retries);

        if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
            std::string memory_path;
            CgroupGetControllerPath("memory", &memory_path);
            memory_path += "/apps";
            if (RemoveProcessGroup(memory_path.c_str(), uid, initialPid, retries)) return -1;
        }

        return err;
    } else {
        if (retries > 0) {
            LOG(ERROR) << "Failed to kill process cgroup uid " << uid << " pid " << initialPid
                       << " in " << static_cast<int>(ms) << "ms, " << processes
                       << " processes remain";
        }
        return -1;
    }
}

int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
    std::string cgroup;

    if (memControl && !UsePerAppMemcg()) {
        PLOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
        return -EINVAL;
    }

    if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
        CgroupGetControllerPath("memory", &cgroup);
        cgroup += "/apps";
        int ret = createProcessGroupInternal(uid, initialPid, cgroup);
        if (ret != 0) {
            return ret;
        }
    }

    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cgroup);
    return createProcessGroupInternal(uid, initialPid, cgroup);
}

设置ro.config.per_app_memcg属性为false,将不会使用per_app_memcg。
在开发板上实际测试时发现,未设置ro.config.per_app_memcg,由于是低内存设备,swap内存会很快被用光。将ro.config.per_app_memcg属性为false,不适用per_app_memcg,swap内存还是会快速为0.

2. art

如果ro.config.low_ram属性为true,此时会在虚拟机启动的时候添加LowMemoryMode参数。

// frameworks/base/core/jni/AndroidRuntime.cpp
/*
 * Start the Dalvik Virtual Machine.
 *
 * Various arguments, most determined by system properties, are passed in.
 * The "mOptions" vector is updated.
 *
 * CAUTION: when adding options in here, be careful not to put the
 * char buffer inside a nested scope.  Adding the buffer to the
 * options using mOptions.add() does not copy the buffer, so if the
 * buffer goes out of scope the option may be overwritten.  It's best
 * to put the buffer at the top of the function so that it is more
 * unlikely that someone will surround it in a scope at a later time
 * and thus introduce a bug.
 *
 * Returns 0 on success.
 */

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote)
{
......
    property_get("ro.config.low_ram", propBuf, "");
    if (strcmp(propBuf, "true") == 0) {
      addOption("-XX:LowMemoryMode");
    }
......
}

在下面的函数中会解析出来,如果是低内存设备,background_collector_type_为kCollectorTypeSS。

// art/runtime/parsed_options.cc

bool ParsedOptions::DoParse(const RuntimeOptions& options,
                            bool ignore_unrecognized,
                            RuntimeArgumentMap* runtime_options) {
  for (size_t i = 0; i < options.size(); ++i) {
    if (true && options[0].first == "-Xzygote") {
      LOG(INFO) << "option[" << i << "]=" << options[i].first;
    }
  }

......
  {
    // If not set, background collector type defaults to homogeneous compaction.
    // If not low memory mode, semispace otherwise.

    gc::CollectorType background_collector_type_;
    gc::CollectorType collector_type_ = (XGcOption{}).collector_type_;
    bool low_memory_mode_ = args.Exists(M::LowMemoryMode);

    background_collector_type_ = args.GetOrDefault(M::BackgroundGc);
    {
      XGcOption* xgc = args.Get(M::GcOption);
      if (xgc != nullptr && xgc->collector_type_ != gc::kCollectorTypeNone) {
        collector_type_ = xgc->collector_type_;
      }
    }

    if (background_collector_type_ == gc::kCollectorTypeNone) {
      background_collector_type_ = low_memory_mode_ ?
          gc::kCollectorTypeSS : gc::kCollectorTypeHomogeneousSpaceCompact;
    }

    args.Set(M::BackgroundGc, BackgroundGcOption { background_collector_type_ });
  }
......

}

还有下面的影响,不太清楚

// art/runtime/runtime.cc

static constexpr double kLowMemoryMinLoadFactor = 0.5;
static constexpr double kLowMemoryMaxLoadFactor = 0.8;
static constexpr double kNormalMinLoadFactor = 0.4;
static constexpr double kNormalMaxLoadFactor = 0.7;

bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {

......
  is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode);
......

  float foreground_heap_growth_multiplier;
  if (is_low_memory_mode_ && !runtime_options.Exists(Opt::ForegroundHeapGrowthMultiplier)) {
    // If low memory mode, use 1.0 as the multiplier by default.
    foreground_heap_growth_multiplier = 1.0f;
  } else {
    foreground_heap_growth_multiplier =
        runtime_options.GetOrDefault(Opt::ForegroundHeapGrowthMultiplier) +
            kExtraDefaultHeapGrowthMultiplier;
  }
}


double Runtime::GetHashTableMinLoadFactor() const {
  return is_low_memory_mode_ ? kLowMemoryMinLoadFactor : kNormalMinLoadFactor;
}

double Runtime::GetHashTableMaxLoadFactor() const {
  return is_low_memory_mode_ ? kLowMemoryMaxLoadFactor : kNormalMaxLoadFactor;
}

独特见解