From 518668e04311adb7b027f7e3f07d2c293c2e2e98 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 13 Apr 2018 11:36:46 -0700 Subject: [PATCH 1/4] lmkd: Add lmkd README file Add README file providing lmkd overview and description for its properties. Bug: 77299493 Bug: 75322373 Change-Id: I30236dd4ccaa2b48c31dfb80b63f0b82b49b5744 Merged-In: I30236dd4ccaa2b48c31dfb80b63f0b82b49b5744 Signed-off-by: Suren Baghdasaryan (cherry picked from commit 7e5dea82a5a2f86117722ff11c93bc672ffb7903) --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba2e83d --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +Android Low Memory Killer Daemon +================================ + + +Introduction +------------ + +Android Low Memory Killer Daemon (lmkd) is a process monitoring memory +state of a running Android system and reacting to high memory pressure +by killing the least essential process(es) to keep system performing +at acceptable levels. + + +Background +---------- + +Historically on Android systems memory monitoring and killing of +non-essential processes was handled by a kernel lowmemorykiller driver. +Since Linux Kernel 4.12 the lowmemorykiller driver has been removed and +instead userspace lmkd daemon performs these tasks. + + +Android Properties +------------------ + +lmkd can be configured on a particular system using the following Android +properties: + + ro.config.low_ram: choose between low-memory vs high-performance + device. Default = false. + + ro.lmk.low: min oom_adj score for processes eligible to be + killed at low vmpressure level. Default = 1001 + (disabled) + + ro.lmk.medium: min oom_adj score for processes eligible to be + killed at medium vmpressure level. Default = 800 + (non-essential processes) + + ro.lmk.critical: min oom_adj score for processes eligible to be + killed at critical vmpressure level. Default = 0 + (all processes) + + ro.lmk.critical_upgrade: enables upgrade to critical level. Default = false + + ro.lmk.upgrade_pressure: max mem_pressure at which level will be upgraded + because system is swapping too much. Default = 100 + (disabled) + + ro.lmk.downgrade_pressure: min mem_pressure at which vmpressure event will + be ignored because enough free memory is still + available. Default = 100 (disabled) + + ro.lmk.kill_heaviest_task: kill heaviest eligible task (best decision) vs. + any eligible task (fast decision). Default = false + + ro.lmk.kill_timeout_ms: duration in ms after a kill when no additional + kill will be done, Default = 0 (disabled) + + ro.lmk.debug: enable lmkd debug logs, Default = false From efe582c34f321e551f252e4b0498ecf13427e7f2 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 13 Apr 2018 11:45:38 -0700 Subject: [PATCH 2/4] lmkd: Rename is_go_device variable to better reflect its function Rename is_go_device variable to low_ram_device to better reflect its meaning and relation to ro.config.low_ram variable. Bug: 77299493 Bug: 75322373 Change-Id: I6e2eaebe79cf2e6edf861f7c602e52a5b573ad0a Merged-In: I6e2eaebe79cf2e6edf861f7c602e52a5b573ad0a Signed-off-by: Suren Baghdasaryan (cherry picked from commit fe2be6cc44073b7ef09ca849004e5072bdf18857) --- lmkd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lmkd.c b/lmkd.c index ee2f343..5f27dfc 100644 --- a/lmkd.c +++ b/lmkd.c @@ -112,7 +112,7 @@ static bool debug_process_killing; static bool enable_pressure_upgrade; static int64_t upgrade_pressure; static int64_t downgrade_pressure; -static bool is_go_device; +static bool low_ram_device; static bool kill_heaviest_task; static unsigned long kill_timeout_ms; @@ -771,7 +771,7 @@ static int find_and_kill_processes(enum vmpressure_level level, struct proc *procp; while (true) { - if (is_go_device) + if (low_ram_device) procp = proc_adj_lru(i); else procp = proc_get_heaviest(i); @@ -962,7 +962,7 @@ static void mp_event_common(int data, uint32_t events __unused) { } do_kill: - if (is_go_device) { + if (low_ram_device) { /* For Go devices kill only one task */ if (find_and_kill_processes(level, 0) == 0) { if (debug_process_killing) { @@ -1199,7 +1199,7 @@ int main(int argc __unused, char **argv __unused) { (int64_t)property_get_int32("ro.lmk.downgrade_pressure", 100); kill_heaviest_task = property_get_bool("ro.lmk.kill_heaviest_task", true); - is_go_device = property_get_bool("ro.config.low_ram", false); + low_ram_device = property_get_bool("ro.config.low_ram", false); kill_timeout_ms = (unsigned long)property_get_int32("ro.lmk.kill_timeout_ms", 0); From 9b5a5869e9d0d73a5bd82628c92e8502f023e1b8 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 13 Apr 2018 11:49:54 -0700 Subject: [PATCH 3/4] lmkd: Fix usage of ro.lmk.kill_heaviest_task property lmkd should use ro.lmk.kill_heaviest_task property to select between algorithms for victim selection. Set ro.lmk.kill_heaviest_task default value to false in order to keep it compatible with previous versions of lmkd (killing the heaviest task is a new mechanism). Bug: 77299493 Bug: 75322373 Change-Id: I78d2dc79d9c54e636c26665605518d9c87b535b3 Merged-In: I78d2dc79d9c54e636c26665605518d9c87b535b3 Signed-off-by: Suren Baghdasaryan (cherry picked from commit 818b59b2292f87c3781a6f7a288d10e2453b0d13) --- lmkd.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lmkd.c b/lmkd.c index 5f27dfc..6b0d8be 100644 --- a/lmkd.c +++ b/lmkd.c @@ -771,10 +771,8 @@ static int find_and_kill_processes(enum vmpressure_level level, struct proc *procp; while (true) { - if (low_ram_device) - procp = proc_adj_lru(i); - else - procp = proc_get_heaviest(i); + procp = kill_heaviest_task ? + proc_get_heaviest(i) : proc_adj_lru(i); if (!procp) break; @@ -1198,7 +1196,7 @@ int main(int argc __unused, char **argv __unused) { downgrade_pressure = (int64_t)property_get_int32("ro.lmk.downgrade_pressure", 100); kill_heaviest_task = - property_get_bool("ro.lmk.kill_heaviest_task", true); + property_get_bool("ro.lmk.kill_heaviest_task", false); low_ram_device = property_get_bool("ro.config.low_ram", false); kill_timeout_ms = (unsigned long)property_get_int32("ro.lmk.kill_timeout_ms", 0); From c482e927a2661d8f7b707d209efdb2c3ea176e9d Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 13 Apr 2018 12:43:41 -0700 Subject: [PATCH 4/4] lmkd: Optimize frequent file reads by keeping file descriptors open To check system memory state lmkd is using same files every time vmpressure event is received. Instead of opening and closing these files every time we store the file descriptor and use pread() to reread the file from the beginning. Bug: 77299493 Bug: 75322373 Change-Id: I8e27f8b9526e82e3cc313a02fce215c2e4dd3d29 Merged-In: I8e27f8b9526e82e3cc313a02fce215c2e4dd3d29 Signed-off-by: Suren Baghdasaryan (cherry picked from commit d716fe3610aae8cefcc676ade00c7bfd2b823c20) --- lmkd.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/lmkd.c b/lmkd.c index 6b0d8be..72e3251 100644 --- a/lmkd.c +++ b/lmkd.c @@ -171,6 +171,11 @@ struct proc { struct proc *pidhash_next; }; +struct reread_data { + const char* const filename; + int fd; +}; + #ifdef LMKD_LOG_STATS static bool enable_stats_log; static android_log_context log_ctx; @@ -186,12 +191,27 @@ static struct adjslot_list procadjslot_list[ADJTOSLOT(OOM_SCORE_ADJ_MAX) + 1]; /* PAGE_SIZE / 1024 */ static long page_k; +static bool parse_int64(const char* str, int64_t* ret) { + char* endptr; + long long val = strtoll(str, &endptr, 10); + if (str == endptr || val > INT64_MAX) { + return false; + } + *ret = (int64_t)val; + return true; +} + +/* + * Read file content from the beginning up to max_len bytes or EOF + * whichever happens first. + */ static ssize_t read_all(int fd, char *buf, size_t max_len) { ssize_t ret = 0; + off_t offset = 0; while (max_len > 0) { - ssize_t r = read(fd, buf, max_len); + ssize_t r = TEMP_FAILURE_RETRY(pread(fd, buf, max_len, offset)); if (r == 0) { break; } @@ -200,12 +220,44 @@ static ssize_t read_all(int fd, char *buf, size_t max_len) } ret += r; buf += r; + offset += r; max_len -= r; } return ret; } +/* + * Read a new or already opened file from the beginning. + * If the file has not been opened yet data->fd should be set to -1. + * To be used with files which are read often and possibly during high + * memory pressure to minimize file opening which by itself requires kernel + * memory allocation and might result in a stall on memory stressed system. + */ +static int reread_file(struct reread_data *data, char *buf, size_t buf_size) { + ssize_t size; + + if (data->fd == -1) { + data->fd = open(data->filename, O_RDONLY | O_CLOEXEC); + if (data->fd == -1) { + ALOGE("%s open: %s", data->filename, strerror(errno)); + return -1; + } + } + + size = read_all(data->fd, buf, buf_size - 1); + if (size < 0) { + ALOGE("%s read: %s", data->filename, strerror(errno)); + close(data->fd); + data->fd = -1; + return -1; + } + ALOG_ASSERT((size_t)size < buf_size - 1, data->filename " too large"); + buf[size] = 0; + + return 0; +} + static struct proc *pid_lookup(int pid) { struct proc *procp; @@ -442,7 +494,7 @@ static void ctrl_data_close(int dsock_idx) { static int ctrl_data_read(int dsock_idx, char *buf, size_t bufsz) { int ret = 0; - ret = read(data_sock[dsock_idx].sock, buf, bufsz); + ret = TEMP_FAILURE_RETRY(read(data_sock[dsock_idx].sock, buf, bufsz)); if (ret == -1) { ALOGE("control data socket read failed; errno=%d", errno); @@ -803,23 +855,19 @@ static int find_and_kill_processes(enum vmpressure_level level, return pages_freed; } -static int64_t get_memory_usage(const char* path) { +static int64_t get_memory_usage(struct reread_data *file_data) { int ret; int64_t mem_usage; char buf[32]; - int fd = open(path, O_RDONLY | O_CLOEXEC); - if (fd == -1) { - ALOGE("%s open: errno=%d", path, errno); + + if (reread_file(file_data, buf, sizeof(buf)) < 0) { return -1; } - ret = read_all(fd, buf, sizeof(buf) - 1); - close(fd); - if (ret < 0) { - ALOGE("%s error: errno=%d", path, errno); + if (!parse_int64(buf, &mem_usage)) { + ALOGE("%s parse error", file_data->filename); return -1; } - sscanf(buf, "%" SCNd64, &mem_usage); if (mem_usage == 0) { ALOGE("No memory!"); return -1; @@ -879,6 +927,14 @@ static void mp_event_common(int data, uint32_t events __unused) { static struct timeval last_report_tm; static unsigned long skip_count = 0; enum vmpressure_level level = (enum vmpressure_level)data; + static struct reread_data mem_usage_file_data = { + .filename = MEMCG_MEMORY_USAGE, + .fd = -1, + }; + static struct reread_data memsw_usage_file_data = { + .filename = MEMCG_MEMORYSW_USAGE, + .fd = -1, + }; /* * Check all event counters from low to critical @@ -887,7 +943,8 @@ static void mp_event_common(int data, uint32_t events __unused) { */ for (lvl = VMPRESS_LEVEL_LOW; lvl < VMPRESS_LEVEL_COUNT; lvl++) { if (mpevfd[lvl] != -1 && - read(mpevfd[lvl], &evcount, sizeof(evcount)) > 0 && + TEMP_FAILURE_RETRY(read(mpevfd[lvl], + &evcount, sizeof(evcount))) > 0 && evcount > 0 && lvl > level) { level = lvl; } @@ -924,9 +981,10 @@ static void mp_event_common(int data, uint32_t events __unused) { return; } - mem_usage = get_memory_usage(MEMCG_MEMORY_USAGE); - memsw_usage = get_memory_usage(MEMCG_MEMORYSW_USAGE); - if (memsw_usage < 0 || mem_usage < 0) { + if ((mem_usage = get_memory_usage(&mem_usage_file_data)) < 0) { + goto do_kill; + } + if ((memsw_usage = get_memory_usage(&memsw_usage_file_data)) < 0) { goto do_kill; }