diff --git a/Android.bp b/Android.bp index 0481c96..e4f5e97 100644 --- a/Android.bp +++ b/Android.bp @@ -2,6 +2,32 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } +soong_config_module_type { + name: "lmkd_hooks_cc_defaults", + module_type: "cc_defaults", + config_namespace: "lmkd", + bool_variables: ["use_hooks"], + properties: [ + "cflags", + "static_libs", + ], +} + +lmkd_hooks_cc_defaults { + name: "lmkd_hooks_defaults", + + soong_config_variables: { + use_hooks: { + cflags: [ + "-DLMKD_USE_HOOKS" + ], + static_libs: [ + "liblmkdhooks" + ] + } + } +} + cc_defaults { name: "stats_defaults", cflags: [ @@ -38,7 +64,7 @@ cc_binary { "-DLMKD_TRACE_KILLS" ], init_rc: ["lmkd.rc"], - defaults: ["stats_defaults"], + defaults: ["stats_defaults", "lmkd_hooks_defaults"], logtags: ["event.logtags"], afdo: true, } diff --git a/include/lmkd_hooks.h b/include/lmkd_hooks.h new file mode 100644 index 0000000..259a3fd --- /dev/null +++ b/include/lmkd_hooks.h @@ -0,0 +1,67 @@ +/* + * Copyright 2022 Google, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This file defines no-op hook functions for LMKD. To override these + * definitions, enable the use_lmkd_hooks product variable and create a library + * "liblmkdhooks" that supplies definitions for the hook functions in your + * vendor folder. + */ + +#ifndef _LMKD_HOOKS_H_ +#define _LMKD_HOOKS_H_ + +#include + +__BEGIN_DECLS + +#ifdef LMKD_USE_HOOKS + +/* + * Initialize all necessary Android props and perform any necessary validation + * on the values. Called before lmkd_init_hook() and will be called again + * whenever LMKD receives the LMK_UPDATE_PROPS command. Returns true on success, + * false otherwise. + */ +bool lmkd_update_props_hook(); +/* + * Perform any necessary initialization for the hooks. Called only once at the + * end of LMKD's init(). Returns true on success, false otherwise. + */ +bool lmkd_init_hook(); +/* + * Allows for interception of a kill by LMKD. This hook may attempt to free + * memory elsewhere to avoid the specified process being killed. Returns 0 to + * proceed with the kill, or the number of memory pages freed elsewhere to skip + * the kill. + */ +int lmkd_free_memory_before_kill_hook(struct proc* procp, int proc_size_pages, + int proc_oom_score, int kill_reason); + +#else /* LMKD_USE_HOOKS */ + +static inline bool lmkd_update_props_hook() { return true; } +static inline bool lmkd_init_hook() { return true; } +static inline int lmkd_free_memory_before_kill_hook(struct proc*, int, int, + int) { + return 0; +} + +#endif /* LMKD_USE_HOOKS */ + +__END_DECLS + +#endif diff --git a/lmkd.cpp b/lmkd.cpp index 13386cc..db5c762 100644 --- a/lmkd.cpp +++ b/lmkd.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -547,7 +548,7 @@ static uint32_t killcnt_total = 0; /* PAGE_SIZE / 1024 */ static long page_k; -static void update_props(); +static bool update_props(); static bool init_monitors(); static void destroy_monitors(); @@ -1512,14 +1513,19 @@ static void ctrl_command_handler(int dsock_idx) { case LMK_UPDATE_PROPS: if (nargs != 0) goto wronglen; - update_props(); - if (!use_inkernel_interface) { - /* Reinitialize monitors to apply new settings */ - destroy_monitors(); - result = init_monitors() ? 0 : -1; - } else { - result = 0; + result = -1; + if (update_props()) { + if (!use_inkernel_interface) { + /* Reinitialize monitors to apply new settings */ + destroy_monitors(); + if (init_monitors()) { + result = 0; + } + } else { + result = 0; + } } + len = lmkd_pack_set_update_props_repl(packet, result); if (ctrl_data_write(dsock_idx, (char *)packet, len) != len) { ALOGE("Failed to report operation results"); @@ -2320,6 +2326,17 @@ static int kill_one_process(struct proc* procp, int min_oom_score, struct kill_i snprintf(desc, sizeof(desc), "lmk,%d,%d,%d,%d,%d", pid, ki ? (int)ki->kill_reason : -1, procp->oomadj, min_oom_score, ki ? ki->max_thrashing : -1); + result = lmkd_free_memory_before_kill_hook(procp, rss_kb / page_k, min_oom_score, + ki->kill_reason); + if (result > 0) { + /* + * Memory was freed elsewhere; no need to kill. Note: intentionally do not + * pid_remove(pid) since it was not killed. + */ + ALOGI("Skipping kill; %ld kB freed elsewhere.", result * page_k); + return result; + } + trace_kill_start(pid, desc); start_wait_for_proc_kill(pidfd < 0 ? pid : pidfd); @@ -3499,6 +3516,11 @@ static int init(void) { } ALOGI("Process polling is %s", pidfd_supported ? "supported" : "not supported" ); + if (!lmkd_init_hook()) { + ALOGE("Failed to initialize LMKD hooks."); + return -1; + } + return 0; } @@ -3687,7 +3709,7 @@ int issue_reinit() { return res == UPDATE_PROPS_SUCCESS ? 0 : -1; } -static void update_props() { +static bool update_props() { /* By default disable low level vmpressure events */ level_oomadj[VMPRESS_LEVEL_LOW] = GET_LMK_PROPERTY(int32, "low", OOM_SCORE_ADJ_MAX + 1); @@ -3731,6 +3753,14 @@ static void update_props() { stall_limit_critical = GET_LMK_PROPERTY(int64, "stall_limit_critical", 100); reaper.enable_debug(debug_process_killing); + + /* Call the update props hook */ + if (!lmkd_update_props_hook()) { + ALOGE("Failed to update LMKD hook props."); + return false; + } + + return true; } int main(int argc, char **argv) { @@ -3741,7 +3771,10 @@ int main(int argc, char **argv) { return issue_reinit(); } - update_props(); + if (!update_props()) { + ALOGE("Failed to initialize props, exiting."); + return -1; + } ctx = create_android_logger(KILLINFO_LOG_TAG);