From 5c48096d8dc460e6d8bd96476c57dc9371103f94 Mon Sep 17 00:00:00 2001 From: Jing Ji Date: Wed, 4 Dec 2019 09:22:05 -0800 Subject: [PATCH] lmkd: Support to send unsolicited message to clients For now the only unsolicited message from lmkd is the process kills on memory pressure. Bug: 136036078 Test: atest ApplicationExitInfoTest Change-Id: I503fd6a45ebab5276460b0ab978ebb2b8431dc0d Signed-off-by: Jing Ji --- README.md | 10 ++ include/lmkd.h | 22 +++- lmkd.c | 308 +++++++++++++++++++++++++++++++------------------ statslog.c | 79 +------------ statslog.h | 35 +++--- 5 files changed, 250 insertions(+), 204 deletions(-) diff --git a/README.md b/README.md index 8a73692..b02d3db 100644 --- a/README.md +++ b/README.md @@ -88,3 +88,13 @@ properties: ro.lmk.psi_complete_stall_ms: complete PSI stall threshold in milliseconds for triggering critical memory notification. Default = 700 + +lmkd will set the following Android properties according to current system +configurations: + + sys.lmk.minfree_levels: minfree:oom_adj_score pairs, delimited by comma + + sys.lmk.reportkills: whether or not it supports reporting process kills + to clients. Test app should check this property + before testing low memory kill notification. + Default will be unset. diff --git a/include/lmkd.h b/include/lmkd.h index bd9b80e..e27494c 100644 --- a/include/lmkd.h +++ b/include/lmkd.h @@ -27,11 +27,12 @@ __BEGIN_DECLS * Supported LMKD commands */ enum lmk_cmd { - LMK_TARGET = 0, /* Associate minfree with oom_adj_score */ - LMK_PROCPRIO, /* Register a process and set its oom_adj_score */ - LMK_PROCREMOVE, /* Unregister a process */ - LMK_PROCPURGE, /* Purge all registered processes */ - LMK_GETKILLCNT, /* Get number of kills */ + LMK_TARGET = 0, /* Associate minfree with oom_adj_score */ + LMK_PROCPRIO, /* Register a process and set its oom_adj_score */ + LMK_PROCREMOVE, /* Unregister a process */ + LMK_PROCPURGE, /* Purge all registered processes */ + LMK_GETKILLCNT, /* Get number of kills */ + LMK_PROCKILL, /* Unsolicited msg to system_server on proc kills */ }; /* @@ -201,6 +202,17 @@ static inline size_t lmkd_pack_set_getkillcnt_repl(LMKD_CTRL_PACKET packet, int return 2 * sizeof(int); } +/** + * Prepare LMK_PROCKILL unsolicited packet and return packet size in bytes. + * Warning: no checks performed, caller should ensure valid parameters. + */ +static inline size_t lmkd_pack_set_prockills(LMKD_CTRL_PACKET packet, pid_t pid, uid_t uid) { + packet[0] = htonl(LMK_PROCKILL); + packet[1] = htonl(pid); + packet[2] = htonl(uid); + return 3 * sizeof(int); +} + __END_DECLS #endif /* _LMKD_H_ */ diff --git a/lmkd.c b/lmkd.c index 4352498..1f94e57 100644 --- a/lmkd.c +++ b/lmkd.c @@ -200,7 +200,7 @@ static int psi_complete_stall_ms; static int thrashing_limit_pct; static int thrashing_limit_decay_pct; static bool use_psi_monitors = false; -static struct kernel_poll_info kpoll_info; +static int kpoll_fd; static struct psi_threshold psi_thresholds[VMPRESS_LEVEL_COUNT] = { { PSI_SOME, 70 }, /* 70ms out of 1sec for partial stall */ { PSI_SOME, 100 }, /* 100ms out of 1sec for partial stall */ @@ -638,6 +638,176 @@ static char *reread_file(struct reread_data *data) { return buf; } +static bool claim_record(struct proc* procp, pid_t pid) { + if (procp->reg_pid == pid) { + /* Record already belongs to the registrant */ + return true; + } + if (procp->reg_pid == 0) { + /* Old registrant is gone, claim the record */ + procp->reg_pid = pid; + return true; + } + /* The record is owned by another registrant */ + return false; +} + +static void remove_claims(pid_t pid) { + int i; + + for (i = 0; i < PIDHASH_SZ; i++) { + struct proc* procp = pidhash[i]; + while (procp) { + if (procp->reg_pid == pid) { + procp->reg_pid = 0; + } + procp = procp->pidhash_next; + } + } +} + +static void ctrl_data_close(int dsock_idx) { + struct epoll_event epev; + + ALOGI("closing lmkd data connection"); + if (epoll_ctl(epollfd, EPOLL_CTL_DEL, data_sock[dsock_idx].sock, &epev) == -1) { + // Log a warning and keep going + ALOGW("epoll_ctl for data connection socket failed; errno=%d", errno); + } + maxevents--; + + close(data_sock[dsock_idx].sock); + data_sock[dsock_idx].sock = -1; + + /* Mark all records of the old registrant as unclaimed */ + remove_claims(data_sock[dsock_idx].pid); +} + +static ssize_t ctrl_data_read(int dsock_idx, char* buf, size_t bufsz, struct ucred* sender_cred) { + struct iovec iov = {buf, bufsz}; + char control[CMSG_SPACE(sizeof(struct ucred))]; + struct msghdr hdr = { + NULL, 0, &iov, 1, control, sizeof(control), 0, + }; + ssize_t ret; + ret = TEMP_FAILURE_RETRY(recvmsg(data_sock[dsock_idx].sock, &hdr, 0)); + if (ret == -1) { + ALOGE("control data socket read failed; %s", strerror(errno)); + return -1; + } + if (ret == 0) { + ALOGE("Got EOF on control data socket"); + return -1; + } + + struct ucred* cred = NULL; + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr); + while (cmsg != NULL) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { + cred = (struct ucred*)CMSG_DATA(cmsg); + break; + } + cmsg = CMSG_NXTHDR(&hdr, cmsg); + } + + if (cred == NULL) { + ALOGE("Failed to retrieve sender credentials"); + /* Close the connection */ + ctrl_data_close(dsock_idx); + return -1; + } + + memcpy(sender_cred, cred, sizeof(struct ucred)); + + /* Store PID of the peer */ + data_sock[dsock_idx].pid = cred->pid; + + return ret; +} + +static int ctrl_data_write(int dsock_idx, char* buf, size_t bufsz) { + int ret = 0; + + ret = TEMP_FAILURE_RETRY(write(data_sock[dsock_idx].sock, buf, bufsz)); + + if (ret == -1) { + ALOGE("control data socket write failed; errno=%d", errno); + } else if (ret == 0) { + ALOGE("Got EOF on control data socket"); + ret = -1; + } + + return ret; +} + +/* + * Write the pid/uid pair over the data socket, note: all active clients + * will receive this unsolicited notification. + */ +static void ctrl_data_write_lmk_kill_occurred(pid_t pid, uid_t uid) { + LMKD_CTRL_PACKET packet; + size_t len = lmkd_pack_set_prockills(packet, pid, uid); + + for (int i = 0; i < MAX_DATA_CONN; i++) { + if (data_sock[i].sock >= 0) { + ctrl_data_write(i, (char*)packet, len); + } + } +} + +static void poll_kernel(int poll_fd) { + if (poll_fd == -1) { + // not waiting + return; + } + + while (1) { + char rd_buf[256]; + int bytes_read = TEMP_FAILURE_RETRY(pread(poll_fd, (void*)rd_buf, sizeof(rd_buf), 0)); + if (bytes_read <= 0) break; + rd_buf[bytes_read] = '\0'; + + int64_t pid; + int64_t uid; + int64_t group_leader_pid; + int64_t rss_in_pages; + struct memory_stat mem_st = {}; + int16_t oom_score_adj; + int16_t min_score_adj; + int64_t starttime; + char* taskname = 0; + + int fields_read = + sscanf(rd_buf, + "%" SCNd64 " %" SCNd64 " %" SCNd64 " %" SCNd64 " %" SCNd64 " %" SCNd64 + " %" SCNd16 " %" SCNd16 " %" SCNd64 "\n%m[^\n]", + &pid, &uid, &group_leader_pid, &mem_st.pgfault, &mem_st.pgmajfault, + &rss_in_pages, &oom_score_adj, &min_score_adj, &starttime, &taskname); + + /* only the death of the group leader process is logged */ + if (fields_read == 10 && group_leader_pid == pid) { + ctrl_data_write_lmk_kill_occurred((pid_t)pid, (uid_t)uid); + mem_st.process_start_time_ns = starttime * (NS_PER_SEC / sysconf(_SC_CLK_TCK)); + mem_st.rss_in_bytes = rss_in_pages * PAGE_SIZE; + stats_write_lmk_kill_occurred_pid(LMK_KILL_OCCURRED, uid, pid, oom_score_adj, + min_score_adj, 0, &mem_st); + } + + free(taskname); + } +} + +static bool init_poll_kernel() { + kpoll_fd = TEMP_FAILURE_RETRY(open("/proc/lowmemorykiller", O_RDONLY | O_NONBLOCK | O_CLOEXEC)); + + if (kpoll_fd < 0) { + ALOGE("kernel lmk event file could not be opened; errno=%d", errno); + return false; + } + + return true; +} + static struct proc *pid_lookup(int pid) { struct proc *procp; @@ -848,34 +1018,6 @@ static char *proc_get_name(int pid, char *buf, size_t buf_size) { return buf; } -static bool claim_record(struct proc *procp, pid_t pid) { - if (procp->reg_pid == pid) { - /* Record already belongs to the registrant */ - return true; - } - if (procp->reg_pid == 0) { - /* Old registrant is gone, claim the record */ - procp->reg_pid = pid; - return true; - } - /* The record is owned by another registrant */ - return false; -} - -static void remove_claims(pid_t pid) { - int i; - - for (i = 0; i < PIDHASH_SZ; i++) { - struct proc *procp = pidhash[i]; - while (procp) { - if (procp->reg_pid == pid) { - procp->reg_pid = 0; - } - procp = procp->pidhash_next; - } - } -} - static void cmd_procprio(LMKD_CTRL_PACKET packet, int field_count, struct ucred *cred) { struct proc *procp; char path[LINE_MAX]; @@ -920,8 +1062,7 @@ static void cmd_procprio(LMKD_CTRL_PACKET packet, int field_count, struct ucred } if (use_inkernel_interface) { - stats_store_taskname(params.pid, proc_get_name(params.pid, path, sizeof(path)), - kpoll_info.poll_fd); + stats_store_taskname(params.pid, proc_get_name(params.pid, path, sizeof(path))); return; } @@ -1015,7 +1156,15 @@ static void cmd_procremove(LMKD_CTRL_PACKET packet, struct ucred *cred) { lmkd_pack_get_procremove(packet, ¶ms); if (use_inkernel_interface) { - stats_remove_taskname(params.pid, kpoll_info.poll_fd); + /* + * Perform an extra check before the pid is removed, after which it + * will be impossible for poll_kernel to get the taskname. poll_kernel() + * is potentially a long-running blocking function; however this method + * handles AMS requests but does not block AMS. + */ + poll_kernel(kpoll_fd); + + stats_remove_taskname(params.pid); return; } @@ -1198,81 +1347,6 @@ static void cmd_target(int ntargets, LMKD_CTRL_PACKET packet) { } } -static void ctrl_data_close(int dsock_idx) { - struct epoll_event epev; - - ALOGI("closing lmkd data connection"); - if (epoll_ctl(epollfd, EPOLL_CTL_DEL, data_sock[dsock_idx].sock, &epev) == -1) { - // Log a warning and keep going - ALOGW("epoll_ctl for data connection socket failed; errno=%d", errno); - } - maxevents--; - - close(data_sock[dsock_idx].sock); - data_sock[dsock_idx].sock = -1; - - /* Mark all records of the old registrant as unclaimed */ - remove_claims(data_sock[dsock_idx].pid); -} - -static ssize_t ctrl_data_read(int dsock_idx, char *buf, size_t bufsz, struct ucred *sender_cred) { - struct iovec iov = { buf, bufsz }; - char control[CMSG_SPACE(sizeof(struct ucred))]; - struct msghdr hdr = { - NULL, 0, &iov, 1, control, sizeof(control), 0, - }; - ssize_t ret; - - ret = TEMP_FAILURE_RETRY(recvmsg(data_sock[dsock_idx].sock, &hdr, 0)); - if (ret == -1) { - ALOGE("control data socket read failed; %s", strerror(errno)); - return -1; - } - if (ret == 0) { - ALOGE("Got EOF on control data socket"); - return -1; - } - - struct ucred* cred = NULL; - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr); - while (cmsg != NULL) { - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { - cred = (struct ucred*)CMSG_DATA(cmsg); - break; - } - cmsg = CMSG_NXTHDR(&hdr, cmsg); - } - - if (cred == NULL) { - ALOGE("Failed to retrieve sender credentials"); - /* Close the connection */ - ctrl_data_close(dsock_idx); - return -1; - } - - memcpy(sender_cred, cred, sizeof(struct ucred)); - - /* Store PID of the peer */ - data_sock[dsock_idx].pid = cred->pid; - - return ret; -} - -static int ctrl_data_write(int dsock_idx, char *buf, size_t bufsz) { - int ret = 0; - - ret = TEMP_FAILURE_RETRY(write(data_sock[dsock_idx].sock, buf, bufsz)); - - if (ret == -1) { - ALOGE("control data socket write failed; errno=%d", errno); - } else if (ret == 0) { - ALOGE("Got EOF on control data socket"); - ret = -1; - } - - return ret; -} - static void ctrl_command_handler(int dsock_idx) { LMKD_CTRL_PACKET packet; struct ucred cred; @@ -1327,6 +1401,10 @@ static void ctrl_command_handler(int dsock_idx) { if (ctrl_data_write(dsock_idx, (char *)packet, len) != len) return; break; + case LMK_PROCKILL: + /* This command code is NOT expected at all */ + ALOGE("Received unexpected command code %d", cmd); + break; default: ALOGE("Received unknown command code %d", cmd); return; @@ -1946,6 +2024,8 @@ static int kill_one_process(struct proc* procp, int min_oom_score, int kill_reas stats_write_lmk_kill_occurred(LMK_KILL_OCCURRED, uid, taskname, procp->oomadj, min_oom_score, tasksize, mem_st); + ctrl_data_write_lmk_kill_occurred((pid_t)pid, uid); + result = tasksize; out: @@ -2703,7 +2783,7 @@ err_open_mpfd: static void kernel_event_handler(int data __unused, uint32_t events __unused, struct polling_params *poll_params __unused) { - kpoll_info.handler(kpoll_info.poll_fd); + poll_kernel(kpoll_fd); } static int init(void) { @@ -2759,15 +2839,17 @@ static int init(void) { if (use_inkernel_interface) { ALOGI("Using in-kernel low memory killer interface"); - if (init_poll_kernel(&kpoll_info)) { + if (init_poll_kernel()) { epev.events = EPOLLIN; epev.data.ptr = (void*)&kernel_poll_hinfo; - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, kpoll_info.poll_fd, &epev) != 0) { + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, kpoll_fd, &epev) != 0) { ALOGE("epoll_ctl for lmk events failed (errno=%d)", errno); - close(kpoll_info.poll_fd); - kpoll_info.poll_fd = -1; + close(kpoll_fd); + kpoll_fd = -1; } else { maxevents++; + /* let the others know it does support reporting kills */ + property_set("sys.lmk.reportkills", "1"); } } } else { @@ -2787,6 +2869,8 @@ static int init(void) { } else { ALOGI("Using vmpressure for memory pressure detection"); } + /* let the others know it does support reporting kills */ + property_set("sys.lmk.reportkills", "1"); } for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) { diff --git a/statslog.c b/statslog.c index 4f76261..428ffb5 100644 --- a/statslog.c +++ b/statslog.c @@ -181,9 +181,9 @@ stats_write_lmk_kill_occurred(int32_t code, int32_t uid, char const* process_nam return write_to_logger(log_ctx, LOG_ID_STATS); } -static int stats_write_lmk_kill_occurred_pid(int32_t code, int32_t uid, int pid, - int32_t oom_score, int32_t min_oom_score, int tasksize, - struct memory_stat *mem_st) { +int stats_write_lmk_kill_occurred_pid(int32_t code, int32_t uid, int pid, int32_t oom_score, + int32_t min_oom_score, int tasksize, + struct memory_stat* mem_st) { struct proc* proc = pid_lookup(pid); if (!proc) return -EINVAL; @@ -290,65 +290,6 @@ struct memory_stat *stats_read_memory_stat(bool per_app_memcg, int pid, uid_t ui return NULL; } -static void poll_kernel(int poll_fd) { - if (poll_fd == -1) { - // not waiting - return; - } - - while (1) { - char rd_buf[256]; - int bytes_read = - TEMP_FAILURE_RETRY(pread(poll_fd, (void*)rd_buf, sizeof(rd_buf), 0)); - if (bytes_read <= 0) break; - rd_buf[bytes_read] = '\0'; - - int64_t pid; - int64_t uid; - int64_t group_leader_pid; - int64_t rss_in_pages; - struct memory_stat mem_st = {}; - int16_t oom_score_adj; - int16_t min_score_adj; - int64_t starttime; - char* taskname = 0; - - int fields_read = sscanf(rd_buf, - "%" SCNd64 " %" SCNd64 " %" SCNd64 " %" SCNd64 " %" SCNd64 - " %" SCNd64 " %" SCNd16 " %" SCNd16 " %" SCNd64 "\n%m[^\n]", - &pid, &uid, &group_leader_pid, &mem_st.pgfault, - &mem_st.pgmajfault, &rss_in_pages, &oom_score_adj, - &min_score_adj, &starttime, &taskname); - - /* only the death of the group leader process is logged */ - if (fields_read == 10 && group_leader_pid == pid) { - mem_st.process_start_time_ns = starttime * (NS_PER_SEC / sysconf(_SC_CLK_TCK)); - mem_st.rss_in_bytes = rss_in_pages * PAGE_SIZE; - stats_write_lmk_kill_occurred_pid(LMK_KILL_OCCURRED, uid, pid, oom_score_adj, - min_score_adj, 0, &mem_st); - } - - free(taskname); - } -} - -bool init_poll_kernel(struct kernel_poll_info *poll_info) { - if (!enable_stats_log) { - return false; - } - - poll_info->poll_fd = - TEMP_FAILURE_RETRY(open("/proc/lowmemorykiller", O_RDONLY | O_NONBLOCK | O_CLOEXEC)); - - if (poll_info->poll_fd < 0) { - ALOGE("kernel lmk event file could not be opened; errno=%d", errno); - return false; - } - poll_info->handler = poll_kernel; - - return true; -} - static void proc_insert(struct proc* procp) { if (!pidhash) { pidhash = calloc(PIDHASH_SZ, sizeof(*pidhash)); @@ -359,19 +300,11 @@ static void proc_insert(struct proc* procp) { pidhash[hval] = procp; } -void stats_remove_taskname(int pid, int poll_fd) { +void stats_remove_taskname(int pid) { if (!enable_stats_log || !pidhash) { return; } - /* - * Perform an extra check before the pid is removed, after which it - * will be impossible for poll_kernel to get the taskname. poll_kernel() - * is potentially a long-running blocking function; however this method - * handles AMS requests but does not block AMS. - */ - poll_kernel(poll_fd); - int hval = pid_hashfn(pid); struct proc* procp; struct proc* prevp; @@ -391,7 +324,7 @@ void stats_remove_taskname(int pid, int poll_fd) { free(procp); } -void stats_store_taskname(int pid, const char* taskname, int poll_fd) { +void stats_store_taskname(int pid, const char* taskname) { if (!enable_stats_log) { return; } @@ -401,7 +334,7 @@ void stats_store_taskname(int pid, const char* taskname, int poll_fd) { if (strcmp(procp->taskname, taskname) == 0) { return; } - stats_remove_taskname(pid, poll_fd); + stats_remove_taskname(pid); } procp = malloc(sizeof(struct proc)); procp->pid = pid; diff --git a/statslog.h b/statslog.h index a09c7dd..e6f121f 100644 --- a/statslog.h +++ b/statslog.h @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -36,11 +37,6 @@ struct memory_stat { int64_t process_start_time_ns; }; -struct kernel_poll_info { - int poll_fd; - void (*handler)(int poll_fd); -}; - /* * These are defined in * http://cs/android/frameworks/base/cmds/statsd/src/atoms.proto @@ -84,12 +80,20 @@ stats_write_lmk_kill_occurred(int32_t code, int32_t uid, char const* process_name, int32_t oom_score, int32_t min_oom_score, int tasksize, struct memory_stat *mem_st); +/** + * Logs the event when LMKD kills a process to reduce memory pressure. + * Code: LMK_KILL_OCCURRED = 51 + */ +int stats_write_lmk_kill_occurred_pid(int32_t code, int32_t uid, int pid, int32_t oom_score, + int32_t min_oom_score, int tasksize, + struct memory_stat* mem_st); + struct memory_stat *stats_read_memory_stat(bool per_app_memcg, int pid, uid_t uid); /** * Registers a process taskname by pid, while it is still alive. */ -void stats_store_taskname(int pid, const char* taskname, int poll_fd); +void stats_store_taskname(int pid, const char* taskname); /** * Unregister all process tasknames. @@ -99,9 +103,7 @@ void stats_purge_tasknames(); /** * Unregister a process taskname, e.g. after it has been killed. */ -void stats_remove_taskname(int pid, int poll_fd); - -bool init_poll_kernel(struct kernel_poll_info *poll_info); +void stats_remove_taskname(int pid); #else /* LMKD_LOG_STATS */ @@ -117,17 +119,22 @@ stats_write_lmk_kill_occurred(int32_t code __unused, int32_t uid __unused, int32_t min_oom_score __unused, int tasksize __unused, struct memory_stat *mem_st __unused) { return -EINVAL; } +static inline int stats_write_lmk_kill_occurred_pid(int32_t code __unused, int32_t uid __unused, + int pid __unused, int32_t oom_score __unused, + int32_t min_oom_score __unused, + int tasksize __unused, + struct memory_stat* mem_st __unused) { + return -EINVAL; +} + static inline struct memory_stat *stats_read_memory_stat(bool per_app_memcg __unused, int pid __unused, uid_t uid __unused) { return NULL; } -static inline void stats_store_taskname(int pid __unused, const char* taskname __unused, - int poll_fd __unused) {} +static inline void stats_store_taskname(int pid __unused, const char* taskname __unused) {} static inline void stats_purge_tasknames() {} -static inline void stats_remove_taskname(int pid __unused, int poll_fd __unused) {} - -static inline bool init_poll_kernel(struct kernel_poll_info *poll_info __unused) { return false; } +static inline void stats_remove_taskname(int pid __unused) {} #endif /* LMKD_LOG_STATS */