From 06f75707e1bbdc551ea6ca9f3225b3c0b9fcf513 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 26 Jan 2018 12:51:19 -0800 Subject: [PATCH 1/4] lmkd: Add support for multiple lmkd client connections (cherry pick from commit 3cfb2c8b1018babf0007d8eb6263e25360409a33) lmkd testing requires communication with lmkd daemon to register new native processes. New implementation allows more than one communication channel to lmkd. Current max number of communication channels is set to two - one for ActivityManager and another one for a test process. Bug: 63631020 Change-Id: I736115938a3c5ad9253bce29a17cd5349af190eb Merged-In: I736115938a3c5ad9253bce29a17cd5349af190eb Signed-off-by: Suren Baghdasaryan --- lmkd/lmkd.c | 193 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 128 insertions(+), 65 deletions(-) diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c index 338e5fa20..2a7fedb44 100644 --- a/lmkd/lmkd.c +++ b/lmkd/lmkd.c @@ -122,13 +122,30 @@ static bool is_go_device; static bool kill_heaviest_task; static unsigned long kill_timeout_ms; -/* control socket listen and data */ -static int ctrl_lfd; -static int ctrl_dfd = -1; -static int ctrl_dfd_reopened; /* did we reopen ctrl conn on this loop? */ +/* data required to handle events */ +struct event_handler_info { + int data; + void (*handler)(int data, uint32_t events); +}; -/* 3 memory pressure levels, 1 ctrl listen socket, 1 ctrl data socket */ -#define MAX_EPOLL_EVENTS 5 +/* data required to handle socket events */ +struct sock_event_handler_info { + int sock; + struct event_handler_info handler_info; +}; + +/* max supported number of data connections */ +#define MAX_DATA_CONN 2 + +/* socket event handler data */ +static struct sock_event_handler_info ctrl_sock; +static struct sock_event_handler_info data_sock[MAX_DATA_CONN]; + +/* vmpressure event handler data */ +static struct event_handler_info vmpressure_hinfo[VMPRESS_LEVEL_COUNT]; + +/* 3 memory pressure levels, 1 ctrl listen socket, 2 ctrl data socket */ +#define MAX_EPOLL_EVENTS (1 + MAX_DATA_CONN + VMPRESS_LEVEL_COUNT) static int epollfd; static int maxevents; @@ -398,17 +415,24 @@ static void cmd_target(int ntargets, int *params) { } } -static void ctrl_data_close(void) { - ALOGI("Closing Activity Manager data connection"); - close(ctrl_dfd); - ctrl_dfd = -1; +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; } -static int ctrl_data_read(char *buf, size_t bufsz) { +static int ctrl_data_read(int dsock_idx, char *buf, size_t bufsz) { int ret = 0; - ret = read(ctrl_dfd, buf, bufsz); + ret = read(data_sock[dsock_idx].sock, buf, bufsz); if (ret == -1) { ALOGE("control data socket read failed; errno=%d", errno); @@ -420,14 +444,14 @@ static int ctrl_data_read(char *buf, size_t bufsz) { return ret; } -static void ctrl_command_handler(void) { +static void ctrl_command_handler(int dsock_idx) { int ibuf[CTRL_PACKET_MAX / sizeof(int)]; int len; int cmd = -1; int nargs; int targets; - len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX); + len = ctrl_data_read(dsock_idx, (char *)ibuf, CTRL_PACKET_MAX); if (len <= 0) return; @@ -465,40 +489,57 @@ wronglen: ALOGE("Wrong control socket read length cmd=%d len=%d", cmd, len); } -static void ctrl_data_handler(uint32_t events) { - if (events & EPOLLHUP) { - ALOGI("ActivityManager disconnected"); - if (!ctrl_dfd_reopened) - ctrl_data_close(); - } else if (events & EPOLLIN) { - ctrl_command_handler(); +static void ctrl_data_handler(int data, uint32_t events) { + if (events & EPOLLIN) { + ctrl_command_handler(data); } } -static void ctrl_connect_handler(uint32_t events __unused) { - struct epoll_event epev; +static int get_free_dsock() { + for (int i = 0; i < MAX_DATA_CONN; i++) { + if (data_sock[i].sock < 0) { + return i; + } + } + return -1; +} - if (ctrl_dfd >= 0) { - ctrl_data_close(); - ctrl_dfd_reopened = 1; +static void ctrl_connect_handler(int data __unused, uint32_t events __unused) { + struct epoll_event epev; + int free_dscock_idx = get_free_dsock(); + + if (free_dscock_idx < 0) { + /* + * Number of data connections exceeded max supported. This should not + * happen but if it does we drop all existing connections and accept + * the new one. This prevents inactive connections from monopolizing + * data socket and if we drop ActivityManager connection it will + * immediately reconnect. + */ + for (int i = 0; i < MAX_DATA_CONN; i++) { + ctrl_data_close(i); + } + free_dscock_idx = 0; } - ctrl_dfd = accept(ctrl_lfd, NULL, NULL); - - if (ctrl_dfd < 0) { + data_sock[free_dscock_idx].sock = accept(ctrl_sock.sock, NULL, NULL); + if (data_sock[free_dscock_idx].sock < 0) { ALOGE("lmkd control socket accept failed; errno=%d", errno); return; } - ALOGI("ActivityManager connected"); - maxevents++; + ALOGI("lmkd data connection established"); + /* use data to store data connection idx */ + data_sock[free_dscock_idx].handler_info.data = free_dscock_idx; + data_sock[free_dscock_idx].handler_info.handler = ctrl_data_handler; epev.events = EPOLLIN; - epev.data.ptr = (void *)ctrl_data_handler; - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_dfd, &epev) == -1) { + epev.data.ptr = (void *)&(data_sock[free_dscock_idx].handler_info); + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, data_sock[free_dscock_idx].sock, &epev) == -1) { ALOGE("epoll_ctl for data connection socket failed; errno=%d", errno); - ctrl_data_close(); + ctrl_data_close(free_dscock_idx); return; } + maxevents++; } static int zoneinfo_parse_protection(char *cp) { @@ -802,7 +843,7 @@ static inline unsigned long get_time_diff_ms(struct timeval *from, (to->tv_usec - from->tv_usec) / 1000; } -static void mp_event_common(enum vmpressure_level level) { +static void mp_event_common(int data, uint32_t events __unused) { int ret; unsigned long long evcount; int64_t mem_usage, memsw_usage; @@ -811,6 +852,7 @@ static void mp_event_common(enum vmpressure_level level) { struct mem_size free_mem; static struct timeval last_report_tm; static unsigned long skip_count = 0; + enum vmpressure_level level = (enum vmpressure_level)data; /* * Check all event counters from low to critical @@ -927,26 +969,15 @@ do_kill: } } -static void mp_event_low(uint32_t events __unused) { - mp_event_common(VMPRESS_LEVEL_LOW); -} - -static void mp_event_medium(uint32_t events __unused) { - mp_event_common(VMPRESS_LEVEL_MEDIUM); -} - -static void mp_event_critical(uint32_t events __unused) { - mp_event_common(VMPRESS_LEVEL_CRITICAL); -} - -static bool init_mp_common(void *event_handler, enum vmpressure_level level) { +static bool init_mp_common(enum vmpressure_level level) { int mpfd; int evfd; int evctlfd; char buf[256]; struct epoll_event epev; int ret; - const char *levelstr = level_name[level]; + int level_idx = (int)level; + const char *levelstr = level_name[level_idx]; mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level", O_RDONLY | O_CLOEXEC); if (mpfd < 0) { @@ -972,7 +1003,7 @@ static bool init_mp_common(void *event_handler, enum vmpressure_level level) { goto err; } - ret = write(evctlfd, buf, strlen(buf) + 1); + ret = TEMP_FAILURE_RETRY(write(evctlfd, buf, strlen(buf) + 1)); if (ret == -1) { ALOGE("cgroup.event_control write failed for level %s; errno=%d", levelstr, errno); @@ -980,7 +1011,10 @@ static bool init_mp_common(void *event_handler, enum vmpressure_level level) { } epev.events = EPOLLIN; - epev.data.ptr = event_handler; + /* use data to store event level */ + vmpressure_hinfo[level_idx].data = level_idx; + vmpressure_hinfo[level_idx].handler = mp_event_common; + epev.data.ptr = (void *)&vmpressure_hinfo[level_idx]; ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, evfd, &epev); if (ret == -1) { ALOGE("epoll_ctl for level %s failed; errno=%d", levelstr, errno); @@ -1017,21 +1051,27 @@ static int init(void) { return -1; } - ctrl_lfd = android_get_control_socket("lmkd"); - if (ctrl_lfd < 0) { + // mark data connections as not connected + for (int i = 0; i < MAX_DATA_CONN; i++) { + data_sock[i].sock = -1; + } + + ctrl_sock.sock = android_get_control_socket("lmkd"); + if (ctrl_sock.sock < 0) { ALOGE("get lmkd control socket failed"); return -1; } - ret = listen(ctrl_lfd, 1); + ret = listen(ctrl_sock.sock, MAX_DATA_CONN); if (ret < 0) { ALOGE("lmkd control socket listen failed (errno=%d)", errno); return -1; } epev.events = EPOLLIN; - epev.data.ptr = (void *)ctrl_connect_handler; - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1) { + ctrl_sock.handler_info.handler = ctrl_connect_handler; + epev.data.ptr = (void *)&(ctrl_sock.handler_info); + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_sock.sock, &epev) == -1) { ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno); return -1; } @@ -1043,10 +1083,9 @@ static int init(void) { if (use_inkernel_interface) { ALOGI("Using in-kernel low memory killer interface"); } else { - if (!init_mp_common((void *)&mp_event_low, VMPRESS_LEVEL_LOW) || - !init_mp_common((void *)&mp_event_medium, VMPRESS_LEVEL_MEDIUM) || - !init_mp_common((void *)&mp_event_critical, - VMPRESS_LEVEL_CRITICAL)) { + if (!init_mp_common(VMPRESS_LEVEL_LOW) || + !init_mp_common(VMPRESS_LEVEL_MEDIUM) || + !init_mp_common(VMPRESS_LEVEL_CRITICAL)) { ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer"); return -1; } @@ -1061,12 +1100,14 @@ static int init(void) { } static void mainloop(void) { + struct event_handler_info* handler_info; + struct epoll_event *evt; + while (1) { struct epoll_event events[maxevents]; int nevents; int i; - ctrl_dfd_reopened = 0; nevents = epoll_wait(epollfd, events, maxevents, -1); if (nevents == -1) { @@ -1076,11 +1117,33 @@ static void mainloop(void) { continue; } - for (i = 0; i < nevents; ++i) { - if (events[i].events & EPOLLERR) + /* + * First pass to see if any data socket connections were dropped. + * Dropped connection should be handled before any other events + * to deallocate data connection and correctly handle cases when + * connection gets dropped and reestablished in the same epoll cycle. + * In such cases it's essential to handle connection closures first. + */ + for (i = 0, evt = &events[0]; i < nevents; ++i, evt++) { + if ((evt->events & EPOLLHUP) && evt->data.ptr) { + ALOGI("lmkd data connection dropped"); + handler_info = (struct event_handler_info*)evt->data.ptr; + ctrl_data_close(handler_info->data); + } + } + + /* Second pass to handle all other events */ + for (i = 0, evt = &events[0]; i < nevents; ++i, evt++) { + if (evt->events & EPOLLERR) ALOGD("EPOLLERR on event #%d", i); - if (events[i].data.ptr) - (*(void (*)(uint32_t))events[i].data.ptr)(events[i].events); + if (evt->events & EPOLLHUP) { + /* This case was handled in the first pass */ + continue; + } + if (evt->data.ptr) { + handler_info = (struct event_handler_info*)evt->data.ptr; + handler_info->handler(handler_info->data, evt->events); + } } } } From a92de71a51f675b054e052b4b6a444db4a22d037 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Wed, 7 Mar 2018 12:27:50 -0800 Subject: [PATCH 2/4] lmkd: Introduce liblmkd_utils for communicating with lmkd process (cherry pick from commit 0f1005180513e880bf7c90a3f2c2bf1a36aa1406) A number of tools and tests require communication with lmkd. In order to avoid code duplication liblmkd_utils implements functions commonly used when interacting with lmkd process. Isolate communication protocol details into lmkd.h Bug: 63631020 Change-Id: Id840983d55b7db60013d52dee0c3187943811822 Merged-In: Id840983d55b7db60013d52dee0c3187943811822 Signed-off-by: Suren Baghdasaryan --- lmkd/Android.bp | 15 ++++ lmkd/include/liblmkd_utils.h | 54 +++++++++++++ lmkd/include/lmkd.h | 147 +++++++++++++++++++++++++++++++++++ lmkd/liblmkd_utils.c | 76 ++++++++++++++++++ lmkd/lmkd.c | 100 ++++++++++++------------ 5 files changed, 343 insertions(+), 49 deletions(-) create mode 100644 lmkd/include/liblmkd_utils.h create mode 100644 lmkd/include/lmkd.h create mode 100644 lmkd/liblmkd_utils.c diff --git a/lmkd/Android.bp b/lmkd/Android.bp index 76d308a3c..d172755bd 100644 --- a/lmkd/Android.bp +++ b/lmkd/Android.bp @@ -6,6 +6,7 @@ cc_binary { "liblog", "libcutils", ], + local_include_dirs: ["include"], cflags: ["-Werror"], init_rc: ["lmkd.rc"], @@ -18,3 +19,17 @@ cc_binary { }, }, } + +cc_library_static { + name: "liblmkd_utils", + srcs: ["liblmkd_utils.c"], + shared_libs: [ + "libcutils", + ], + export_include_dirs: ["include"], + cppflags: [ + "-g", + "-Wall", + "-Werror", + ] +} diff --git a/lmkd/include/liblmkd_utils.h b/lmkd/include/liblmkd_utils.h new file mode 100644 index 000000000..72e3f4a2b --- /dev/null +++ b/lmkd/include/liblmkd_utils.h @@ -0,0 +1,54 @@ +/* + * Copyright 2018 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. + */ + +#ifndef _LIBLMKD_UTILS_H_ +#define _LIBLMKD_UTILS_H_ + +#include +#include + +#include + +__BEGIN_DECLS + +/* + * Connects to lmkd process and returns socket handle. + * On success returns socket handle. + * On error, -1 is returned, and errno is set appropriately. + */ +int lmkd_connect(); + +/* + * Registers a process with lmkd and sets its oomadj score. + * On success returns 0. + * On error, -1 is returned. + * In the case of error errno is set appropriately. + */ +int lmkd_register_proc(int sock, struct lmk_procprio *params); + +/* + * Creates memcg directory for given process. + * On success returns 0. + * -1 is returned if path creation failed. + * -2 is returned if tasks file open operation failed. + * -3 is returned if tasks file write operation failed. + * In the case of error errno is set appropriately. + */ +int create_memcg(uid_t uid, pid_t pid); + +__END_DECLS + +#endif /* _LIBLMKD_UTILS_H_ */ diff --git a/lmkd/include/lmkd.h b/lmkd/include/lmkd.h new file mode 100644 index 000000000..fe6364d82 --- /dev/null +++ b/lmkd/include/lmkd.h @@ -0,0 +1,147 @@ +/* + * Copyright 2018 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. + */ + +#ifndef _LMKD_H_ +#define _LMKD_H_ + +#include +#include +#include + +__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 */ +}; + +/* + * Max number of targets in LMK_TARGET command. + */ +#define MAX_TARGETS 6 + +/* + * Max packet length in bytes. + * Longest packet is LMK_TARGET followed by MAX_TARGETS + * of minfree and oom_adj_score values + */ +#define CTRL_PACKET_MAX_SIZE (sizeof(int) * (MAX_TARGETS * 2 + 1)) + +/* LMKD packet - first int is lmk_cmd followed by payload */ +typedef int LMKD_CTRL_PACKET[CTRL_PACKET_MAX_SIZE / sizeof(int)]; + +/* Get LMKD packet command */ +inline enum lmk_cmd lmkd_pack_get_cmd(LMKD_CTRL_PACKET pack) { + return (enum lmk_cmd)ntohl(pack[0]); +} + +/* LMK_TARGET packet payload */ +struct lmk_target { + int minfree; + int oom_adj_score; +}; + +/* + * For LMK_TARGET packet get target_idx-th payload. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline void lmkd_pack_get_target(LMKD_CTRL_PACKET packet, + int target_idx, struct lmk_target *target) { + target->minfree = ntohl(packet[target_idx * 2 + 1]); + target->oom_adj_score = ntohl(packet[target_idx * 2 + 2]); +} + +/* + * Prepare LMK_TARGET packet and return packet size in bytes. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline size_t lmkd_pack_set_target(LMKD_CTRL_PACKET packet, + struct lmk_target *targets, + size_t target_cnt) { + int idx = 0; + packet[idx++] = htonl(LMK_TARGET); + while (target_cnt) { + packet[idx++] = htonl(targets->minfree); + packet[idx++] = htonl(targets->oom_adj_score); + targets++; + target_cnt--; + } + return idx * sizeof(int); +} + +/* LMK_PROCPRIO packet payload */ +struct lmk_procprio { + pid_t pid; + uid_t uid; + int oomadj; +}; + +/* + * For LMK_PROCPRIO packet get its payload. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline void lmkd_pack_get_procprio(LMKD_CTRL_PACKET packet, + struct lmk_procprio *params) { + params->pid = (pid_t)ntohl(packet[1]); + params->uid = (uid_t)ntohl(packet[2]); + params->oomadj = ntohl(packet[3]); +} + +/* + * Prepare LMK_PROCPRIO packet and return packet size in bytes. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline size_t lmkd_pack_set_procprio(LMKD_CTRL_PACKET packet, + struct lmk_procprio *params) { + packet[0] = htonl(LMK_PROCPRIO); + packet[1] = htonl(params->pid); + packet[2] = htonl(params->uid); + packet[3] = htonl(params->oomadj); + return 4 * sizeof(int); +} + +/* LMK_PROCREMOVE packet payload */ +struct lmk_procremove { + pid_t pid; +}; + +/* + * For LMK_PROCREMOVE packet get its payload. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline void lmkd_pack_get_procremove(LMKD_CTRL_PACKET packet, + struct lmk_procremove *params) { + params->pid = (pid_t)ntohl(packet[1]); +} + +/* + * Prepare LMK_PROCREMOVE packet and return packet size in bytes. + * Warning: no checks performed, caller should ensure valid parameters. + */ +inline size_t lmkd_pack_set_procremove(LMKD_CTRL_PACKET packet, + struct lmk_procprio *params) { + packet[0] = htonl(LMK_PROCREMOVE); + packet[1] = htonl(params->pid); + return 2 * sizeof(int); +} + +__END_DECLS + +#endif /* _LMKD_H_ */ diff --git a/lmkd/liblmkd_utils.c b/lmkd/liblmkd_utils.c new file mode 100644 index 000000000..fa3b7a920 --- /dev/null +++ b/lmkd/liblmkd_utils.c @@ -0,0 +1,76 @@ +/* + * Copyright 2018 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +int lmkd_connect() { + return socket_local_client("lmkd", + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_SEQPACKET); +} + +int lmkd_register_proc(int sock, struct lmk_procprio *params) { + LMKD_CTRL_PACKET packet; + size_t size; + int ret; + + size = lmkd_pack_set_procprio(packet, params); + ret = TEMP_FAILURE_RETRY(write(sock, packet, size)); + + return (ret < 0) ? -1 : 0; +} + +int create_memcg(uid_t uid, pid_t pid) { + char buf[256]; + int tasks_file; + int written; + + snprintf(buf, sizeof(buf), "/dev/memcg/apps/uid_%u", uid); + if (mkdir(buf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && + errno != EEXIST) { + return -1; + } + + snprintf(buf, sizeof(buf), "/dev/memcg/apps/uid_%u/pid_%u", uid, pid); + if (mkdir(buf, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && + errno != EEXIST) { + return -1; + } + + snprintf(buf, sizeof(buf), "/dev/memcg/apps/uid_%u/pid_%u/tasks", uid, pid); + tasks_file = open(buf, O_WRONLY); + if (tasks_file < 0) { + return -2; + } + written = snprintf(buf, sizeof(buf), "%u", pid); + if (__predict_false(written >= (int)sizeof(buf))) { + written = sizeof(buf) - 1; + } + written = TEMP_FAILURE_RETRY(write(tasks_file, buf, written)); + close(tasks_file); + + return (written < 0) ? -3 : 0; +} + diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c index 2a7fedb44..45fa863b2 100644 --- a/lmkd/lmkd.c +++ b/lmkd/lmkd.c @@ -16,7 +16,6 @@ #define LOG_TAG "lowmemorykiller" -#include #include #include #include @@ -34,6 +33,7 @@ #include #include +#include #include /* @@ -71,19 +71,6 @@ #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) #define EIGHT_MEGA (1 << 23) -enum lmk_cmd { - LMK_TARGET, - LMK_PROCPRIO, - LMK_PROCREMOVE, -}; - -#define MAX_TARGETS 6 -/* - * longest is LMK_TARGET followed by MAX_TARGETS each minfree and minkillprio - * values - */ -#define CTRL_PACKET_MAX (sizeof(int) * (MAX_TARGETS * 2 + 1)) - /* default to old in-kernel interface if no memory pressure events */ static int use_inkernel_interface = 1; static bool has_inkernel_module; @@ -300,45 +287,49 @@ static void writefilestring(const char *path, char *s) { close(fd); } -static void cmd_procprio(int pid, int uid, int oomadj) { +static void cmd_procprio(LMKD_CTRL_PACKET packet) { struct proc *procp; char path[80]; char val[20]; int soft_limit_mult; + struct lmk_procprio params; - if (oomadj < OOM_SCORE_ADJ_MIN || oomadj > OOM_SCORE_ADJ_MAX) { - ALOGE("Invalid PROCPRIO oomadj argument %d", oomadj); + lmkd_pack_get_procprio(packet, ¶ms); + + if (params.oomadj < OOM_SCORE_ADJ_MIN || + params.oomadj > OOM_SCORE_ADJ_MAX) { + ALOGE("Invalid PROCPRIO oomadj argument %d", params.oomadj); return; } - snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid); - snprintf(val, sizeof(val), "%d", oomadj); + snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid); + snprintf(val, sizeof(val), "%d", params.oomadj); writefilestring(path, val); if (use_inkernel_interface) return; - if (oomadj >= 900) { + if (params.oomadj >= 900) { soft_limit_mult = 0; - } else if (oomadj >= 800) { + } else if (params.oomadj >= 800) { soft_limit_mult = 0; - } else if (oomadj >= 700) { + } else if (params.oomadj >= 700) { soft_limit_mult = 0; - } else if (oomadj >= 600) { + } else if (params.oomadj >= 600) { // Launcher should be perceptible, don't kill it. - oomadj = 200; + params.oomadj = 200; soft_limit_mult = 1; - } else if (oomadj >= 500) { + } else if (params.oomadj >= 500) { soft_limit_mult = 0; - } else if (oomadj >= 400) { + } else if (params.oomadj >= 400) { soft_limit_mult = 0; - } else if (oomadj >= 300) { + } else if (params.oomadj >= 300) { soft_limit_mult = 1; - } else if (oomadj >= 200) { + } else if (params.oomadj >= 200) { soft_limit_mult = 2; - } else if (oomadj >= 100) { + } else if (params.oomadj >= 100) { soft_limit_mult = 10; - } else if (oomadj >= 0) { + } else if (params.oomadj >= 0) { soft_limit_mult = 20; } else { // Persistent processes will have a large @@ -346,11 +337,13 @@ static void cmd_procprio(int pid, int uid, int oomadj) { soft_limit_mult = 64; } - snprintf(path, sizeof(path), "/dev/memcg/apps/uid_%d/pid_%d/memory.soft_limit_in_bytes", uid, pid); + snprintf(path, sizeof(path), + "/dev/memcg/apps/uid_%d/pid_%d/memory.soft_limit_in_bytes", + params.uid, params.pid); snprintf(val, sizeof(val), "%d", soft_limit_mult * EIGHT_MEGA); writefilestring(path, val); - procp = pid_lookup(pid); + procp = pid_lookup(params.pid); if (!procp) { procp = malloc(sizeof(struct proc)); if (!procp) { @@ -358,33 +351,38 @@ static void cmd_procprio(int pid, int uid, int oomadj) { return; } - procp->pid = pid; - procp->uid = uid; - procp->oomadj = oomadj; + procp->pid = params.pid; + procp->uid = params.uid; + procp->oomadj = params.oomadj; proc_insert(procp); } else { proc_unslot(procp); - procp->oomadj = oomadj; + procp->oomadj = params.oomadj; proc_slot(procp); } } -static void cmd_procremove(int pid) { +static void cmd_procremove(LMKD_CTRL_PACKET packet) { + struct lmk_procremove params; + if (use_inkernel_interface) return; - pid_remove(pid); + lmkd_pack_get_procremove(packet, ¶ms); + pid_remove(params.pid); } -static void cmd_target(int ntargets, int *params) { +static void cmd_target(int ntargets, LMKD_CTRL_PACKET packet) { int i; + struct lmk_target target; if (ntargets > (int)ARRAY_SIZE(lowmem_adj)) return; for (i = 0; i < ntargets; i++) { - lowmem_minfree[i] = ntohl(*params++); - lowmem_adj[i] = ntohl(*params++); + lmkd_pack_get_target(packet, i, &target); + lowmem_minfree[i] = target.minfree; + lowmem_adj[i] = target.oom_adj_score; } lowmem_targets_size = ntargets; @@ -445,38 +443,42 @@ static int ctrl_data_read(int dsock_idx, char *buf, size_t bufsz) { } static void ctrl_command_handler(int dsock_idx) { - int ibuf[CTRL_PACKET_MAX / sizeof(int)]; + LMKD_CTRL_PACKET packet; int len; - int cmd = -1; + enum lmk_cmd cmd; int nargs; int targets; - len = ctrl_data_read(dsock_idx, (char *)ibuf, CTRL_PACKET_MAX); + len = ctrl_data_read(dsock_idx, (char *)packet, CTRL_PACKET_MAX_SIZE); if (len <= 0) return; + if (len < (int)sizeof(int)) { + ALOGE("Wrong control socket read length len=%d", len); + return; + } + + cmd = lmkd_pack_get_cmd(packet); nargs = len / sizeof(int) - 1; if (nargs < 0) goto wronglen; - cmd = ntohl(ibuf[0]); - switch(cmd) { case LMK_TARGET: targets = nargs / 2; if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj)) goto wronglen; - cmd_target(targets, &ibuf[1]); + cmd_target(targets, packet); break; case LMK_PROCPRIO: if (nargs != 3) goto wronglen; - cmd_procprio(ntohl(ibuf[1]), ntohl(ibuf[2]), ntohl(ibuf[3])); + cmd_procprio(packet); break; case LMK_PROCREMOVE: if (nargs != 1) goto wronglen; - cmd_procremove(ntohl(ibuf[1])); + cmd_procremove(packet); break; default: ALOGE("Received unknown command code %d", cmd); From e708b6fc687962941e89aaea69d81a7d1244e32c Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Mon, 22 Jan 2018 16:16:06 -0800 Subject: [PATCH 3/4] lmkd: Implement lmkd-test (cherry pick from commit 5f5b30e58ba8964f78231ebf96e4ec80e7a31ecd) lmkd-test executes alloc-stress native application and monitors kernel logs for OOM kills. Bug: 63631020 Change-Id: Ia9441cc5f8e283131f0bc839cb808a911bcdb168 Merged-In: Ia9441cc5f8e283131f0bc839cb808a911bcdb168 Signed-off-by: Suren Baghdasaryan --- lmkd/tests/Android.bp | 40 +++++ lmkd/tests/lmkd_test.cpp | 373 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 413 insertions(+) create mode 100644 lmkd/tests/Android.bp create mode 100644 lmkd/tests/lmkd_test.cpp diff --git a/lmkd/tests/Android.bp b/lmkd/tests/Android.bp new file mode 100644 index 000000000..cbf44e9fc --- /dev/null +++ b/lmkd/tests/Android.bp @@ -0,0 +1,40 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// 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. + +cc_test { + name: "lmkd_unit_test", + + shared_libs: [ + "libbase", + "liblog", + ], + + static_libs: [ + "liblmkd_utils", + ], + + target: { + android: { + srcs: ["lmkd_test.cpp"], + }, + }, + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], + + compile_multilib: "first", +} diff --git a/lmkd/tests/lmkd_test.cpp b/lmkd/tests/lmkd_test.cpp new file mode 100644 index 000000000..4afaeb81f --- /dev/null +++ b/lmkd/tests/lmkd_test.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace android::base; + +#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree" +#define LMKDTEST_RESPAWN_FLAG "LMKDTEST_RESPAWN" + +#define LMKD_LOGCAT_MARKER "lowmemorykiller" +#define LMKD_KILL_MARKER_TEMPLATE LMKD_LOGCAT_MARKER ": Killing '%s'" +#define OOM_MARKER "Out of memory" +#define OOM_KILL_MARKER "Killed process" +#define MIN_LOG_SIZE 100 + +#define ONE_MB (1 << 20) + +/* Test constant parameters */ +#define OOM_ADJ_MAX 1000 +#define OOM_ADJ_MIN 0 +#define OOM_ADJ_STEP 100 +#define STEP_COUNT ((OOM_ADJ_MAX - OOM_ADJ_MIN) / OOM_ADJ_STEP + 1) + +#define ALLOC_STEP (ONE_MB) +#define ALLOC_DELAY 1000 + +/* Utility functions */ +std::string readCommand(const std::string& command) { + FILE* fp = popen(command.c_str(), "r"); + std::string content; + ReadFdToString(fileno(fp), &content); + pclose(fp); + return content; +} + +std::string readLogcat(const std::string& marker) { + std::string content = readCommand("logcat -d -b all"); + size_t pos = content.find(marker); + if (pos == std::string::npos) return ""; + content.erase(0, pos); + return content; +} + +bool writeFile(const std::string& file, const std::string& string) { + if (getuid() == static_cast(AID_ROOT)) { + return WriteStringToFile(string, file); + } + return string == readCommand( + "echo -n '" + string + "' | su root tee " + file + " 2>&1"); +} + +bool writeKmsg(const std::string& marker) { + return writeFile("/dev/kmsg", marker); +} + +std::string getTextAround(const std::string& text, size_t pos, + size_t lines_before, size_t lines_after) { + size_t start_pos = pos; + + // find start position + // move up lines_before number of lines + while (lines_before > 0 && + (start_pos = text.rfind('\n', start_pos)) != std::string::npos) { + lines_before--; + } + // move to the beginning of the line + start_pos = text.rfind('\n', start_pos); + start_pos = (start_pos == std::string::npos) ? 0 : start_pos + 1; + + // find end position + // move down lines_after number of lines + while (lines_after > 0 && + (pos = text.find('\n', pos)) != std::string::npos) { + pos++; + lines_after--; + } + return text.substr(start_pos, (pos == std::string::npos) ? + std::string::npos : pos - start_pos); +} + +bool getExecPath(std::string &path) { + char buf[PATH_MAX + 1]; + int ret = readlink("/proc/self/exe", buf, sizeof(buf) - 1); + if (ret < 0) { + return false; + } + buf[ret] = '\0'; + path = buf; + return true; +} + +/* Child synchronization primitives */ +#define STATE_INIT 0 +#define STATE_CHILD_READY 1 +#define STATE_PARENT_READY 2 + +struct state_sync { + pthread_mutex_t mutex; + pthread_cond_t condition; + int state; +}; + +struct state_sync * init_state_sync_obj() { + struct state_sync *ssync; + + ssync = (struct state_sync*)mmap(NULL, sizeof(struct state_sync), + PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); + if (ssync == MAP_FAILED) { + return NULL; + } + + pthread_mutexattr_t mattr; + pthread_mutexattr_init(&mattr); + pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); + pthread_mutex_init(&ssync->mutex, &mattr); + + pthread_condattr_t cattr; + pthread_condattr_init(&cattr); + pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); + pthread_cond_init(&ssync->condition, &cattr); + + ssync->state = STATE_INIT; + return ssync; +} + +void destroy_state_sync_obj(struct state_sync *ssync) { + pthread_cond_destroy(&ssync->condition); + pthread_mutex_destroy(&ssync->mutex); + munmap(ssync, sizeof(struct state_sync)); +} + +void signal_state(struct state_sync *ssync, int state) { + pthread_mutex_lock(&ssync->mutex); + ssync->state = state; + pthread_cond_signal(&ssync->condition); + pthread_mutex_unlock(&ssync->mutex); +} + +void wait_for_state(struct state_sync *ssync, int state) { + pthread_mutex_lock(&ssync->mutex); + while (ssync->state != state) { + pthread_cond_wait(&ssync->condition, &ssync->mutex); + } + pthread_mutex_unlock(&ssync->mutex); +} + +/* Memory allocation and data sharing */ +struct shared_data { + size_t allocated; + bool finished; + size_t total_size; + size_t step_size; + size_t step_delay; + int oomadj; +}; + +volatile void *gptr; +void add_pressure(struct shared_data *data) { + volatile void *ptr; + size_t allocated_size = 0; + + data->finished = false; + while (allocated_size < data->total_size) { + ptr = mmap(NULL, data->step_size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + if (ptr != MAP_FAILED) { + /* create ptr aliasing to prevent compiler optimizing the access */ + gptr = ptr; + /* make data non-zero */ + memset((void*)ptr, (int)(allocated_size + 1), data->step_size); + allocated_size += data->step_size; + data->allocated = allocated_size; + } + usleep(data->step_delay); + } + data->finished = (allocated_size >= data->total_size); +} + +/* Memory stress test main body */ +void runMemStressTest() { + struct shared_data *data; + struct state_sync *ssync; + int sock; + pid_t pid; + uid_t uid = getuid(); + + ASSERT_FALSE((sock = lmkd_connect()) < 0) + << "Failed to connect to lmkd process, err=" << strerror(errno); + + /* allocate shared memory to communicate params with a child */ + data = (struct shared_data*)mmap(NULL, sizeof(struct shared_data), + PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); + ASSERT_FALSE(data == MAP_FAILED) << "Memory allocation failure"; + data->total_size = (size_t)-1; /* allocate until killed */ + data->step_size = ALLOC_STEP; + data->step_delay = ALLOC_DELAY; + + /* allocate state sync object */ + ASSERT_FALSE((ssync = init_state_sync_obj()) == NULL) + << "Memory allocation failure"; + + /* run the test gradually decreasing oomadj */ + data->oomadj = OOM_ADJ_MAX; + while (data->oomadj >= OOM_ADJ_MIN) { + ASSERT_FALSE((pid = fork()) < 0) + << "Failed to spawn a child process, err=" << strerror(errno); + if (pid != 0) { + /* Parent */ + struct lmk_procprio params; + /* wait for child to start and get ready */ + wait_for_state(ssync, STATE_CHILD_READY); + params.pid = pid; + params.uid = uid; + params.oomadj = data->oomadj; + ASSERT_FALSE(lmkd_register_proc(sock, ¶ms) < 0) + << "Failed to communicate with lmkd, err=" << strerror(errno); + // signal the child it can proceed + signal_state(ssync, STATE_PARENT_READY); + waitpid(pid, NULL, 0); + if (data->finished) { + GTEST_LOG_(INFO) << "Child [pid=" << pid << "] allocated " + << data->allocated / ONE_MB << "MB"; + } else { + GTEST_LOG_(INFO) << "Child [pid=" << pid << "] allocated " + << data->allocated / ONE_MB + << "MB before being killed"; + } + data->oomadj -= OOM_ADJ_STEP; + } else { + /* Child */ + pid = getpid(); + GTEST_LOG_(INFO) << "Child [pid=" << pid + << "] is running at oomadj=" + << data->oomadj; + data->allocated = 0; + data->finished = false; + ASSERT_FALSE(create_memcg(uid, pid) != 0) + << "Child [pid=" << pid << "] failed to create a cgroup"; + signal_state(ssync, STATE_CHILD_READY); + wait_for_state(ssync, STATE_PARENT_READY); + add_pressure(data); + /* should not reach here, child should be killed by OOM/LMK */ + FAIL() << "Child [pid=" << pid << "] was not killed"; + break; + } + } + destroy_state_sync_obj(ssync); + munmap(data, sizeof(struct shared_data)); + close(sock); +} + +TEST(lmkd, check_for_oom) { + // test requirements + // userdebug build + if (!__android_log_is_debuggable()) { + GTEST_LOG_(INFO) << "Must be userdebug build, terminating test"; + return; + } + // check if in-kernel LMK driver is present + if (!access(INKERNEL_MINFREE_PATH, W_OK)) { + GTEST_LOG_(INFO) << "Must not have kernel lowmemorykiller driver," + << " terminating test"; + return; + } + + // if respawned test process then run the test and exit (no analysis) + if (getenv(LMKDTEST_RESPAWN_FLAG) != NULL) { + runMemStressTest(); + return; + } + + // Main test process + // mark the beginning of the test + std::string marker = StringPrintf( + "LMKD test start %lu\n", static_cast(time(nullptr))); + ASSERT_TRUE(writeKmsg(marker)); + + // get executable complete path + std::string test_path; + ASSERT_TRUE(getExecPath(test_path)); + + std::string test_output; + if (getuid() != static_cast(AID_ROOT)) { + // if not root respawn itself as root and capture output + std::string command = StringPrintf( + "%s=true su root %s 2>&1", LMKDTEST_RESPAWN_FLAG, + test_path.c_str()); + std::string test_output = readCommand(command); + GTEST_LOG_(INFO) << test_output; + } else { + // main test process is root, run the test + runMemStressTest(); + } + + // Analyze results + // capture logcat containind kernel logs + std::string logcat_out = readLogcat(marker); + + // 1. extract LMKD kills from logcat output, count kills + std::stringstream kill_logs; + int hit_count = 0; + size_t pos = 0; + marker = StringPrintf(LMKD_KILL_MARKER_TEMPLATE, test_path.c_str()); + + while (true) { + if ((pos = logcat_out.find(marker, pos)) != std::string::npos) { + kill_logs << getTextAround(logcat_out, pos, 0, 1); + pos += marker.length(); + hit_count++; + } else { + break; + } + } + GTEST_LOG_(INFO) << "====Logged kills====" << std::endl + << kill_logs.str(); + EXPECT_TRUE(hit_count == STEP_COUNT) << "Number of kills " << hit_count + << " is less than expected " + << STEP_COUNT; + + // 2. check kernel logs for OOM kills + pos = logcat_out.find(OOM_MARKER); + bool oom_detected = (pos != std::string::npos); + bool oom_kill_detected = (oom_detected && + logcat_out.find(OOM_KILL_MARKER, pos) != std::string::npos); + + EXPECT_FALSE(oom_kill_detected) << "OOM kill is detected!"; + if (oom_detected || oom_kill_detected) { + // capture logcat with logs around all OOMs + pos = 0; + while ((pos = logcat_out.find(OOM_MARKER, pos)) != std::string::npos) { + GTEST_LOG_(INFO) << "====Logs around OOM====" << std::endl + << getTextAround(logcat_out, pos, + MIN_LOG_SIZE / 2, MIN_LOG_SIZE / 2); + pos += strlen(OOM_MARKER); + } + } + + // output complete logcat with kernel (might get truncated) + GTEST_LOG_(INFO) << "====Complete logcat output====" << std::endl + << logcat_out; +} + From b453066e20f211fcb7edd3d1f61170588bb978fe Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Fri, 9 Mar 2018 16:45:26 -0800 Subject: [PATCH 4/4] lmkd: Fix lmkd-test to work with relative paths This change allows lmkd-test to work correctly when it's started using absolute as well as relative path. Change-Id: Ida58d13d66a224119a363f59cc59289f0167d3c9 --- lmkd/tests/lmkd_test.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lmkd/tests/lmkd_test.cpp b/lmkd/tests/lmkd_test.cpp index 4afaeb81f..f17512daf 100644 --- a/lmkd/tests/lmkd_test.cpp +++ b/lmkd/tests/lmkd_test.cpp @@ -110,14 +110,9 @@ std::string getTextAround(const std::string& text, size_t pos, } bool getExecPath(std::string &path) { - char buf[PATH_MAX + 1]; - int ret = readlink("/proc/self/exe", buf, sizeof(buf) - 1); - if (ret < 0) { - return false; - } - buf[ret] = '\0'; - path = buf; - return true; + // exec path as utf8z c_str(). + // std::string contains _all_ nul terminated argv[] strings. + return android::base::ReadFileToString("/proc/self/cmdline", &path); } /* Child synchronization primitives */ @@ -314,8 +309,8 @@ TEST(lmkd, check_for_oom) { if (getuid() != static_cast(AID_ROOT)) { // if not root respawn itself as root and capture output std::string command = StringPrintf( - "%s=true su root %s 2>&1", LMKDTEST_RESPAWN_FLAG, - test_path.c_str()); + "%s=true su root %s --gtest_filter=lmkd.check_for_oom 2>&1", + LMKDTEST_RESPAWN_FLAG, test_path.c_str()); std::string test_output = readCommand(command); GTEST_LOG_(INFO) << test_output; } else {