#include <sys/types.h>
#include <sys/stat.h>
#include <sys/shm.h>
-#include <sys/eventfd.h>
#include <sys/sysinfo.h>
#include <Ecore.h>
#include <sys/time.h>
#include <bundle.h>
#include <eventsystem.h>
-#include "freezer.h"
#include "trace.h"
#include "cgroup.h"
#include "lowmem-handler.h"
#include "proc-common.h"
#include "procfs.h"
+#include "freezer.h"
#include "resourced.h"
#include "macro.h"
#include "notifier.h"
#include "heart-common.h"
#include "proc-main.h"
#include "edbus-handler.h"
+#include "util.h"
-#define LOWMEM_DEFAULT_CGROUP "/sys/fs/cgroup/memory"
#define LOWMEM_NO_LIMIT 0
#define LOWMEM_THRES_INIT 0
#define MEMCG_MOVE_CHARGE_PATH "memory.move_charge_at_immigrate"
-#define MEMCG_OOM_CONTROL_PATH "memory.oom_control"
-#define MEMCG_LIMIT_PATH "memory.limit_in_bytes"
-#define MEMCG_EVENTFD_CONTROL "cgroup.event_control"
#define MEMCG_EVENTFD_MEMORY_PRESSURE "memory.pressure_level"
#define MEM_CONF_FILE RD_CONFIG_FILE(memory)
#define MEM_VIP_SECTION "VIP_PROCESS"
#define MEM_POPUP_SECTION "POPUP"
#define MEM_POPUP_STRING "oom_popup"
-#define BtoMB(x) ((x) >> 20)
-#define BtoKB(x) ((x) >> 10)
-#define KBtoMB(x) ((x) >> 10)
-#define BtoPAGE(x) ((x) >> 12)
-#define MBtoKB(x) ((x) << 10)
-
#define BUF_MAX 1024
#define MAX_MEMORY_CGROUP_VICTIMS 10
-#define MAX_CGROUP_VICTIMS 1
+#define MAX_VICTIMS_BETWEEN_CHECK 3
#define OOM_TIMER_INTERVAL 2
#define OOM_KILLER_PRIORITY -20
#define MAX_FD_VICTIMS 10
-#define MAX_FGRD_KILL 3
+#define NUM_RM_LOGS 5
#define THRESHOLD_MARGIN 10 /* MB */
#define MEM_SIZE_64 64 /* MB */
static unsigned dynamic_threshold_adj_gap;
static unsigned dynamic_oom_threshold;
-struct task_info {
- pid_t pid;
- pid_t pgid;
- int oom_score_adj;
- int size;
+static char *event_level = MEMCG_DEFAULT_EVENT_LEVEL;
+
+/**
+ * Resourced Low Memory Killer
+ * NOTE: planned to be moved to a separate file.
+ */
+/*-------------------------------------------------*/
+#define OOM_TIMER_INTERVAL_SEC 2
+#define LMW_LOOP_WAIT_TIMEOUT_MSEC OOM_TIMER_INTERVAL_SEC*(G_USEC_PER_SEC)
+#define LMW_RETRY_WAIT_TIMEOUT_MSEC (G_USEC_PER_SEC)
+
+enum lmk_type {
+ LMK_MEMORY, /* Kill all range of apps OOMADJ_INIT ~ OOMADJ_APP_MAX */
+ LMK_ACTIVE, /* Kill active apps OOMADJ_BACKGRD_PERCEPTIBLE ~ OOMADJ_BACKGRD_LOCKED */
+ LMK_INACTIVE, /* Kill only inactive processes OOMADJ_FAVORITE ~ OOMADJ_APP_MAX */
+};
+
+struct lowmem_control {
+ /*
+ * For each queued request the following properties
+ * are required with two exceptions:
+ * - status is being set by LMK
+ * - callback is optional
+ */
+ /* Processing flags*/
+ unsigned int flags;
+ /* Indictator for OOM score of targeted processes */
+ enum lmk_type type;
+ /* Desired size to be restored - level to be reached (MB)*/
+ unsigned int size;
+ /* Max number of processes to be considered */
+ unsigned int count;
+ /* Memory reclaim status */
+ int status;
+ /*
+ * Optional - if set, will be triggered by LMK once the request
+ * is handled.
+ */
+ void (*callback) (struct lowmem_control *);
};
-struct lowmem_process_entry {
- int cur_mem_state;
- int new_mem_state;
- void (*action) (void);
+struct lowmem_worker {
+ pthread_t worker_thread;
+ GAsyncQueue *queue;
+ int active;
+ int running;
};
+static struct lowmem_worker lmw;
+
+#define LOWMEM_WORKER_IS_ACTIVE(_lmw) g_atomic_int_get(&(_lmw)->active)
+#define LOWMEM_WORKER_ACTIVATE(_lmw) g_atomic_int_set(&(_lmw)->active, 1)
+#define LOWMEM_WORKER_DEACTIVATE(_lmw) g_atomic_int_set(&(_lmw)->active, 0)
+
+#define LOWMEM_WORKER_IS_RUNNING(_lmw) g_atomic_int_get(&(_lmw)->running)
+#define LOWMEM_WORKER_RUN(_lmw) g_atomic_int_set(&(_lmw)->running, 1)
+#define LOWMEM_WORKER_IDLE(_lmw) g_atomic_int_set(&(_lmw)->running, 0)
+
+#define LOWMEM_NEW_REQUEST() g_slice_new0(struct lowmem_control)
+
+#define LOWMEM_DESTROY_REQUEST(_ctl) \
+ g_slice_free(typeof(*(_ctl)), _ctl); \
+
+#define LOWMEM_SET_REQUEST(c, __flags, __type, __size, __count, __cb) \
+{ \
+ (c)->flags = __flags; (c)->type = __type; \
+ (c)->size = __size; (c)->count = __count; \
+ (c)->callback = __cb; \
+}
+
+static void lowmem_queue_request(struct lowmem_worker *lmw,
+ struct lowmem_control *ctl)
+{
+ if (LOWMEM_WORKER_IS_ACTIVE(lmw))
+ g_async_queue_push(lmw->queue, ctl);
+}
+
+/* internal */
+static void lowmem_drain_queue(struct lowmem_worker *lmw)
+{
+ struct lowmem_control *ctl;
+
+ g_async_queue_lock(lmw->queue);
+ while ((ctl = g_async_queue_try_pop_unlocked(lmw->queue))) {
+ if (ctl->callback)
+ ctl->callback(ctl);
+ LOWMEM_DESTROY_REQUEST(ctl);
+ }
+ g_async_queue_unlock(lmw->queue);
+}
+
+static void lowmem_request_destroy(gpointer data)
+{
+ struct lowmem_control *ctl = (struct lowmem_control*) data;
+
+ if (ctl->callback)
+ ctl->callback(ctl);
+ LOWMEM_DESTROY_REQUEST(ctl);
+}
+
+/*-------------------------------------------------*/
+
/* low memory action function for cgroup */
-static void memory_cgroup_medium_act(int type, struct memcg_info *mi);
+static void memory_cgroup_medium_act(enum lmk_type type, struct memcg_info *mi);
/* low memory action function */
static void normal_act(void);
static void swap_act(void);
static void low_act(void);
static void medium_act(void);
-static Eina_Bool medium_cb(void *data);
-
-#define LOWMEM_ENTRY(c, n, act) \
- { LOWMEM_##c, LOWMEM_##n, act}
-
-static struct lowmem_process_entry lpe[] = {
- LOWMEM_ENTRY(NORMAL, SWAP, swap_act),
- LOWMEM_ENTRY(NORMAL, LOW, low_act),
- LOWMEM_ENTRY(NORMAL, MEDIUM, medium_act),
- LOWMEM_ENTRY(SWAP, NORMAL, normal_act),
- LOWMEM_ENTRY(SWAP, LOW, low_act),
- LOWMEM_ENTRY(SWAP, MEDIUM, medium_act),
- LOWMEM_ENTRY(LOW, SWAP, swap_act),
- LOWMEM_ENTRY(LOW, NORMAL, normal_act),
- LOWMEM_ENTRY(LOW, MEDIUM, medium_act),
- LOWMEM_ENTRY(MEDIUM, SWAP, swap_act),
- LOWMEM_ENTRY(MEDIUM, NORMAL, normal_act),
- LOWMEM_ENTRY(MEDIUM, LOW, low_act),
-};
static int cur_mem_state = LOWMEM_NORMAL;
-static Ecore_Timer *oom_check_timer;
static int num_max_victims = MAX_MEMORY_CGROUP_VICTIMS;
-
-static pthread_t oom_thread;
-static pthread_mutex_t oom_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_cond_t oom_cond = PTHREAD_COND_INITIALIZER;
+static int num_vict_between_check = MAX_VICTIMS_BETWEEN_CHECK;
static unsigned long totalram;
static unsigned long ktotalram;
-static int fg_killed;
-static struct module_ops memory_modules_ops;
-static struct module_ops *lowmem_ops;
+static const struct module_ops memory_modules_ops;
+static const struct module_ops *lowmem_ops;
static bool oom_popup_enable;
static bool oom_popup;
-static const char *memcg_name[MEMCG_MAX] = {
- NULL,
- "platform",
- "foreground",
- "previous",
- "favorite",
- "background",
- "swap",
+static struct memcg_info gmi[MEMCG_MAX] = {
+ {LOWMEM_ROOT_CGROUP, "/", MEMCG_ROOT,},
+ {LOWMEM_APPS_CGROUP, "Apps", MEMCG_MEMORY,},
+ {LOWMEM_MEMLIMIT_CGROUP, "MemLimit", MEMCG_MEMORY,},
+ {LOWMEM_SWAP_CGROUP, "Swap", MEMCG_MEMORY,},
};
-static bool memcg_swap_status[MEMCG_MAX] = {false, };
-
enum memory_level {
MEMORY_LEVEL_NORMAL,
MEMORY_LEVEL_LOW,
static GPtrArray *vip_apps;
-static char *convert_memstate_to_str(int mem_state)
+static const char *convert_memstate_to_str(int mem_state)
{
- char *tmp = NULL;
- switch (mem_state) {
- case LOWMEM_NORMAL:
- tmp = "mem normal";
- break;
- case LOWMEM_SWAP:
- tmp = "mem swap";
- break;
- case LOWMEM_LOW:
- tmp = "mem low";
- break;
- case LOWMEM_MEDIUM:
- tmp = "mem medium";
- break;
- default:
- assert(0);
- }
- return tmp;
+ static const char *state_table[] = {"mem normal", "mem swap", "mem low",
+ "mem medium"};
+ if (mem_state >= 0 && mem_state < LOWMEM_MAX_LEVEL)
+ return state_table[mem_state];
+ return "";
}
-static void adjust_dynamic_threshold(int victim_memcg)
+static void adjust_dynamic_threshold(enum lmk_type lmk_type)
{
unsigned prev_dynamic_threshold = dynamic_oom_threshold;
unsigned available;
break;
case LOWMEM_SWAP:
case LOWMEM_LOW:
- if (victim_memcg <= MEMCG_FAVORITE) {
+ if (lmk_type < LMK_INACTIVE) {
dynamic_oom_threshold -= dynamic_threshold_adj_gap;
break;
}
dynamic_oom_threshold = memcg_root->threshold[LOWMEM_MEDIUM];
break;
case LOWMEM_MEDIUM:
- if (victim_memcg <= MEMCG_FAVORITE)
+ if (lmk_type < LMK_INACTIVE)
dynamic_oom_threshold -= dynamic_threshold_adj_gap;
if (dynamic_oom_threshold < dynamic_threshold_min)
break;
}
- _I("dynamic_threshold is changed from %u to %u, cur_mem_state = %s, victim_memcg = %d",
+ _I("dynamic_threshold is changed from %u to %u, cur_mem_state = %s, lmk_type = %d",
prev_dynamic_threshold, dynamic_oom_threshold,
convert_memstate_to_str(cur_mem_state),
- victim_memcg);
+ lmk_type);
}
static int lowmem_launch_oompopup(void)
if (!sysinfo(&si)) {
totalram = si.totalram;
- ktotalram = BtoKB(totalram);
+ ktotalram = BYTE_TO_KBYTE(totalram);
}
}
-static int get_proc_mem_usage(pid_t pid, unsigned int *usage)
+static int lowmem_mem_usage_uss(pid_t pid, unsigned int *usage)
{
unsigned int uss, zram = 0;
int ret;
return RESOURCED_ERROR_NONE;
}
-static int lowmem_check_current_state(struct memcg_info *mi)
+static int lowmem_mem_usage_rss(pid_t pid, unsigned int *usage)
{
- unsigned long long usage, oomleave;
+ unsigned int rss, swap;
int ret;
- oomleave = (unsigned long long)(mi->oomleave);
- ret = memcg_get_anon_usage(mi, &usage);
+ *usage = 0;
- if (ret) {
- _D("getting anonymous usage fails");
+ ret = proc_get_mem_usage(pid, &swap, &rss);
+ if (ret != RESOURCED_ERROR_NONE)
return ret;
- }
- if (oomleave > usage) {
- _D("%s : usage : %llu, leave threshold : %llu",
- __func__, usage, oomleave);
- return RESOURCED_ERROR_NONE;
- } else {
- _D("%s : usage : %llu, leave threshold: %llu",
- __func__, usage, oomleave);
- return RESOURCED_ERROR_FAIL;
- }
+ *usage = rss + swap;
+
+ return RESOURCED_ERROR_NONE;
}
-static int lowmem_get_task_info_array_for_memcg(struct memcg_info *mi, GArray *tasks_array)
+unsigned int lowmem_get_task_mem_usage_rss(const struct task_info *tsk)
{
- int pid_idx, tsk_idx;
- char appname[BUF_MAX] = {0, };
+ unsigned int size = 0, total_size = 0;
+ int index, ret;
+ pid_t pid;
- GArray *pids_array = g_array_new(false, false, sizeof(pid_t));
- memcg_get_pids(mi, pids_array);
+ /*
+ * If pids are allocated only when there are multiple processes with
+ * the same pgid e.g., browser and web process. Mostly, single process
+ * is used.
+ */
+ if (tsk->pids == NULL) {
+ ret = lowmem_mem_usage_rss(tsk->pid, &size);
- if (pids_array->len == 0)
- /*
- * if task read in this cgroup fails,
- * return the current number of victims
+ /* If there is no proc entry for given pid the process
+ * should be abandoned during further processing
*/
- return tasks_array->len;
+ if (ret < 0)
+ _D("failed to get rss memory usage of %d", tsk->pid);
- for (pid_idx = 0; pid_idx < pids_array->len; pid_idx++) {
- pid_t tpid = 0;
- int toom = 0;
- unsigned int tsize = 0;
-
- tpid = g_array_index(pids_array, pid_t, pid_idx);
+ return size;
+ }
- if (proc_get_oom_score_adj(tpid, &toom) < 0 ||
- toom <= OOMADJ_SERVICE_MIN) {
- _D("pid(%d) was already terminated or high priority oom = %d",
- tpid, toom);
+ for (index = 0; index < tsk->pids->len; index++) {
+ pid = g_array_index(tsk->pids, pid_t, index);
+ ret = lowmem_mem_usage_rss(pid, &size);
+ if (ret != RESOURCED_ERROR_NONE)
continue;
- }
+ total_size += size;
+ }
- if (get_proc_mem_usage(tpid, &tsize) < 0) {
- _D("pid(%d) size is not available\n", tpid);
- continue;
- }
+ return total_size;
+}
- if (proc_get_cmdline(tpid, appname) < 0)
- continue;
+static unsigned int get_task_mem_usage_uss(const struct task_info *tsk)
+{
+ unsigned int size = 0, total_size = 0;
+ int index, ret;
+ pid_t pid;
- for (tsk_idx = 0; tsk_idx < tasks_array->len; tsk_idx++) {
- struct task_info *tsk = &g_array_index(tasks_array,
- struct task_info, tsk_idx);
- if (getpgid(tpid) == tsk->pgid) {
- tsk->size += tsize;
- if (tsk->oom_score_adj <= 0 && toom > 0) {
- tsk->pid = tpid;
- tsk->oom_score_adj = toom;
- }
- break;
- }
- }
+ /*
+ * If pids are allocated only when there are multiple processes with
+ * the same pgid e.g., browser and web process. Mostly, single process
+ * is used.
+ */
+ if (tsk->pids == NULL) {
+ ret = lowmem_mem_usage_uss(tsk->pid, &size);
- if (tsk_idx == tasks_array->len) {
- struct task_info tsk;
- tsk.pid = tpid;
- tsk.pgid = getpgid(tpid);
- tsk.oom_score_adj = toom;
- tsk.size = tsize;
+ if (ret < 0)
+ _D("failed to get uss memory usage of %d", tsk->pid);
- g_array_append_val(tasks_array, tsk);
- }
+ return size;
+ }
+ for (index = 0; index < tsk->pids->len; index++) {
+ pid = g_array_index(tsk->pids, pid_t, index);
+ ret = lowmem_mem_usage_uss(pid, &size);
+ if (ret != RESOURCED_ERROR_NONE)
+ continue;
+ total_size += size;
}
- g_array_free(pids_array, TRUE);
- return tasks_array->len;
+ return total_size;
+}
+
+int compare_func(const struct dirent **a, const struct dirent **b)
+{
+ const char *fn_a = (*a)->d_name;
+ const char *fn_b = (*b)->d_name;
+ char *str_at = strrchr(fn_a, '_') + 1;
+ char *str_bt = strrchr(fn_b, '_') + 1;
+
+ return strcmp(str_at, str_bt);
}
-static void lowmem_kill_victim(struct task_info *tsk,
- int flags, unsigned int *total_size)
+static int lowmem_kill_victim(const struct task_info *tsk,
+ int flags, unsigned int *victim_size)
{
pid_t pid;
int ret;
- unsigned int total = 0;
char appname[PATH_MAX];
- int sigterm;
+ int sigterm = 0;
+ unsigned int size;
+ struct proc_app_info *pai;
pid = tsk->pid;
if (pid <= 0 || pid == getpid())
- return;
+ return RESOURCED_ERROR_FAIL;
ret = proc_get_cmdline(pid, appname);
if (ret == RESOURCED_ERROR_FAIL)
- return;
+ return RESOURCED_ERROR_FAIL;
- if (!strncmp("memps", appname, strlen(appname)+1) ||
- !strncmp("crash-worker", appname, strlen(appname)+1) ||
- !strncmp("system-syspopup", appname, strlen(appname)+1)) {
+ if (!strcmp("crash-worker", appname) ||
+ !strcmp("system-syspopup", appname)) {
_E("%s(%d) was selected, skip it", appname, pid);
- return;
+ return RESOURCED_ERROR_FAIL;
}
- ret = get_proc_mem_usage(tsk->pid, &total);
- if (ret != RESOURCED_ERROR_NONE) {
- _E("Failed to get memory usage : %d", ret);
- return;
- }
- total += *total_size;
+ size = get_task_mem_usage_uss(tsk);
+ if (size == 0)
+ return ret;
- resourced_proc_status_change(PROC_CGROUP_SET_TERMINATE_REQUEST,
- pid, NULL, NULL, PROC_TYPE_NONE);
+ pai = find_app_info(pid);
- if (tsk->oom_score_adj < OOMADJ_BACKGRD_LOCKED) {
- sigterm = 1;
- } else if (tsk->oom_score_adj == OOMADJ_BACKGRD_LOCKED) {
- int app_flag = proc_get_appflag(pid);
- sigterm = app_flag & PROC_SIGTERM;
- } else
- sigterm = 0;
+ if (pai) {
+ resourced_proc_status_change(PROC_CGROUP_SET_TERMINATE_REQUEST,
+ pid, NULL, NULL, PROC_TYPE_NONE);
+
+ if (tsk->oom_score_adj < OOMADJ_BACKGRD_LOCKED) {
+ sigterm = 1;
+ } else if (tsk->oom_score_adj == OOMADJ_BACKGRD_LOCKED) {
+ int app_flag = pai->flags;
+ sigterm = app_flag & PROC_SIGTERM;
+ }
+
+ if (pai->memory.oom_killed)
+ sigterm = 0;
+
+ pai->memory.oom_killed = true;
+ }
if (sigterm)
kill(pid, SIGTERM);
else
kill(pid, SIGKILL);
- _E("we killed, force(%d), %d (%s) score = %d, size = %u KB, victim total size = %u KB, sigterm = %d\n",
- flags & OOM_FORCE, pid, appname, tsk->oom_score_adj,
- tsk->size, total, sigterm);
- *total_size = total;
+ _E("we killed, force(%d), %d (%s) score = %d, size: rss = %u uss = %u KB, sigterm = %d\n",
+ flags & OOM_FORCE, pid, appname, tsk->oom_score_adj,
+ tsk->size, size, sigterm);
+ *victim_size = size;
if (tsk->oom_score_adj > OOMADJ_FOREGRD_UNLOCKED)
- return;
+ return RESOURCED_ERROR_NONE;
if (oom_popup_enable && !oom_popup) {
lowmem_launch_oompopup();
oom_popup = true;
}
+
+ return RESOURCED_ERROR_NONE;
}
-/* return RESOURCED_ERROR_NONE when kill should be continued */
+/* return LOWMEM_RECLAIM_CONT when killing should be continued */
static int lowmem_check_kill_continued(struct task_info *tsk, int flags)
{
unsigned int available;
* only when the available memory is less than dynamic oom threshold.
*/
if (tsk->oom_score_adj > OOMADJ_BACKGRD_PERCEPTIBLE)
- return RESOURCED_ERROR_NONE;
+ return LOWMEM_RECLAIM_CONT;
- if ((flags & OOM_FORCE) || !(flags & OOM_TIMER_CHECK)) {
- _I("%d is skipped during force kill, flag = %d",
+ if ((flags & (OOM_FORCE|OOM_SINGLE_SHOT)) || !(flags & OOM_REVISE)) {
+ _I("%d is dropped during force kill, flag = %d",
tsk->pid, flags);
- return RESOURCED_ERROR_FAIL;
+ return LOWMEM_RECLAIM_DROP;
}
available = proc_get_mem_available();
if (available > dynamic_oom_threshold) {
_I("available: %d MB, larger than %u MB, do not kill foreground",
available, dynamic_oom_threshold);
- return RESOURCED_ERROR_FAIL;
+ return LOWMEM_RECLAIM_WAIT;
}
- return RESOURCED_ERROR_NONE;
+ return LOWMEM_RECLAIM_CONT;
}
static int compare_victims(const struct task_info *ta, const struct task_info *tb)
if (ta->oom_score_adj != tb->oom_score_adj)
return tb->oom_score_adj - ta->oom_score_adj;
+ /*
+ * Get memory usage for tasks with the same oom score.
+ * Since there could be multiple tasks with the same pgid,
+ * we sum all of the tasks.
+ * Even though we calculate memory usage when we should compare it,
+ * there could be frequent cases to get memory usage of tasks that
+ * would not be selected as victim. Therefore we just use RSS + Swap,
+ * which causes much less overhead.
+ */
return (int)(tb->size) - (int)(ta->size);
}
static int compare_victims_point(const struct task_info *ta, const struct task_info *tb)
{
unsigned int pa, pb;
+
assert(ta != NULL);
assert(tb != NULL);
- /*
- * followed by kernel badness point calculation using heuristic.
- * oom_score_adj is normalized by its unit, which varies -1000 ~ 1000.
- * Since we only consider tasks with oom_score_adj larger than 0
- * as victim candidates, point always has positive value.
- */
pa = ta->oom_score_adj * (ktotalram / 1000) + ta->size;
pb = tb->oom_score_adj * (ktotalram / 1000) + tb->size;
return pb - pa;
}
-static int lowmem_kill_cgroup_victims(int type, struct memcg_info *mi,
- int max_victims, unsigned should_be_freed, int flags,
- unsigned int *total_size, int *completed)
+static void lowmem_free_task_info_array(GArray *array)
{
- int i, ret, victim = 0, count = 0;
- unsigned total_victim_size = 0;
- GArray *candidates = NULL;
-
- candidates = g_array_new(false, false, sizeof(struct task_info));
-
- /* if g_array_new fails, return the current number of victims */
- if (candidates == NULL)
- return victim;
-
- /*
- * if there is no tasks in this cgroup,
- * return the current number of victims
- */
- count = lowmem_get_task_info_array_for_memcg(mi, candidates);
- if (count == 0) {
- g_array_free(candidates, true);
- return victim;
- }
-
- g_array_sort(candidates,
- (GCompareFunc)compare_victims);
+ int i;
- for (i = 0; i < candidates->len; i++) {
+ for (i = 0; i < array->len; i++) {
struct task_info *tsk;
- if (i >= max_victims ||
- (!(flags & OOM_NOMEMORY_CHECK) &&
- total_victim_size >= MBtoKB(should_be_freed))) {
- _E("victim = %d, max_victims = %d, total_size = %u",
- i, max_victims, total_victim_size);
- break;
- }
-
- tsk = &g_array_index(candidates, struct task_info, i);
-
- ret = lowmem_check_kill_continued(tsk, flags);
- if (ret == RESOURCED_ERROR_FAIL && completed) {
- _E("checked kill continued and completed");
- *completed = 1;
- break;
- }
- lowmem_kill_victim(tsk, flags, &total_victim_size);
+ tsk = &g_array_index(array, struct task_info, i);
+ if (tsk->pids)
+ g_array_free(tsk->pids, true);
}
- victim = i;
- g_array_free(candidates, true);
- *total_size = total_victim_size;
-
- return victim;
+ g_array_free(array, true);
}
static inline int is_dynamic_process_killer(int flags)
return (flags & OOM_FORCE) && !(flags & OOM_NOMEMORY_CHECK);
}
-static int lowmem_kill_subcgroup_victims(int type, int max_victims, int flags,
- unsigned int *total_size, int *completed)
-{
- GSList *iter = NULL;
- GArray *candidates = NULL;
- int i, ret, victim = 0;
- unsigned int total_victim_size = 0;
- struct task_info *tsk;
-
- candidates = g_array_new(false, false, sizeof(struct task_info));
- gslist_for_each_item(iter, memcg_tree[type]->cgroups) {
- struct memcg_info *mi =
- (struct memcg_info *)(iter->data);
- int count = lowmem_get_task_info_array_for_memcg(mi, candidates);
- _D("get %d pids", count);
- }
-
- g_array_sort(candidates, (GCompareFunc)compare_victims);
-
- for (i = 0; i < candidates->len; i++) {
- if (i == max_victims)
- break;
-
- tsk = &g_array_index(candidates, struct task_info, i);
-
- ret = lowmem_check_kill_continued(tsk, flags);
- if (ret == RESOURCED_ERROR_FAIL)
- break;
-
- lowmem_kill_victim(tsk, flags, &total_victim_size);
- }
-
- victim = i;
- g_array_free(candidates, true);
- return victim;
-}
-
static unsigned int is_memory_recovered(unsigned int *avail, unsigned int *thres)
{
unsigned int available = proc_get_mem_available();
_E("fail to open /proc");
return RESOURCED_ERROR_FAIL;
}
- while ((dentry = readdir(dp))) {
+ while ((dentry = readdir(dp)) != NULL) {
struct task_info tsk;
pid_t pid = 0, pgid = 0;
int oom = 0;
- unsigned int size = 0;
if (!isdigit(dentry->d_name[0]))
continue;
pid = (pid_t)atoi(dentry->d_name);
- if (pid < 0)
- continue;
-
pgid = getpgid(pid);
- if (pgid < 0)
+ if (!pgid)
continue;
if (proc_get_oom_score_adj(pid, &oom) < 0) {
continue;
}
- if (get_proc_mem_usage(pid, &size) < 0) {
- _D("pid(%d) size is not available\n", pid);
- continue;
- }
-
if (proc_get_cmdline(pid, appname) < 0)
continue;
+ /*
+ * Currently, for tasks in the memory cgroup,
+ * do not consider multiple tasks with one pgid.
+ */
tsk.pid = pid;
tsk.pgid = pgid;
tsk.oom_score_adj = oom;
- tsk.size = size;
+ tsk.pids = NULL;
+ tsk.size = lowmem_get_task_mem_usage_rss(&tsk);
g_array_append_val(pids, tsk);
}
closedir(dp);
-
- if (pids->len)
- return pids->len;
- return RESOURCED_ERROR_FAIL;
+ return RESOURCED_ERROR_NONE;
}
-static int lowmem_kill_memory_cgroup_victims(int flags)
+static int lowmem_kill_victims_from_proc(int flags, int *completed, int threshold)
{
GArray *candidates = NULL;
- int i, count, victim = 0;
- unsigned int av, total_victim_size = 0;
+ int i, ret, victim = 0;
+ unsigned int victim_size = 0;
+ unsigned int total_size = 0;
+ unsigned int available = 0;
struct task_info *tsk;
candidates = g_array_new(false, false, sizeof(struct task_info));
if (candidates == NULL)
return victim;
- count = lowmem_get_pids_proc(candidates);
+ ret = lowmem_get_pids_proc(candidates);
- if (count <= 0)
- return victim;
+ if (ret != RESOURCED_ERROR_NONE || !candidates->len)
+ goto leave;
g_array_sort(candidates, (GCompareFunc)compare_victims_point);
_I("start to kill for memory cgroup");
+ available = proc_get_mem_available();
+
for (i = 0; i < candidates->len; i++) {
- tsk = &g_array_index(candidates, struct task_info, i);
+ if (i >= num_max_victims)
+ break;
- av = proc_get_mem_available();
+ /*
+ * Available memory is checking only every
+ * num_vict_between_check process for reducing burden.
+ */
- if (av > dynamic_oom_threshold || i >= num_max_victims) {
- _I("checking proc, available: %d MB, larger than threshold margin", av);
- g_array_free(candidates, true);
- victim = i;
- return victim;
+ if (!(i % num_vict_between_check)) {
+ if (proc_get_mem_available() > threshold) {
+ *completed = LOWMEM_RECLAIM_DONE;
+ break;
+ }
}
- lowmem_kill_victim(tsk, flags, &total_victim_size);
+
+ tsk = &g_array_index(candidates, struct task_info, i);
+
+ if (available + total_size > dynamic_oom_threshold)
+ break;
+ ret = lowmem_kill_victim(tsk, flags, &victim_size);
+ if (ret != RESOURCED_ERROR_NONE)
+ continue;
+ victim++;
+ total_size += KBYTE_TO_MBYTE(victim_size);
+ }
+
+ if (available + total_size > dynamic_oom_threshold) {
+ _I("Reached the threshold margin: available mem: %d MB\n",
+ available);
+ *completed = LOWMEM_RECLAIM_DONE;
+ } else {
+ _I("Failed to reach the threshold margin: available mem: %d "
+ "with %d processes killed\n", available, victim);
+ *completed = LOWMEM_RECLAIM_CONT;
}
- victim = i;
- g_array_free(candidates, true);
+
+leave:
+ lowmem_free_task_info_array(candidates);
+
return victim;
}
-/* Find victims: (SWAP -> ) BACKGROUND */
-static int lowmem_kill_all_cgroup_victims(int flags, int *completed)
+/**
+ * @brief Terminate up to max_victims processes after finding them from pai.
+ It depends on proc_app_info lists
+ and it also reference systemservice cgroup
+ because some processes in this group don't have proc_app_info.
+ *
+ * @max_victims: max number of processes to be terminated
+ * @start_oom: find victims from start oom adj score value
+ * @end_oom: find victims to end oom adj score value
+ * @should_be_freed: amount of memory to be reclaimed (in MB)
+ * @total_size[out]: total size of possibly reclaimed memory (required)
+ * @completed: final outcome (optional)
+ * @threshold: desired value of memory available
+ */
+static int lowmem_kill_victims(int max_victims,
+ int start_oom, int end_oom, unsigned should_be_freed, int flags,
+ unsigned int *total_size, int *completed, int threshold)
{
- int i, count = 0;
- unsigned int available = 0, should_be_freed = 0, leave_threshold = 0;
- struct memcg_info *mi;
- unsigned int total_size = 0;
+ GSList *proc_app_list = NULL;
+ int i, ret, victim = 0;
+ unsigned int victim_size = 0;
+ unsigned int total_victim_size = 0;
+ unsigned int total_task_size = 0;
+ int status = LOWMEM_RECLAIM_NONE;
+ GArray *candidates = NULL;
+ GSList *iter, *iterchild;
+ struct proc_app_info *pai = NULL;
+ int oom_score_adj;
+ int should_be_freed_kb = MBYTE_TO_KBYTE(should_be_freed);
+
+ candidates = g_array_new(false, false, sizeof(struct task_info));
- for (i = MEMCG_MAX - 1; i > 0; i--) {
- adjust_dynamic_threshold(i);
+ proc_app_list = proc_app_list_open();
+ gslist_for_each_item(iter, proc_app_list) {
+ struct task_info ti;
- should_be_freed = is_memory_recovered(&available, &leave_threshold);
+ pai = (struct proc_app_info *)iter->data;
+ if (!pai->main_pid)
+ continue;
- if (should_be_freed == 0)
- return count;
+ oom_score_adj = pai->memory.oom_score_adj;
+ if (oom_score_adj > end_oom || oom_score_adj < start_oom)
+ continue;
- if (!memcg_tree[i] || !memcg_tree[i]->info)
+ if ((flags & OOM_REVISE) && pai->memory.oom_killed)
continue;
- mi = memcg_tree[i]->info;
+ ti.pid = pai->main_pid;
+ ti.pgid = getpgid(ti.pid);
+ ti.oom_score_adj = oom_score_adj;
- /*
- * Processes in the previous cgroup are killed only when
- * the available memory is less than dynamic oom threshold.
- */
- if ((i <= MEMCG_PREVIOUS) &&
- (available > dynamic_oom_threshold)) {
- _E("do not try fg group, %u > %u, completed",
- available, dynamic_oom_threshold);
+ if (pai->childs) {
+ ti.pids = g_array_new(false, false, sizeof(pid_t));
+ g_array_append_val(ti.pids, ti.pid);
+ gslist_for_each_item(iterchild, pai->childs) {
+ pid_t child = GPOINTER_TO_PID(iterchild->data);
+ g_array_append_val(ti.pids, child);
+ }
+ } else
+ ti.pids = NULL;
- if (completed)
- *completed = 1;
+ ti.size = lowmem_get_task_mem_usage_rss(&ti);
+ g_array_append_val(candidates, ti);
+ total_task_size += ti.size;
+ }
- return count;
- }
+ proc_app_list_close();
- _I("%s start, available = %u, should_be_freed = %u",
- mi->name, available, should_be_freed);
+ if (!candidates->len)
+ goto leave;
- if (memcg_tree[i]->use_hierarchy)
- count = lowmem_kill_subcgroup_victims(i, num_max_victims,
- flags, &total_size, completed);
- else
- count = lowmem_kill_cgroup_victims(i, mi,
- num_max_victims, should_be_freed,
- flags, &total_size, completed);
+ g_array_sort(candidates, (GCompareFunc)compare_victims);
- if (count == 0) {
- _E("%s: there is no victim", mi->name);
- continue;
- }
+ for (i = 0; i < candidates->len; i++) {
+ struct task_info *tsk;
- if (completed && *completed) {
- _E("completed after kill %s cgroup", mi->name);
+ if (i >= max_victims) {
+ status = LOWMEM_RECLAIM_WAIT;
break;
}
- if ((flags & OOM_TIMER_CHECK) && (i <= MEMCG_PREVIOUS)) {
- if (++fg_killed >= MAX_FGRD_KILL) {
- _E("foreground is killed %d times and search from proc", fg_killed);
- fg_killed = 0;
- continue;
+ /*
+ * Available memory is checking only every
+ * num_vict_between_check process for reducing burden.
+ */
+ if (!(i % num_vict_between_check)) {
+ if (proc_get_mem_available() > threshold) {
+ status = LOWMEM_RECLAIM_DONE;
+ break;
}
- _E("foreground is killed %d times", fg_killed);
}
- _E("%s: kill %d victims, total_size = %u",
- mi->name, count, total_size);
- return count;
- }
+ if (!(flags & OOM_NOMEMORY_CHECK) &&
+ total_victim_size >= should_be_freed_kb) {
+ _E("victim = %d, max_victims = %d, total_size = %u",
+ victim, max_victims, total_victim_size);
+ status = LOWMEM_RECLAIM_DONE;
+ break;
+ }
- if (completed && !(*completed))
- count = lowmem_kill_memory_cgroup_victims(flags);
+ tsk = &g_array_index(candidates, struct task_info, i);
- return count;
-}
+ status = lowmem_check_kill_continued(tsk, flags);
+ if (status != LOWMEM_RECLAIM_CONT)
+ break;
-static int lowmem_kill_victims(int type, struct memcg_info *mi, int flags,
- int *completed)
-{
- unsigned int total_size = 0;
- int count;
+ _I("select victims from proc_app_list pid(%d)\n", tsk->pid);
- if (type == MEMCG_MEMORY)
- count = lowmem_kill_all_cgroup_victims(flags, completed);
- else
- count = lowmem_kill_cgroup_victims(type, mi,
- MAX_CGROUP_VICTIMS, mi->threshold_leave,
- flags, &total_size, completed);
+ ret = lowmem_kill_victim(tsk, flags, &victim_size);
+ if (ret != RESOURCED_ERROR_NONE)
+ continue;
+ victim++;
+ total_victim_size += victim_size;
+ }
- return count;
+leave:
+ lowmem_free_task_info_array(candidates);
+ *total_size = total_victim_size;
+ *completed = status;
+ return victim;
}
-static int lowmem_oom_killer_cb(int type, struct memcg_info *mi, int flags,
- int *completed)
+static void calualate_range_of_oom(enum lmk_type lmk, int *min, int *max)
{
- int count = 0;
-
- /* get multiple victims from /sys/fs/cgroup/memory/.../tasks */
- count = lowmem_kill_victims(type, mi, flags, completed);
-
- if (count == 0) {
- _D("victim count = %d", count);
- return count;
+ if (lmk == LMK_INACTIVE) {
+ *max = OOMADJ_APP_MAX;
+ *min = OOMADJ_FAVORITE;
+ } else if (lmk == LMK_ACTIVE) {
+ *max = OOMADJ_APP_MAX;
+ *min = OOMADJ_BACKGRD_PERCEPTIBLE;
+ } else {
+ *max = OOMADJ_APP_MAX;
+ *min = OOMADJ_SU;
}
-
- /* check current memory status */
- if (!(flags & OOM_FORCE) && type != MEMCG_MEMORY &&
- lowmem_check_current_state(mi) >= 0)
- return count;
-
- return count;
}
-static int lowmem_force_oom_killer(int flags, unsigned int should_be_freed,
- int max_victims)
+static void lowmem_handle_request(struct lowmem_control *ctl)
{
- int count = 0, completed = 0, i;
- unsigned int total_size, freed = 0;
+ int start_oom, end_oom;
+ int count = 0, victim_cnt = 0;
+ int status = LOWMEM_RECLAIM_NONE;
+ unsigned int available = 0, leave_threshold = 0;
+ unsigned int total_size = 0;
+ unsigned int current_size = 0;
+ unsigned int reclaim_size, shortfall = 0;
+ enum lmk_type lmk_type = ctl->type;
- lowmem_change_memory_state(LOWMEM_LOW, 1);
- for (i = MEMCG_MAX - 1; i >= MEMCG_BACKGROUND; i--) {
- int num_max = max_victims - count;
- unsigned int remained = should_be_freed - freed;
- count += lowmem_kill_cgroup_victims(i, memcg_tree[i]->info,
- num_max, remained, flags, &total_size, &completed);
- freed += KBtoMB(total_size);
- _D("force kill total %d victims, freed = %u", count, freed);
- if (should_be_freed > 0 && freed >= should_be_freed)
- break;
- }
- lowmem_change_memory_state(LOWMEM_NORMAL, 0);
+ available = proc_get_mem_available();
+ reclaim_size = ctl->size > available
+ ? ctl->size - available : 0;
- return count;
-}
+ if (!reclaim_size) {
+ status = LOWMEM_RECLAIM_DONE;
+ goto done;
+ }
-static void *lowmem_oom_killer_pthread(void *arg)
+ /*
+ * We've entered here after LOWMEM_RECLAIM_WAIT with lmk_type == LMK_MEMORY.
+ * We're going to try to kill some of processes in /procfs to handle
+ * low memory situation. If this doesn't end with success let's try again
+ * starting from Inactive.
+ */
+ if (lmk_type == LMK_MEMORY) {
+ count += lowmem_kill_victims_from_proc(ctl->flags, &status, ctl->size);
+ _D("Returned from /proc killing, count: %d, status: %d", count, status);
+ if (status == LOWMEM_RECLAIM_DONE)
+ goto done;
+ else
+ lmk_type = LMK_INACTIVE;
+ }
+retry:
+ /* Prepare LMK to start doing it's job. Check preconditions. */
+ calualate_range_of_oom(lmk_type, &start_oom, &end_oom);
+ adjust_dynamic_threshold(lmk_type);
+ shortfall = is_memory_recovered(&available, &leave_threshold);
+
+ if (!shortfall || !reclaim_size) {
+ status = LOWMEM_RECLAIM_DONE;
+ goto done;
+ }
+ _D("reclaim start, available = %u, currently required = %u, possibly reclaimed: %u, type: %d",
+ available, reclaim_size, total_size, lmk_type);
+
+ /* precaution */
+ current_size = 0;
+ victim_cnt = lowmem_kill_victims(ctl->count - count, start_oom, end_oom,
+ reclaim_size, ctl->flags, ¤t_size, &status, ctl->size);
+
+ current_size = KBYTE_TO_MBYTE(current_size);
+ count += victim_cnt;
+ total_size += current_size;
+
+ reclaim_size -= reclaim_size > current_size
+ ? current_size : reclaim_size;
+ _E("kill %d victims, total_size = %u from %d to %d %d",
+ victim_cnt, current_size, start_oom, end_oom, status);
+
+ if ((status == LOWMEM_RECLAIM_DONE) ||
+ (status == LOWMEM_RECLAIM_WAIT) ||
+ (status == LOWMEM_RECLAIM_DROP))
+ goto done;
+
+ if (victim_cnt)
+ _E("So far: killed %d processes reclaimed: %u remaining: %u shortfall: %u\n",
+ count, total_size, reclaim_size, shortfall);
+
+ /*
+ * If it doesn't finish reclaiming memory in first operation,
+ - if flags has OOM_IN_DEPTH,
+ try to find victims again in the active cgroup.
+ otherwise, just return because there is no more victims in the desired cgroup.
+ - if flags has OOM_REVISE,
+ it means that resourced can't find victims from proc_app_list.
+ So, it should search victims or malicious process from /proc.
+ But searching /proc leads to abnormal behaviour.
+ (Make sluggish or kill same victims continuously)
+ Thus, otherwise, just return in first operation and wait some period.
+ */
+ if ((count < ctl->count) && (ctl->flags & OOM_IN_DEPTH)) {
+ if (lmk_type == LMK_INACTIVE) {
+ /* We tried in inactive processes and didn't succeed, try immediately in active */
+ _D("Inactive wasn't enough, lets try in Active.");
+ lmk_type = LMK_ACTIVE;
+ goto retry;
+ } else if (lmk_type == LMK_ACTIVE) {
+ /*
+ * We tried in INACTIVE and ACTIVE but still didn't succeed
+ * so it's time to try in /proc. Before doing it wait some time.
+ */
+ ctl->status = LOWMEM_RECLAIM_WAIT;
+ ctl->type = LMK_MEMORY;
+ _D("We would need to kill from /proc wait some time and try again");
+ return;
+ }
+ }
+done:
+ _E("Done: killed %d processes reclaimed: %u remaining: %u shortfall: %u status: %d\n",
+ count, total_size, reclaim_size, shortfall, status);
+ ctl->status = status;
+ return;
+}
+
+static void *lowmem_reclaim_worker(void *arg)
{
- int ret = RESOURCED_ERROR_NONE;
+ struct lowmem_worker *lmw = (struct lowmem_worker *)arg;
setpriority(PRIO_PROCESS, 0, OOM_KILLER_PRIORITY);
+ g_async_queue_ref(lmw->queue);
+
while (1) {
- /*
- * When signalled by main thread,
- * it starts lowmem_oom_killer_cb().
- */
- ret = pthread_mutex_lock(&oom_mutex);
- if (ret) {
- _E("oom thread::pthread_mutex_lock() failed, %d", ret);
- break;
- }
+ struct lowmem_control *ctl;
- ret = pthread_cond_wait(&oom_cond, &oom_mutex);
- if (ret) {
- _E("oom thread::pthread_cond_wait() failed, %d", ret);
- pthread_mutex_unlock(&oom_mutex);
- break;
- }
+ LOWMEM_WORKER_IDLE(lmw);
+ /* Wait on any wake-up call */
+ ctl = g_async_queue_pop(lmw->queue);
- _I("oom thread conditional signal received and start");
- lowmem_oom_killer_cb(MEMCG_MEMORY, memcg_root, OOM_NONE, NULL);
+ if (!LOWMEM_WORKER_IS_ACTIVE(lmw) || (ctl->flags & OOM_DROP))
+ break;
- _I("lowmem_oom_killer_cb finished");
+ LOWMEM_WORKER_RUN(lmw);
+process_again:
+ lowmem_handle_request(ctl);
+ /**
+ * Case the process failed to reclaim requested amount of memory
+ * or still under have memory pressure - try the timeout wait.
+ * There is a chance this will get woken-up in a better reality.
+ */
+ if (ctl->status != LOWMEM_RECLAIM_DONE &&
+ !(ctl->flags & OOM_SINGLE_SHOT)) {
+ unsigned int available = proc_get_mem_available();
+
+ if (available >= ctl->size) {
+ _E("Memory restored: requested: %u available: %u\n",
+ ctl->size, available);
+ ctl->status = LOWMEM_RECLAIM_DONE;
+ if (ctl->callback)
+ ctl->callback(ctl);
+ LOWMEM_DESTROY_REQUEST(ctl);
+ LOWMEM_WORKER_IDLE(lmw);
+ continue;
+ }
- ret = pthread_mutex_unlock(&oom_mutex);
- if (ret) {
- _E("oom thread::pthread_mutex_unlock() failed, %d", ret);
- break;
+ if (LOWMEM_WORKER_IS_ACTIVE(lmw)) {
+ g_usleep(LMW_RETRY_WAIT_TIMEOUT_MSEC);
+ ctl->flags |= OOM_REVISE;
+ goto process_again;
+ }
}
- }
- /* Now our thread finishes - cleanup TID */
- oom_thread = 0;
+ /*
+ * The ctl callback would check available size again.
+ * And it is last point in reclaiming worker.
+ * Resourced sent SIGKILL signal to victim processes
+ * so it should wait for a some seconds until each processes returns memory.
+ */
+ g_usleep(LMW_LOOP_WAIT_TIMEOUT_MSEC);
+ if (ctl->callback)
+ ctl->callback(ctl);
- return NULL;
+ /* The lmk becomes the owner of all queued requests .. */
+ LOWMEM_DESTROY_REQUEST(ctl);
+ LOWMEM_WORKER_IDLE(lmw);
+ }
+ g_async_queue_unref(lmw->queue);
+ pthread_exit(NULL);
}
static void change_lowmem_state(unsigned int mem_state)
{
- if (cur_mem_state == mem_state)
- return;
-
- _I("[LOW MEM STATE] %s ==> %s, changed available = %d MB",
- convert_memstate_to_str(cur_mem_state),
- convert_memstate_to_str(mem_state),
- proc_get_mem_available());
-
+ _I("[LOW MEM STATE] %s ==> %s", convert_memstate_to_str(cur_mem_state),
+ convert_memstate_to_str(mem_state));
cur_mem_state = mem_state;
- adjust_dynamic_threshold(MEMCG_BACKGROUND);
+ adjust_dynamic_threshold(LMK_INACTIVE);
+ resourced_notify(RESOURCED_NOTIFIER_MEM_STATE_CHANGED,
+ (void *)cur_mem_state);
}
static void lowmem_swap_memory(enum memcg_type type, struct memcg_info *mi)
{
unsigned int available;
struct swap_status_msg msg;
- static const struct module_ops *swap;
if (cur_mem_state == LOWMEM_NORMAL)
return;
- if (!swap) {
- swap = find_module("swap");
- if (!swap)
- return;
- }
+ if (swap_get_state() != SWAP_ON)
+ return;
available = proc_get_mem_available();
if (cur_mem_state != LOWMEM_SWAP &&
available <= memcg_root->threshold[LOWMEM_SWAP])
swap_act();
- memcg_swap_status[type] = true;
msg.type = type;
msg.info = mi;
resourced_notify(RESOURCED_NOTIFIER_SWAP_START, &msg);
static void normal_act(void)
{
int ret, status;
- int index;
- struct swap_status_msg msg;
ret = vconf_get_int(VCONFKEY_SYSMAN_LOW_MEMORY, &status);
if (ret)
}
change_lowmem_state(LOWMEM_NORMAL);
- for (index = 0; index < MEMCG_MAX; ++index) {
- if (!memcg_swap_status[index])
- continue;
-
- msg.type = index;
- msg.info = memcg_tree[index]->info;
- resourced_notify(RESOURCED_NOTIFIER_SWAP_UNSET_LIMIT, &msg);
- memcg_swap_status[index] = false;
- }
-
if (proc_get_freezer_status() == CGROUP_FREEZER_PAUSED)
resourced_notify(RESOURCED_NOTIFIER_FREEZER_CGROUP_STATE,
- (void *)CGROUP_FREEZER_ENABLED);
+ (void *)CGROUP_FREEZER_ENABLED);
}
static void swap_act(void)
change_lowmem_state(LOWMEM_SWAP);
if (proc_get_freezer_status() == CGROUP_FREEZER_PAUSED)
resourced_notify(RESOURCED_NOTIFIER_FREEZER_CGROUP_STATE,
- (void *)CGROUP_FREEZER_ENABLED);
+ (void *)CGROUP_FREEZER_ENABLED);
if (swap_get_state() != SWAP_ON)
resourced_notify(RESOURCED_NOTIFIER_SWAP_ACTIVATE, NULL);
static void low_act(void)
{
- int ret, status;
-
- ret = vconf_get_int(VCONFKEY_SYSMAN_LOW_MEMORY, &status);
-
- if (ret)
- _D("vconf_get_int fail %s", VCONFKEY_SYSMAN_LOW_MEMORY);
-
- if (proc_get_freezer_status() == CGROUP_FREEZER_ENABLED)
- resourced_notify(RESOURCED_NOTIFIER_FREEZER_CGROUP_STATE,
- (void *)CGROUP_FREEZER_PAUSED);
change_lowmem_state(LOWMEM_LOW);
resourced_notify(RESOURCED_NOTIFIER_SWAP_COMPACT, (void *)SWAP_COMPACT_LOWMEM_LOW);
-
- /* Since vconf for soft warning could be set during low memory check,
- * we set it only when the current status is not soft warning.
- */
- if (status != VCONFKEY_SYSMAN_LOW_MEMORY_SOFT_WARNING) {
- vconf_set_int(VCONFKEY_SYSMAN_LOW_MEMORY,
- VCONFKEY_SYSMAN_LOW_MEMORY_SOFT_WARNING);
- memory_level_send_system_event(MEMORY_LEVEL_LOW);
- }
+ memory_level_send_system_event(MEMORY_LEVEL_LOW);
}
-static Eina_Bool medium_cb(void *data)
+static void medium_cb(struct lowmem_control *ctl)
{
- unsigned int available;
- int count = 0;
- int completed = 0;
-
- available = proc_get_mem_available();
- _I("available = %u, timer run until reaching leave threshold", available);
-
- if (available >= memcg_root->threshold_leave && oom_check_timer != NULL) {
- ecore_timer_del(oom_check_timer);
- oom_check_timer = NULL;
- _I("oom_check_timer deleted after reaching leave threshold");
- normal_act();
- fg_killed = 0;
+ if (ctl->status == LOWMEM_RECLAIM_DONE)
oom_popup = false;
- return ECORE_CALLBACK_CANCEL;
- }
-
- _I("available = %u cannot reach leave threshold %u, timer again",
- available, memcg_root->threshold_leave);
- count = lowmem_oom_killer_cb(MEMCG_MEMORY,
- memcg_root, OOM_TIMER_CHECK, &completed);
-
- /*
- * After running oom killer in timer, but there is no victim,
- * stop timer.
- */
- if (oom_check_timer != NULL &&
- (completed || (!count && available >= dynamic_oom_threshold))) {
- ecore_timer_del(oom_check_timer);
- oom_check_timer = NULL;
- _I("timer deleted, avail:%u, thres:%u, count:%d, completed:%d",
- available, dynamic_oom_threshold, count, completed);
- normal_act();
- fg_killed = 0;
- oom_popup = false;
- return ECORE_CALLBACK_CANCEL;
- }
- return ECORE_CALLBACK_RENEW;
+ lowmem_change_memory_state(LOWMEM_NORMAL, 0);
}
static void medium_act(void)
{
- int ret = 0;
-
- change_lowmem_state(LOWMEM_MEDIUM);
+ unsigned int available;
+ int ret;
+ int status = VCONFKEY_SYSMAN_LOW_MEMORY_NORMAL;
- /* signal to lowmem_oom_killer_pthread to start killer */
- ret = pthread_mutex_trylock(&oom_mutex);
- if (ret) {
- _E("medium_act::pthread_mutex_trylock() failed, %d, errno: %d", ret, errno);
+ /*
+ * Don't trigger reclaim worker
+ * if it is already running
+ */
+ if (LOWMEM_WORKER_IS_RUNNING(&lmw))
return;
- }
- _I("oom mutex trylock success");
- pthread_cond_signal(&oom_cond);
- _I("send signal to oom killer thread");
- pthread_mutex_unlock(&oom_mutex);
- vconf_set_int(VCONFKEY_SYSMAN_LOW_MEMORY,
- VCONFKEY_SYSMAN_LOW_MEMORY_HARD_WARNING);
+ ret = vconf_get_int(VCONFKEY_SYSMAN_LOW_MEMORY, &status);
+ if (ret)
+ _D("vconf_get_int fail %s", VCONFKEY_SYSMAN_LOW_MEMORY);
+
memory_level_send_system_event(MEMORY_LEVEL_CRITICAL);
+ if (status != VCONFKEY_SYSMAN_LOW_MEMORY_HARD_WARNING) {
+ if (proc_get_freezer_status() == CGROUP_FREEZER_ENABLED)
+ resourced_notify(RESOURCED_NOTIFIER_FREEZER_CGROUP_STATE,
+ (void *)CGROUP_FREEZER_PAUSED);
+ vconf_set_int(VCONFKEY_SYSMAN_LOW_MEMORY,
+ VCONFKEY_SYSMAN_LOW_MEMORY_HARD_WARNING);
+ }
+ available = proc_get_mem_available();
+
+ change_lowmem_state(LOWMEM_MEDIUM);
- if (oom_check_timer == NULL) {
- _D("timer run until reaching leave threshold");
- oom_check_timer =
- ecore_timer_add(OOM_TIMER_INTERVAL, medium_cb, (void *)NULL);
+ if (available < memcg_root->threshold_leave) {
+ struct lowmem_control *ctl = LOWMEM_NEW_REQUEST();
+
+ if (ctl) {
+ LOWMEM_SET_REQUEST(ctl, OOM_IN_DEPTH,
+ LMK_INACTIVE, memcg_root->threshold_leave,
+ num_max_victims, medium_cb);
+ lowmem_queue_request(&lmw, ctl);
+ }
}
+
resourced_notify(RESOURCED_NOTIFIER_SWAP_COMPACT, (void *)SWAP_COMPACT_LOWMEM_MEDIUM);
return;
}
+void lowmem_trigger_memory_state_action(int mem_state)
+{
+ /*
+ * Check if the state we want to set is different from current
+ * But it should except this condition if mem_state is already medium.
+ * Otherwise, recalim worker couldn't run any more.
+ */
+ if (mem_state != LOWMEM_MEDIUM && cur_mem_state == mem_state)
+ return;
+
+ switch (mem_state) {
+ case LOWMEM_NORMAL:
+ normal_act();
+ break;
+ case LOWMEM_SWAP:
+ swap_act();
+ break;
+ case LOWMEM_LOW:
+ low_act();
+ break;
+ case LOWMEM_MEDIUM:
+ medium_act();
+ break;
+ default:
+ assert(0);
+ }
+}
+
static void lowmem_dump_cgroup_procs(struct memcg_info *mi)
{
int i;
unsigned int size;
pid_t pid;
- GArray *pids_array = g_array_new(false, false, sizeof(pid_t));
+ GArray *pids_array = NULL;
- memcg_get_pids(mi, pids_array);
+ cgroup_get_pids(mi->name, &pids_array);
for (i = 0; i < pids_array->len; i++) {
pid = g_array_index(pids_array, pid_t, i);
- get_proc_mem_usage(pid, &size);
+ lowmem_mem_usage_uss(pid, &size);
_I("pid = %d, size = %u KB", pid, size);
}
- g_array_free(pids_array, TRUE);
+ g_array_free(pids_array, true);
}
-static void memory_cgroup_medium_act(int type, struct memcg_info *mi)
+static void memory_cgroup_medium_act(enum lmk_type type, struct memcg_info *mi)
{
+ struct lowmem_control *ctl;
+
_I("[LOW MEM STATE] memory cgroup %s oom state",
mi->name);
/* To Do: only start to kill fg victim when no pending fg victim */
lowmem_dump_cgroup_procs(mi);
- lowmem_oom_killer_cb(type, mi, OOM_NONE, NULL);
+
+ ctl = LOWMEM_NEW_REQUEST();
+ if (ctl) {
+ LOWMEM_SET_REQUEST(ctl, OOM_SINGLE_SHOT | OOM_IN_DEPTH, type,
+ mi->oomleave, num_max_victims, NULL);
+ lowmem_queue_request(&lmw, ctl);
+ }
}
static unsigned int check_mem_state(unsigned int available)
lowmem_memcg_set_leave_threshold(MEMCG_MEMORY, value);
} else if (!strncmp(result->name, "ForegroundRatio", strlen("ForegroundRatio")+1)) {
float ratio = atof(result->value);
- memcg_info_set_limit(memcg_tree[MEMCG_FOREGROUND]->info, ratio, totalram);
- } else if (!strncmp(result->name, "ForegroundUseHierarchy", strlen("ForegroundUseHierarchy")+1)) {
- int use_hierarchy = atoi(result->value);
- memcg_tree[MEMCG_FOREGROUND]->use_hierarchy = use_hierarchy;
- } else if (!strncmp(result->name, "ForegroundNumCgroups", strlen("ForegroundNumCgroups")+1)) {
- int num_cgroups = atoi(result->value);
- if (num_cgroups > 0)
- memcg_add_cgroups(memcg_tree[MEMCG_FOREGROUND], num_cgroups);
- memcg_show(memcg_tree[MEMCG_FOREGROUND]);
+ memcg_info_set_limit(memcg_tree[MEMCG_APPS]->info, ratio, totalram);
} else if (!strncmp(result->name, "NumMaxVictims", strlen("NumMaxVictims")+1)) {
int value = atoi(result->value);
num_max_victims = value;
+ /*
+ * calculate number of kill victims between check memory available
+ * depends on number of max kill victims
+ */
+ num_vict_between_check = value > MAX_MEMORY_CGROUP_VICTIMS/2
+ ? 3 : value > MAX_MEMORY_CGROUP_VICTIMS/4
+ ? 2 : 1;
} else if (!strncmp(result->name, "ProactiveThreshold", strlen("ProactiveThreshold")+1)) {
int value = atoi(result->value);
proactive_threshold = value;
} else if (!strncmp(result->name, "DynamicThreshold", strlen("DynamicThreshold")+1)) {
int value = atoi(result->value);
dynamic_threshold_min = value;
+ } else if (!strncmp(result->name, "EventLevel", strlen("EventLevel")+1)) {
+ if (strncmp(event_level, result->value, strlen(event_level)))
+ event_level = strdup(result->value);
+ if (!event_level)
+ return RESOURCED_ERROR_OUT_OF_MEMORY;
}
+
return RESOURCED_ERROR_NONE;
}
static void setup_memcg_params(void)
{
int i;
- unsigned long total_ramsize = BtoMB(totalram);
+ unsigned long total_ramsize = BYTE_TO_MBYTE(totalram);
_D("Total: %lu MB", total_ramsize);
if (total_ramsize <= MEM_SIZE_64) {
/* set thresholds for ram size 64M */
dynamic_threshold_min) >> 2;
dynamic_oom_threshold = memcg_root->threshold[LOWMEM_MEDIUM];
- for (i = LOWMEM_SWAP; i < LOWMEM_MAX_LEVEL; i++)
+ for (i = 0; i < LOWMEM_MAX_LEVEL; i++)
_I("set threshold for state '%s' to %u MB", convert_memstate_to_str(i), memcg_root->threshold[i]);
_I("set number of max victims as %d", num_max_victims);
static void init_memcg_params(void)
{
int idx = 0;
- char buf[MAX_PATH_LENGTH];
+ GSList *cgroups;
+
memcg_tree = (struct memcg **)malloc(sizeof(struct memcg *) *
MEMCG_MAX);
assert(memcg_tree);
for (idx = 0; idx < MEMCG_MAX; idx++) {
- struct memcg_info *mi = NULL;
+ struct memcg_info *mi = &gmi[idx];
memcg_tree[idx] = (struct memcg *)malloc(sizeof(struct memcg));
assert(memcg_tree[idx]);
memcg_init(memcg_tree[idx]);
- if (memcg_name[idx])
- snprintf(buf, MAX_PATH_LENGTH, "%s/%s", LOWMEM_DEFAULT_CGROUP,
- memcg_name[idx]);
- else
- snprintf(buf, MAX_PATH_LENGTH, "%s", LOWMEM_DEFAULT_CGROUP);
- mi = (struct memcg_info *)malloc(sizeof(struct memcg_info));
- assert(mi);
-
- memcg_info_init(mi, buf);
memcg_tree[idx]->info = mi;
- _I("init memory cgroup for %s", buf);
- if (idx == MEMCG_MEMORY)
+ _I("init memory cgroup for %s", mi->name);
+
+ if (mi->parent_memcg == MEMCG_ROOT) {
memcg_root = memcg_tree[idx]->info;
+ } else {
+ cgroups = memcg_tree[mi->parent_memcg]->cgroups;
+ cgroups = g_slist_prepend(cgroups, mi);
+ memcg_tree[mi->parent_memcg]->use_hierarchy = true;
+ }
}
}
-static int write_params_memcg_info(struct memcg_info *mi,
- int write_limit)
+static int write_params_memcg_info(struct memcg_info *mi)
{
unsigned int limit = mi->limit;
const char *name = mi->name;
if (ret)
return ret;
+ if (mi->limit_ratio == LOWMEM_NO_LIMIT)
+ return ret;
+
/*
* for memcg with LOWMEM_NO_LIMIT or write_limit is not set,
* do not set limit for cgroup limit.
*/
- if (mi->limit_ratio == LOWMEM_NO_LIMIT ||
- !write_limit)
+ if (mi->limit_ratio == LOWMEM_NO_LIMIT || !limit)
return ret;
/* disable memcg OOM-killer */
static int write_memcg_params(void)
{
unsigned int i;
- GSList *iter = NULL;
for (i = 0; i < MEMCG_MAX; i++) {
struct memcg_info *mi = memcg_tree[i]->info;
- int write_limit = !memcg_tree[i]->use_hierarchy;
- GSList *list = memcg_tree[i]->cgroups;
- write_params_memcg_info(mi, write_limit);
- /* write limit to the node for sub cgroups */
- write_limit = 1;
- /* write node for sub cgroups */
- gslist_for_each_item(iter, list) {
- struct memcg_info *mi =
- (struct memcg_info *)(iter->data);
- write_params_memcg_info(mi, write_limit);
- }
+ write_params_memcg_info(mi);
}
return RESOURCED_ERROR_NONE;
}
-static struct memcg_info *find_foreground_cgroup(struct proc_app_info *pai)
-{
- unsigned int usage;
- unsigned int min_usage = UINT_MAX;
- struct memcg_info *min_mi = NULL, *mi;
- GSList *iter = NULL;
-
- /*
- * if this process group is already in one of the foreground cgroup,
- * put all of the process in this group into the same cgroup.
- */
- if (pai && (pai->memory.memcg_idx == MEMCG_FOREGROUND)) {
- _D("%s is already in foreground", pai->appid);
- return pai->memory.memcg_info;
- }
-
- /*
- * if any of the process in this group is not in foreground,
- * find foreground cgroup with minimum usage
- */
- if (memcg_tree[MEMCG_FOREGROUND]->use_hierarchy) {
- gslist_for_each_item(iter,
- memcg_tree[MEMCG_FOREGROUND]->cgroups) {
- mi = (struct memcg_info *)(iter->data);
-
- memcg_get_usage(mi, &usage);
- /* select foreground memcg with no task first */
- if (usage == 0) {
- _D("%s' usage is 0, selected", mi->name);
- return mi;
- }
-
- /* select forground memcg with minimum usage */
- if (usage > 0 && min_usage > usage) {
- min_usage = usage;
- min_mi = mi;
- }
- }
- _D("%s is selected at min usage = %u",
- min_mi->name, min_usage);
-
- } else {
- return memcg_tree[MEMCG_FOREGROUND]->info;
- }
-
- return min_mi;
-}
-
static void lowmem_move_memcgroup(int pid, int oom_score_adj)
{
struct proc_app_info *pai = find_app_info(pid);
if (oom_score_adj > OOMADJ_BACKGRD_UNLOCKED + OOMADJ_APP_INCREASE) {
if (pai && (oom_score_adj != pai->memory.oom_score_adj))
proc_set_process_memory_state(pai, pai->memory.memcg_idx,
- pai->memory.memcg_info, oom_score_adj);
+ pai->memory.memcg_info, oom_score_adj);
return;
- } else if (oom_score_adj > OOMADJ_BACKGRD_UNLOCKED) {
- memcg_idx = MEMCG_BACKGROUND;
- mi = memcg_tree[memcg_idx]->info;
- should_swap = 1;
- } else if (oom_score_adj >= OOMADJ_PREVIOUS_BACKGRD) {
- memcg_idx = MEMCG_PREVIOUS;
- mi = memcg_tree[memcg_idx]->info;
- } else if (oom_score_adj >= OOMADJ_FAVORITE) {
- memcg_idx = MEMCG_FAVORITE;
- mi = memcg_tree[memcg_idx]->info;
- should_swap = 1;
- } else if (oom_score_adj == OOMADJ_SERVICE_DEFAULT) {
- memcg_idx = MEMCG_PREVIOUS;
+ } else if (oom_score_adj >= OOMADJ_INIT) {
+ memcg_idx = MEMCG_APPS;
mi = memcg_tree[memcg_idx]->info;
- } else if (oom_score_adj >= OOMADJ_BACKGRD_PERCEPTIBLE) {
- memcg_idx = MEMCG_PREVIOUS;
- mi = memcg_tree[memcg_idx]->info;
- } else if (oom_score_adj >= OOMADJ_FOREGRD_LOCKED ||
- oom_score_adj == OOMADJ_INIT) {
- /*
- * When resume occurs, to prevent resuming process
- * from being killed, raise its oom score to OOMADJ_INIT.
- * However, it could be still in the background group, and
- * selected as a victim. So, we move it to foreground group
- * in advanve.
- */
- memcg_idx = MEMCG_FOREGROUND;
- mi = find_foreground_cgroup(pai);
- } else {
+
+ if (oom_score_adj >= OOMADJ_FAVORITE)
+ should_swap = 1;
+ } else
return;
- }
- _D("pid: %d, proc_name: %s, cg_name: %s, oom_score_adj: %d", pid,
- pai ? pai->appid : "---", memcg_name[memcg_idx],
- oom_score_adj);
cgroup_write_node_uint32(mi->name, CGROUP_FILE_NAME, pid);
proc_set_process_memory_state(pai, memcg_idx, mi, oom_score_adj);
}
-static int oom_thread_create(void)
+static int lowmem_activate_worker(void)
{
int ret = RESOURCED_ERROR_NONE;
- if (oom_thread) {
- _I("oom thread %u already created", (unsigned)oom_thread);
- } else {
- /* initialize oom killer thread */
- ret = pthread_create(&oom_thread, NULL, (void *)lowmem_oom_killer_pthread, (void *)NULL);
- if (ret) {
- _E("pthread creation for lowmem_oom_killer_pthread failed, %d\n", ret);
- oom_thread = 0;
- } else {
- pthread_detach(oom_thread);
- }
+ if (LOWMEM_WORKER_IS_ACTIVE(&lmw)) {
+ _I("LMK worker thread [%d] has already been created\n",
+ (unsigned)lmw.worker_thread);
+ return ret;
}
+ lmw.queue = g_async_queue_new_full(lowmem_request_destroy);
+ if (!lmw.queue) {
+ _E("Failed to create request queue\n");
+ return RESOURCED_ERROR_FAIL;
+ }
+ LOWMEM_WORKER_ACTIVATE(&lmw);
+ ret = pthread_create(&lmw.worker_thread, NULL,
+ (void *)lowmem_reclaim_worker, (void *)&lmw);
+ if (ret) {
+ LOWMEM_WORKER_DEACTIVATE(&lmw);
+ _E("Failed to create LMK thread: %d\n", ret);
+ } else {
+ pthread_detach(lmw.worker_thread);
+ ret = RESOURCED_ERROR_NONE;
+ }
return ret;
}
+static void lowmem_deactivate_worker(void)
+{
+ struct lowmem_control *ctl;
+
+ if (!LOWMEM_WORKER_IS_ACTIVE(&lmw))
+ return;
+
+ LOWMEM_WORKER_DEACTIVATE(&lmw);
+ lowmem_drain_queue(&lmw);
+
+ ctl = LOWMEM_NEW_REQUEST();
+ ctl->flags = OOM_DROP;
+ g_async_queue_push(lmw.queue, ctl);
+ g_async_queue_unref(lmw.queue);
+}
+
static int create_memcgs(void)
{
int i = 0;
int ret = RESOURCED_ERROR_NONE;
- GSList *iter = NULL;
struct memcg_info *mi;
char *name;
- char parent_dir[MAX_PATH_LENGTH];
/* skip for memory cgroup */
for (i = 0; i < MEMCG_MAX; i++) {
- if (!memcg_name[i])
+ if (memcg_root == memcg_tree[i]->info)
continue;
mi = memcg_tree[i]->info;
- name = mi->name;
- ret = cgroup_make_subdir(LOWMEM_DEFAULT_CGROUP, memcg_name[i], NULL);
- _D("Create subcgroup of memory : name = %s, ret = %d", memcg_name[i], ret);
- if (!memcg_tree[i]->use_hierarchy)
- continue;
- /* create sub cgroups */
- gslist_for_each_item(iter, memcg_tree[i]->cgroups) {
- mi = (struct memcg_info *)iter->data;
- name = strstr(mi->name, memcg_name[i]) + strlen(memcg_name[i]) + 1;
- snprintf(parent_dir, MAX_PATH_LENGTH,
- "%s/%s", LOWMEM_DEFAULT_CGROUP, memcg_name[i]);
- ret = cgroup_make_subdir(parent_dir, name, NULL);
- _D("Create subcgroup of memory/%s : name = %s, ret = %d", memcg_name[i], name, ret);
- }
+ name = mi->hashname;
+ ret = cgroup_make_subdir(LOWMEM_ROOT_CGROUP, name, NULL);
+ _D("create memory cgroup for %s, ret = %d", name, ret);
}
return ret;
{
static unsigned int prev_available;
unsigned int available;
- int i, mem_state;
+ int mem_state;
available = proc_get_mem_available();
if (prev_available == available)
return;
mem_state = check_mem_state(available);
- for (i = 0; i < ARRAY_SIZE(lpe); i++) {
- if ((cur_mem_state == lpe[i].cur_mem_state)
- && (mem_state == lpe[i].new_mem_state)) {
- _D("cur_mem_state = %s, new_mem_state = %s, available = %d",
- convert_memstate_to_str(cur_mem_state),
- convert_memstate_to_str(mem_state),
- available);
- lpe[i].action();
- }
- }
+ lowmem_trigger_memory_state_action(mem_state);
+
prev_available = available;
}
-static void lowmem_press_cgroup_handler(int type, struct memcg_info *mi)
+static void lowmem_press_cgroup_handler(enum lmk_type type, struct memcg_info *mi)
{
- unsigned long long usage, threshold;
+ unsigned int usage, threshold;
int ret;
ret = memcg_get_anon_usage(mi, &usage);
return;
}
- threshold = (unsigned long long)(mi->threshold[LOWMEM_MEDIUM]);
+ threshold = mi->threshold[LOWMEM_MEDIUM];
if (usage >= threshold)
memory_cgroup_medium_act(type, mi);
else
- _I("anon page %llu MB < medium threshold %llu MB", BtoMB(usage),
- BtoMB(threshold));
+ _I("anon page %u MB < medium threshold %u MB", BYTE_TO_MBYTE(usage),
+ BYTE_TO_MBYTE(threshold));
}
static Eina_Bool lowmem_press_eventfd_handler(void *data,
int fd, i;
struct memcg_info *mi;
GSList *iter = NULL;
+ enum lmk_type lmk_type = LMK_MEMORY;
if (!ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_READ)) {
_E("ecore_main_fd_handler_active_get error , return\n");
/* call low memory handler for this memcg */
if (i == MEMCG_MEMORY)
lowmem_press_root_cgroup_handler();
- else
- lowmem_press_cgroup_handler(i, mi);
+ else {
+ if (i == MEMCG_APPS)
+ lmk_type = LMK_ACTIVE;
+ else if (i == MEMCG_SWAP)
+ lmk_type = LMK_INACTIVE;
+ lowmem_press_cgroup_handler(lmk_type, mi);
+ }
return ECORE_CALLBACK_RENEW;
}
/* ToDo: iterate child memcgs */
{
mi = (struct memcg_info *)(iter->data);
if (fd == mi->evfd) {
- lowmem_press_cgroup_handler(i, mi);
+ if (i == MEMCG_APPS)
+ lmk_type = LMK_ACTIVE;
+ else if (i == MEMCG_SWAP)
+ lmk_type = LMK_INACTIVE;
+ lowmem_press_cgroup_handler(lmk_type, mi);
_D("lowmem cgroup handler is called for %s",
mi->name);
return ECORE_CALLBACK_RENEW;
return ECORE_CALLBACK_RENEW;
}
-/*
-From memory.txt kernel document -
-To register a notifier, application need:
-- create an eventfd using eventfd(2)
-- open memory.oom_control file
-- write string like "<event_fd> <fd of memory.oom_control>"
-to cgroup.event_control
-*/
static void lowmem_press_register_eventfd(struct memcg_info *mi)
{
- int cgfd, pressurefd, evfd, res, sz;
- char buf[BUF_MAX] = {0, };
+ int evfd;
const char *name = mi->name;
if (mi->threshold[LOWMEM_MEDIUM] == LOWMEM_THRES_INIT)
return;
- snprintf(buf, sizeof(buf), "%s/%s", name, MEMCG_EVENTFD_CONTROL);
- cgfd = open(buf, O_WRONLY);
- if (cgfd < 0) {
- _E("open event_control failed");
- return;
- }
-
- snprintf(buf, sizeof(buf), "%s/%s", name, MEMCG_EVENTFD_MEMORY_PRESSURE);
- pressurefd = open(buf, O_RDONLY);
- if (pressurefd < 0) {
- _E("open pressure control failed");
- close(cgfd);
- return;
- }
+ evfd = memcg_set_eventfd(name, MEMCG_EVENTFD_MEMORY_PRESSURE,
+ event_level);
- /* create an eventfd using eventfd(2)
- use same event fd for using ecore event loop */
- evfd = eventfd(0, O_NONBLOCK);
if (evfd < 0) {
- _E("eventfd() error");
- close(cgfd);
- close(pressurefd);
+ _I("fail to register event press fd %s cgroup", name);
return;
}
- mi->evfd = evfd;
- /* pressure level*/
- /* write event fd low level */
- sz = snprintf(buf, sizeof(buf), "%d %d %s", evfd, pressurefd, mi->event_level);
- sz += 1;
- res = write(cgfd, buf, sz);
- if (res != sz) {
- _E("write cgfd failed : %d for %s", res, name);
- close(cgfd);
- close(pressurefd);
- close(evfd);
- mi->evfd = -1;
- return;
- }
+ mi->evfd = evfd;
_I("register event fd success for %s cgroup", name);
ecore_main_fd_handler_add(evfd, ECORE_FD_READ,
(Ecore_Fd_Cb)lowmem_press_eventfd_handler, NULL, NULL,
NULL);
-
- close(cgfd);
- close(pressurefd);
return;
}
static int lowmem_press_setup_eventfd(void)
{
unsigned int i;
- struct memcg_info *mi;
- GSList *iter = NULL;
for (i = 0; i < MEMCG_MAX; i++) {
- if (!memcg_tree[i]->use_hierarchy) {
- lowmem_press_register_eventfd(memcg_tree[i]->info);
- } else {
- GSList *list = memcg_tree[i]->cgroups;
- gslist_for_each_item(iter, list)
- {
- mi = (struct memcg_info *)(iter->data);
- lowmem_press_register_eventfd(mi);
- }
+ if (!memcg_tree[i]->use_hierarchy)
+ continue;
+
+ lowmem_press_register_eventfd(memcg_tree[i]->info);
+ }
+ return RESOURCED_ERROR_NONE;
+}
+
+int lowmem_trigger_reclaim(int flags)
+{
+ struct lowmem_control *ctl = LOWMEM_NEW_REQUEST();
+
+ if (!ctl)
+ return RESOURCED_ERROR_OUT_OF_MEMORY;
+
+ LOWMEM_SET_REQUEST(ctl, flags | OOM_IN_DEPTH | OOM_SINGLE_SHOT,
+ MEMCG_MAX -1, memcg_root->threshold_leave, MAX_FD_VICTIMS, NULL);
+ lowmem_queue_request(&lmw, ctl);
+ return RESOURCED_ERROR_NONE;
+}
+
+static void lowmem_force_reclaim_cb(struct lowmem_control *ctl)
+{
+ lowmem_change_memory_state(LOWMEM_NORMAL, 0);
+}
+
+static void lowmem_trigger_force_reclaim(unsigned int size)
+{
+ struct lowmem_control *ctl = LOWMEM_NEW_REQUEST();
+
+ lowmem_change_memory_state(LOWMEM_LOW, 1);
+ if (ctl) {
+ LOWMEM_SET_REQUEST(ctl, OOM_FORCE | OOM_SINGLE_SHOT,
+ LMK_INACTIVE, size, num_max_victims,
+ lowmem_force_reclaim_cb);
+
+ lowmem_queue_request(&lmw, ctl);
+ }
+}
+
+static void lowmem_proactive_oom_killer(int flags, char *appid)
+{
+ unsigned int before;
+
+ before = proc_get_mem_available();
+
+ /* If low memory state, just return and kill in oom killer */
+ if (before < memcg_root->threshold[LOWMEM_MEDIUM])
+ return;
+
+#ifdef HEART_SUPPORT
+ struct heart_memory_data *md = NULL;
+
+ /* Get HEART-memory data only if this module is enabled */
+ if (find_module("MEMORY") != NULL)
+ md = heart_memory_get_data(appid, DATA_6HOUR);
+
+ if (md) {
+ unsigned int uss;
+
+ uss = KBYTE_TO_MBYTE(md->avg_uss);
+
+ free(md);
+
+ /*
+ * if launching app is predicted to consume all memory,
+ * free memory up to leave threshold after launching the app.
+ */
+ if (before <= uss) {
+ _D("run history based proactive killer, (before), required level = %u MB",
+ memcg_root->threshold_leave + uss);
+ lowmem_trigger_force_reclaim(memcg_root->threshold_leave + uss);
+ return;
}
+
+ unsigned int after = before - uss;
+ _D("available after launch = %u MB, available = %u MB, uss = %u MB",
+ after, before, uss);
+
+ /*
+ * after launching app, ensure that available memory is
+ * above threshold_leave
+ */
+ if (after >= memcg_root->threshold[LOWMEM_MEDIUM])
+ return;
+
+ _D("run history based proactive killer (after), required level = %u MB",
+ memcg_root->threshold_leave + THRESHOLD_MARGIN);
+ lowmem_trigger_force_reclaim(memcg_root->threshold_leave + THRESHOLD_MARGIN);
+
+ return;
+ }
+#endif
+
+ /*
+ * When there is no history data for the launching app but it is
+ * indicated as PROC_LARGEMEMORY, run oom killer based on dynamic
+ * threshold.
+ */
+ if (!(flags & PROC_LARGEMEMORY))
+ return;
+ /*
+ * run proactive oom killer only when available is larger than
+ * dynamic process threshold
+ */
+ if (!proactive_threshold || before >= proactive_threshold)
+ return;
+
+ /*
+ * free THRESHOLD_MARGIN more than real should be freed,
+ * because launching app is consuming up the memory.
+ */
+ _D("Run threshold based proactive LMK: memory level to reach: %u\n",
+ proactive_leave + THRESHOLD_MARGIN);
+ lowmem_trigger_force_reclaim(proactive_leave + THRESHOLD_MARGIN);
+ return;
+}
+
+static int lowmem_prelaunch_handler(void *data)
+{
+ struct proc_status *ps = (struct proc_status *)data;
+
+ if (!ps->pai)
+ return RESOURCED_ERROR_NO_DATA;
+
+ lowmem_proactive_oom_killer(ps->pai->flags, ps->pai->appid);
+ return RESOURCED_ERROR_NONE;
+}
+
+static int lowmem_control_handler(void *data)
+{
+ struct lowmem_control_data *lowmem_data;
+
+ lowmem_data = (struct lowmem_control_data *)data;
+ switch (lowmem_data->control_type) {
+ case LOWMEM_MOVE_CGROUP:
+ lowmem_move_memcgroup((pid_t)lowmem_data->args[0],
+ (int)lowmem_data->args[1]);
+ break;
+ default:
+ break;
}
return RESOURCED_ERROR_NONE;
}
init_memcg_params();
setup_memcg_params();
-
if (allocate_vip_app_list() != RESOURCED_ERROR_NONE)
_E("allocate_vip_app_list FAIL");
/* vip_list is only needed at the set_vip_list */
free_vip_app_list();
-
config_parse(MEM_CONF_FILE, load_mem_config, NULL);
create_memcgs();
write_memcg_params();
- ret = oom_thread_create();
+ ret = lowmem_activate_worker();
if (ret) {
- _E("oom thread create failed");
+ _E("oom thread create failed\n");
return ret;
}
lowmem_dbus_init();
+ register_notifier(RESOURCED_NOTIFIER_APP_PRELAUNCH, lowmem_prelaunch_handler);
+ register_notifier(RESOURCED_NOTIFIER_MEM_CONTROL, lowmem_control_handler);
+
return ret;
}
int i;
for (i = 0; i < MEMCG_MAX; i++) {
g_slist_free_full(memcg_tree[i]->cgroups, free);
- free(memcg_tree[i]->info);
free(memcg_tree[i]);
}
+ if (event_level != (char*)MEMCG_DEFAULT_EVENT_LEVEL)
+ free(event_level);
+
+ lowmem_deactivate_worker();
+ unregister_notifier(RESOURCED_NOTIFIER_APP_PRELAUNCH, lowmem_prelaunch_handler);
+ unregister_notifier(RESOURCED_NOTIFIER_MEM_CONTROL, lowmem_control_handler);
return RESOURCED_ERROR_NONE;
}
return lowmem_exit();
}
-static int resourced_memory_control(void *data)
-{
- struct lowmem_data_type *l_data;
-
- l_data = (struct lowmem_data_type *)data;
- switch (l_data->control_type) {
- case LOWMEM_MOVE_CGROUP:
- lowmem_move_memcgroup((pid_t)l_data->args[0],
- (int)l_data->args[1]);
- break;
- default:
- break;
- }
- return RESOURCED_ERROR_NONE;
-}
-
-int lowmem_memory_oom_killer(int flags)
-{
- if (flags & OOM_FORCE)
- return lowmem_force_oom_killer(flags, 0, MAX_FD_VICTIMS);
- return lowmem_oom_killer_cb(MEMCG_MEMORY, memcg_root, flags, NULL);
-}
-
-int lowmem_proactive_oom_killer(int flags, char *appid)
-{
- int count = 0;
- unsigned int should_be_freed;
- unsigned int before;
-#ifdef HEART_SUPPORT
- struct heart_memory_data *md;
-#endif
-
- before = proc_get_mem_available();
-
- /* If low memory state, just return and kill in oom killer */
- if (before < memcg_root->threshold[LOWMEM_MEDIUM])
- return RESOURCED_ERROR_FAIL;
-
-#ifdef HEART_SUPPORT
-
- /* Get HEART-memory data only if this module is enabled */
- md = NULL;
- if (find_module("MEMORY") != NULL)
- md = heart_memory_get_data(appid, DATA_6HOUR);
-
- if (md) {
- unsigned int uss;
-
- uss = KBtoMB(md->avg_uss);
-
- free(md);
-
- /*
- * if launching app is predicted to consume all memory,
- * free memory up to leave threshold after launching the app.
- */
- if (before <= uss) {
- should_be_freed = memcg_root->threshold_leave + uss;
- lowmem_force_oom_killer(OOM_FORCE, should_be_freed, num_max_victims);
- return RESOURCED_ERROR_NONE;
- }
-
- unsigned int after = before - uss;
- _D("available after launch = %u MB, available = %u MB, uss = %u MB",
- after, before, uss);
-
- /*
- * after launching app, ensure that available memory is
- * above threshold_leave
- */
- if (after >= memcg_root->threshold[LOWMEM_MEDIUM])
- return RESOURCED_ERROR_FAIL;
-
- should_be_freed = memcg_root->threshold_leave +
- THRESHOLD_MARGIN - after;
- _D("run history based proactive killer, should_be_freed = %u MB",
- should_be_freed);
- lowmem_force_oom_killer(OOM_FORCE, should_be_freed, num_max_victims);
-
- return RESOURCED_ERROR_NONE;
- }
-#endif
-
- /*
- * When there is no history data for the launching app but it is
- * indicated as PROC_LARGEMEMORY, run oom killer based on dynamic
- * threshold.
- */
- if (!(flags & PROC_LARGEMEMORY))
- return RESOURCED_ERROR_FAIL;
- /*
- * run proactive oom killer only when available is larger than
- * dynamic process threshold
- */
- if (!proactive_threshold || before >= proactive_threshold)
- return RESOURCED_ERROR_FAIL;
-
- /*
- * free THRESHOLD_MARGIN more than real should be freed,
- * because launching app is consuming up the memory.
- */
- should_be_freed = proactive_leave - before + THRESHOLD_MARGIN;
- _D("run threshold based proactive killer, should_be_freed = %u MB",
- should_be_freed);
-
- count = lowmem_force_oom_killer(OOM_FORCE, should_be_freed, num_max_victims);
- _D("kill %d victim total", count);
-
- return RESOURCED_ERROR_NONE;
-}
-
void lowmem_change_memory_state(int state, int force)
{
int mem_state;
} else {
unsigned int available = proc_get_mem_available();
mem_state = check_mem_state(available);
- _D("available = %u, mem_state = %s", available,
- convert_memstate_to_str(mem_state));
}
- switch (mem_state) {
- case LOWMEM_NORMAL:
- normal_act();
- break;
- case LOWMEM_SWAP:
- swap_act();
- break;
- case LOWMEM_LOW:
- low_act();
- break;
- case LOWMEM_MEDIUM:
- medium_act();
- break;
- default:
- assert(0);
- }
+ lowmem_trigger_memory_state_action(mem_state);
}
void lowmem_memcg_set_threshold(int type, int level, int value)
return RESOURCED_ERROR_NONE;
}
-static struct module_ops memory_modules_ops = {
+void lowmem_restore_memcg(struct proc_app_info *pai)
+{
+ char *cgpath;
+ int index, ret;
+ struct memcg_info *mi;
+ pid_t pid = pai->main_pid;
+
+ ret = cgroup_pid_get_path("memory", pid, &cgpath);
+ if (ret < 0)
+ return;
+
+ for (index = MEMCG_MAX-1; index >= MEMCG_MEMORY; index--) {
+ mi = &gmi[index];
+ if (strstr(cgpath, mi->hashname))
+ break;
+ }
+ pai->memory.memcg_idx = index;
+ pai->memory.memcg_info = mi;
+}
+
+static const struct module_ops memory_modules_ops = {
.priority = MODULE_PRIORITY_HIGH,
.name = "lowmem",
.init = resourced_memory_init,
.exit = resourced_memory_finalize,
- .control = resourced_memory_control,
};
MODULE_REGISTER(&memory_modules_ops)