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 <jji@google.com>
diff --git a/lmkd.c b/lmkd.c
index 4352498..1f94e57 100644
--- a/lmkd.c
+++ b/lmkd.c
@@ -200,7 +200,7 @@
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 @@
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 @@
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 @@
}
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
} 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++) {