+/**
+ * resourced
+ *
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * 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 excontroller or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file lowmem-controller.c
+ * @desc Provides controller functionalities to kill procs/apps
+ */
+
+#include <string.h>
+#include <limits.h>
+
+#include "lowmem.h"
+#include "lowmem-controller.h"
+#include "proc-common.h"
+#include "proc-main.h"
+#include "resourced.h"
+#include "procfs.h"
+#include "trace.h"
+#include "util.h"
+#include "safe-kill.h"
+
+#define MAX_VICTIMS_BETWEEN_CHECK 3
+
+static int num_vict_between_check = MAX_VICTIMS_BETWEEN_CHECK;
+
+static int lowmem_kill_victim(const struct task_info *tsk, int flags,
+ int memps_log, unsigned int *victim_size,
+ void(*oom_popup)(void))
+{
+ pid_t pid;
+ int ret;
+ char appname[PATH_MAX];
+ int sigterm = 0;
+ struct proc_app_info *pai;
+
+ pid = tsk->pid;
+
+ if (pid <= 0 || pid == getpid())
+ return RESOURCED_ERROR_FAIL;
+
+ ret = proc_get_cmdline(pid, appname, sizeof appname);
+ if (ret == RESOURCED_ERROR_FAIL)
+ return RESOURCED_ERROR_FAIL;
+
+ if (!strcmp("memps", appname) ||
+ !strcmp("crash-worker", appname) ||
+ !strcmp("system-syspopup", appname)) {
+ _E("%s(%d) was selected, skip it", appname, pid);
+ return RESOURCED_ERROR_FAIL;
+ }
+
+ pai = tsk->pai;
+ if (pai) {
+ resourced_proc_status_change(PROC_CGROUP_SET_TERMINATE_REQUEST,
+ pid, NULL, NULL, PROC_TYPE_NONE);
+
+ if (tsk->oom_score_lru <= OOMADJ_BACKGRD_LOCKED) {
+ sigterm = 1;
+ } else if (tsk->oom_score_lru > OOMADJ_BACKGRD_LOCKED &&
+ tsk->oom_score_lru < OOMADJ_BACKGRD_UNLOCKED) {
+ int app_flag = pai->flags;
+ sigterm = app_flag & PROC_SIGTERM;
+ }
+
+ if (pai->memory.oom_killed)
+ sigterm = 0;
+
+ pai->memory.oom_killed = true;
+ }
+
+ if (sigterm)
+ safe_kill(pid, SIGTERM);
+ else
+ safe_kill(pid, SIGKILL);
+
+ _D("[LMK] we killed, force(%d), %d (%s) score = %d, size: rss = %u KB, sigterm = %d\n",
+ flags & OOM_FORCE, pid, appname, tsk->oom_score_adj,
+ tsk->size, sigterm);
+ *victim_size = tsk->size;
+
+ if (tsk->oom_score_lru > OOMADJ_FOREGRD_UNLOCKED)
+ return RESOURCED_ERROR_NONE;
+
+ oom_popup();
+
+ return RESOURCED_ERROR_NONE;
+}
+
+/* return LOWMEM_RECLAIM_CONT when killing should be continued */
+static int lowmem_check_kill_continued(struct task_info *tsk, int flags,
+ unsigned lmk_start_threshold_mb)
+{
+ unsigned int available_mb = 0;
+
+ /**
+ * Processes with the priority higher than perceptible are killed
+ * only when the available memory is less than dynamic oom threshold.
+ */
+ if (tsk->oom_score_lru > OOMADJ_BACKGRD_PERCEPTIBLE)
+ return LOWMEM_RECLAIM_CONT;
+
+ if (flags & (OOM_FORCE | OOM_SINGLE_SHOT)) {
+ _I("[LMK] %d is dropped during force kill, flag=%d",
+ tsk->pid, flags);
+ return LOWMEM_RECLAIM_DROP;
+ }
+ available_mb = proc_get_mem_available();
+ if (available_mb > lmk_start_threshold_mb) {
+ _I("[LMK] available=%d MB, larger than %u MB, do not kill foreground",
+ available_mb, lmk_start_threshold_mb);
+ return LOWMEM_RECLAIM_RETRY;
+ }
+ return LOWMEM_RECLAIM_CONT;
+}
+
+int lowmem_controller_kill_candidates(GArray *candidates,
+ unsigned should_be_freed, unsigned int threshold,
+ int max_victims, int flags,
+ int *status, unsigned int *total_victim_size,
+ unsigned lmk_start_threshold_mb,
+ void(*oom_popup)(void))
+{
+ int ret_kill = 0;
+ int victim_cnt = 0;
+ unsigned int victim_size = 0;
+ unsigned int victim_size_sum = 0;
+ int killer_status = *status;
+ int should_be_freed_kb = MBYTE_TO_KBYTE(should_be_freed);
+
+ for (int i = 0; i < candidates->len; i++) {
+ struct task_info *tsk;
+
+ if (i >= max_victims) {
+ killer_status = LOWMEM_RECLAIM_NEXT_TYPE;
+ break;
+ }
+
+ /**
+ * Available memory is checking only every
+ * num_vict_between_check process for reducing burden.
+ */
+ if (!(i % num_vict_between_check) &&
+ proc_get_mem_available() > threshold) {
+ killer_status = LOWMEM_RECLAIM_DONE;
+ break;
+ }
+
+ if (!(flags & OOM_NOMEMORY_CHECK) &&
+ victim_size_sum >= should_be_freed_kb) {
+ _D("[LMK] victim=%d, max_victims=%d, total_size=%uKB",
+ victim_cnt, max_victims, victim_size_sum);
+ killer_status = LOWMEM_RECLAIM_DONE;
+ break;
+ }
+
+ tsk = &g_array_index(candidates, struct task_info, i);
+
+ killer_status = lowmem_check_kill_continued(tsk, flags,
+ lmk_start_threshold_mb);
+ if (killer_status != LOWMEM_RECLAIM_CONT)
+ break;
+
+ _I("[LMK] select victims from proc_app_list pid(%d) with oom_score_adj(%d)\n",
+ tsk->pid, tsk->oom_score_adj);
+
+ ret_kill = lowmem_kill_victim(tsk, flags, i, &victim_size,
+ oom_popup);
+ if (ret_kill != RESOURCED_ERROR_NONE)
+ continue;
+ victim_cnt++;
+ victim_size_sum += victim_size;
+ }
+
+ *status = killer_status;
+ *total_victim_size = victim_size_sum;
+
+ return victim_cnt;
+}