Merge changes Ida58d13d,Ia9441cc5,Id840983d,I73611593
* changes:
lmkd: Fix lmkd-test to work with relative paths
lmkd: Implement lmkd-test
lmkd: Introduce liblmkd_utils for communicating with lmkd process
lmkd: Add support for multiple lmkd client connections
diff --git a/lmkd/Android.bp b/lmkd/Android.bp
index 76d308a..d172755 100644
--- a/lmkd/Android.bp
+++ b/lmkd/Android.bp
@@ -6,6 +6,7 @@
"liblog",
"libcutils",
],
+ local_include_dirs: ["include"],
cflags: ["-Werror"],
init_rc: ["lmkd.rc"],
@@ -18,3 +19,17 @@
},
},
}
+
+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 0000000..72e3f4a
--- /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 <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <lmkd.h>
+
+__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 0000000..fe6364d
--- /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 <arpa/inet.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__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 0000000..fa3b7a9
--- /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 <errno.h>
+#include <fcntl.h>
+#include <sys/cdefs.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <liblmkd_utils.h>
+#include <cutils/sockets.h>
+
+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 338e5fa..45fa863 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -16,7 +16,6 @@
#define LOG_TAG "lowmemorykiller"
-#include <arpa/inet.h>
#include <errno.h>
#include <inttypes.h>
#include <sched.h>
@@ -34,6 +33,7 @@
#include <cutils/properties.h>
#include <cutils/sockets.h>
+#include <lmkd.h>
#include <log/log.h>
/*
@@ -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;
@@ -122,13 +109,30 @@
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;
@@ -283,45 +287,49 @@
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
@@ -329,11 +337,13 @@
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) {
@@ -341,33 +351,38 @@
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;
@@ -398,17 +413,24 @@
}
}
-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,39 +442,43 @@
return ret;
}
-static void ctrl_command_handler(void) {
- int ibuf[CTRL_PACKET_MAX / sizeof(int)];
+static void ctrl_command_handler(int dsock_idx) {
+ LMKD_CTRL_PACKET packet;
int len;
- int cmd = -1;
+ enum lmk_cmd cmd;
int nargs;
int targets;
- len = ctrl_data_read((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);
@@ -465,40 +491,57 @@
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 +845,7 @@
(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 +854,7 @@
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 +971,15 @@
}
}
-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 +1005,7 @@
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 +1013,10 @@
}
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 +1053,27 @@
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 +1085,9 @@
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 +1102,14 @@
}
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 +1119,33 @@
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);
+ }
}
}
}
diff --git a/lmkd/tests/Android.bp b/lmkd/tests/Android.bp
new file mode 100644
index 0000000..cbf44e9
--- /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 0000000..f17512d
--- /dev/null
+++ b/lmkd/tests/lmkd_test.cpp
@@ -0,0 +1,368 @@
+/*
+ * 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 <sstream>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <lmkd.h>
+#include <liblmkd_utils.h>
+#include <log/log_properties.h>
+#include <private/android_filesystem_config.h>
+
+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<unsigned>(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) {
+ // 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 */
+#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<unsigned long>(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<unsigned>(AID_ROOT)) {
+ // if not root respawn itself as root and capture output
+ std::string command = StringPrintf(
+ "%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 {
+ // 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;
+}
+