4 * Copyright (c) 2015 Samsung Electronics Co., Ltd. All rights reserved.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
22 /* For asprintf(3). Only affects this one file
23 * because tests dislike GNU_SOURCE since it
24 * makes mockability difficult. */
32 #include <sys/types.h>
39 #include "module-data.h"
40 #include "lowmem-handler.h"
45 #include "proc-common.h"
46 #include "memory-cgroup.h"
49 #include "config-parser.h"
50 #include "resourced-helper-worker.h"
51 #include "fd-handler.h"
52 #include "dbus-handler.h"
53 #include "safe-kill.h"
55 #define MEM_CONF_FILE RD_CONFIG_FILE(limiter)
56 #define MEMLIMIT_CONFIG_SECTION "MemLimit"
57 #define MEMLIMIT_CONFIG_TRIGGER "MemLimitTrigger"
59 #define MEMLIMIT_CONFIG_LIM_PFX MEMLIMIT_CONFIG_SECTION
60 #define MEMLIMIT_CONFIG_SERVICE MEMLIMIT_CONFIG_LIM_PFX "Service"
61 #define MEMLIMIT_CONFIG_GUIAPP MEMLIMIT_CONFIG_LIM_PFX "GUIApp"
62 #define MEMLIMIT_CONFIG_WIDGET MEMLIMIT_CONFIG_LIM_PFX "Widget"
63 #define MEMLIMIT_CONFIG_BGAPP MEMLIMIT_CONFIG_LIM_PFX "BgApp"
64 #define MIN_LIMIT_VALUE MBYTE_TO_BYTE(1) /* Byte */
67 /* check only swap usage also */
69 /* register OOM event and control application in kernel side */
71 /* register threshold event and control application in resourced*/
75 static enum mem_limit_type mem_limit;
78 static GHashTable *memory_limit_hash;
79 static char *registerpath;
80 static unsigned int mem_service_limit;
81 static unsigned int mem_widget_limit;
82 static unsigned int mem_guiapp_limit;
83 static unsigned int mem_bgapp_limit;
85 static int mem_service_action = PROC_ACTION_KILL;
86 static int mem_widget_action = PROC_ACTION_KILL;
87 static int mem_guiapp_action = PROC_ACTION_KILL;
88 static int mem_bgapp_action = PROC_ACTION_KILL;
95 struct memory_limit_log {
101 static int get_pid_use_max_memory(GArray *pids_array, unsigned int *max_mem)
104 struct memory_info mi = {0, 0};
106 if (!pids_array->len)
107 return RESOURCED_ERROR_NO_DATA;
109 for (i = 0; i < pids_array->len; i++) {
113 mpid = g_array_index(pids_array, pid_t, i);
117 ret = proc_get_mem_usage(mpid, &size);
118 if (ret != RESOURCED_ERROR_NONE)
121 _D("%d used %d memory", mpid, size);
123 if (size > mi.size) {
129 _D("%d used max %d memory", mi.pid, mi.size);
133 static pid_t get_main_pid(const char *dir, unsigned int *max_mem)
135 GArray *pids_array = NULL;
136 _cleanup_free_ char *path = NULL;
140 ret = asprintf(&path, "%s/", dir);
144 ret = cgroup_get_pids(path, &pids_array);
145 if (ret < 0 || !pids_array->len) {
146 g_array_free(pids_array, true);
147 return RESOURCED_ERROR_NO_DATA;
150 main_pid = get_pid_use_max_memory(pids_array, max_mem);
151 g_array_free(pids_array, true);
155 static void memory_limit_hash_destroy(gpointer data)
157 struct memory_limit_event *mle = (struct memory_limit_event *)data;
159 _E("[DEBUG] Memory limit event structure is NULL");
169 /* NB: `mle->fdh` is NOT cleaned up here. This is because the removal
170 * of `memory_limit_event` only happens in two cases:
172 * a) at the end of `lowmem_limit_cb`, which returns `false`
173 * on removal, which results in `fd_handler` getting cleaned
174 * up independently (see `channel_changed` in `fd-handler.c`)
176 * b) at exit, by which time GIO channels should've already been
177 * cleaned up (due to `g_main_loop_quit` et al.). */
182 static int lowmem_limit_broadcast(pid_t pid)
185 char appname[PROC_NAME_MAX];
186 struct proc_app_info *pai = NULL;
189 pai = find_app_info(pid);
193 ret = proc_get_cmdline(pid, appname, sizeof appname);
195 _E("[DEBUG] Failed to get cmdline basename of pid(%d)", pid);
201 ret = d_bus_broadcast_signal_gvariant(RESOURCED_PATH_OOM,
202 RESOURCED_INTERFACE_OOM, SIGNAL_OOM_MEMLIMIT_EVENT,
203 g_variant_new("(is)", pid, appid));
205 _E("[DEBUG] Fail to broadcast dbus signal with pid(%d), appname(%s)", pid, appname);
210 static gboolean liveness_check_cb(gpointer data)
212 struct memory_limit_event *mle = (struct memory_limit_event *)data;
215 _E("[DEBUG] memory limit event structure is NULL");
220 _E("[DEBUG] pid should be larger than 0");
224 if (kill(mle->pid, 0) == 0) {
225 safe_kill(mle->pid, SIGKILL);
231 return G_SOURCE_REMOVE;
234 static bool memory_action_cb(int fd, void *data)
238 uint32_t usage, max_mem;
240 char *cg_dir = (char *)data;
241 struct memory_limit_event *mle;
242 _cleanup_free_ struct cgroup_memory_stat *mem_stat = NULL;
244 mle = g_hash_table_lookup(memory_limit_hash, cg_dir);
246 _E("invalid event\n");
250 result = read(fd, &dummy_efd, sizeof(dummy_efd));
252 _E("[DEBUG] wrong eventfd %s\n", cg_dir);
256 if (access(cg_dir, F_OK) == -1) {
257 _D("[DEBUG] there is no (%s) cgroup any longer, removing it", cg_dir);
261 result = cgroup_read_node_uint32(cg_dir, MEMCG_SWAP_USAGE, &usage);
263 result = cgroup_read_node_uint32(cg_dir, MEMCG_USAGE, &usage);
265 _D("[DEBUG] there is no (%s) cgroup any longer, removed it", cg_dir);
270 if (usage < mle->threshold) {
271 _D("[DEBUG] (%s) cgroup escaped low memory status. usage(%d), threshold(%d)",
272 cg_dir, usage, mle->threshold);
276 result = memcg_get_memory_stat(cg_dir, &mem_stat);
278 _E("[DEBUG] Failed to get memory status : %s", cg_dir);
282 /* anon_usage = mem_stat->value[CGROUP_MEMORY_STAT_RSS] +
283 mem_stat->value[CGROUP_MEMORY_STAT_SWAP];
284 if (anon_usage < mle->threshold) {
285 _D("[DEBUG] (%s) cgroup escaped low memory status. usage(%d), anon usage (%d), threshold(%d)",
286 cg_dir, usage, anon_usage, mle->threshold);
290 switch (mle->action) {
291 case PROC_ACTION_BROADCAST:
292 main_pid = get_main_pid(cg_dir, &max_mem);
294 _D("[DEBUG] there is no victim, removed cgroup : %s", cg_dir);
298 if (lowmem_limit_broadcast(main_pid)) {
299 _E("[DEBUG] Failed to broadcast of process (%s)", cg_dir);
303 case PROC_ACTION_RECLAIM:
304 lowmem_trigger_swap(0, cg_dir, false);
306 case PROC_ACTION_KILL:
307 main_pid = get_main_pid(cg_dir, &max_mem);
309 _D("[DEBUG] there is no victim, removed cgroup : %s", cg_dir);
312 safe_kill(main_pid, SIGTERM);
314 if (mle->pid == -1) {
316 g_timeout_add_seconds(2, liveness_check_cb, mle);
321 _E("[DEBUG] Unkown action of memory threshold");
327 g_hash_table_remove(memory_limit_hash, cg_dir);
331 int lowmem_reassign_limit(const char *dir,
332 unsigned int limit, enum proc_action action)
335 fd_handler_h fdh = NULL;
337 struct memory_limit_event *mle = NULL;
338 char buf[MAX_DEC_SIZE(int)] = {0};
340 if (memory_limit_hash) {
341 /* TO DO: currently concurrent processes with same app name are located
342 * in the same directory.
343 * Fix to distinguish processes with the same app name
345 hash_entry = g_hash_table_lookup(memory_limit_hash, dir);
347 mle = (struct memory_limit_event *)hash_entry;
348 if (mle->threshold == limit) {
349 return RESOURCED_ERROR_NONE;
354 memory_limit_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
355 NULL, memory_limit_hash_destroy);
356 if (!memory_limit_hash) {
357 _E("[DEBUG] Failed to create hash table");
358 return RESOURCED_ERROR_FAIL;
362 check_oom_and_set_limit(dir, limit * 1.2);
363 snprintf(buf, sizeof(buf), "%d", limit);
366 mle->threshold = limit;
367 memcg_init_eventfd(mle->fd, dir, registerpath, buf);
368 return RESOURCED_ERROR_NONE;
371 fd = memcg_set_eventfd(dir, registerpath, buf);
373 mle = calloc(1, sizeof(struct memory_limit_event));
375 _E("[DEBUG] out of memory");
376 return RESOURCED_ERROR_OUT_OF_MEMORY;
379 mle->path = (char *)strdup(dir);
381 _E("[DEBUG] out of memory");
383 return RESOURCED_ERROR_OUT_OF_MEMORY;
385 mle->action = action;
386 mle->threshold = limit;
388 add_fd_read_handler(fd, memory_action_cb, mle->path, NULL, &fdh);
390 g_hash_table_insert(memory_limit_hash, (gpointer)mle->path,
393 return RESOURCED_ERROR_NONE;
399 int lowmem_limit_move_cgroup(struct proc_app_info *pai)
402 _cleanup_free_ char *path = NULL;
406 return RESOURCED_ERROR_NO_DATA;
408 if (!pai->memory.use_mem_limit)
409 return RESOURCED_ERROR_NO_DATA;
411 ret = asprintf(&path, "%s/%s", MEMCG_HIGH_PP_PATH, pai->appid);
413 _E("not enough memory");
414 return RESOURCED_ERROR_OUT_OF_MEMORY;
416 cgroup_write_pid_fullpath(path, pai->main_pid);
418 gslist_for_each_item(iter, pai->childs)
419 cgroup_write_pid_fullpath(path, GPOINTER_TO_PID(iter->data));
421 return RESOURCED_ERROR_NONE;
424 /*static int memlimit_config_parse(struct parse_result *result, void *user_data)
426 if (!result->section)
427 return RESOURCED_ERROR_NONE;
429 if (strncmp(result->section, MEMLIMIT_CONFIG_SECTION,
430 sizeof(MEMLIMIT_CONFIG_SECTION)))
431 return RESOURCED_ERROR_NONE;
433 if (!result->name || !result->value)
434 return RESOURCED_ERROR_NONE;
436 if (!strncmp(result->name, MEMLIMIT_CONFIG_LIM_PFX, sizeof(MEMLIMIT_CONFIG_LIM_PFX)-1)) {
437 const int limit = atoi(result->value);
438 if (!strcmp(result->name, MEMLIMIT_CONFIG_SERVICE))
439 mem_service_limit = limit;
440 else if (!strcmp(result->name, MEMLIMIT_CONFIG_GUIAPP))
441 mem_guiapp_limit = limit;
442 else if (!strcmp(result->name, MEMLIMIT_CONFIG_WIDGET))
443 mem_widget_limit = limit;
444 else if (!strcmp(result->name, MEMLIMIT_CONFIG_BGAPP))
445 mem_bgapp_limit = limit;
448 return RESOURCED_ERROR_NONE;
451 void lowmem_limit_set_system_service(pid_t pid, unsigned int limit,
452 const char *name, enum proc_action action)
454 _cleanup_free_ char *path = NULL;
456 unsigned int totalram = lowmem_get_totalram();
458 if (limit < MIN_LIMIT_VALUE || limit > totalram) {
459 _E("[DEBUG] It's meaningless to set memory limit with size (%d)", limit);
464 _E("[DEBUG] service name is NULL");
468 result = asprintf(&path, "%s/%s", MEMCG_HIGH_PP_PATH, name);
470 _E("[DEBUG] not enough memory");
474 result = cgroup_make_subdir(MEMCG_HIGH_PP_PATH, name, NULL);
476 _E("[DEBUG] Failed to create cgroup subdir '%s/%s'",
477 MEMCG_HIGH_PP_PATH, name);
481 result = lowmem_reassign_limit(path, limit, action);
483 _W("Failed to reassign limit for %s", path);
487 result = cgroup_write_node_uint32(path, MEMCG_MOVE_CHARGE, 3U);
489 _W("[DEBUG] Failed to set immigrate mode for %s (non-crucial, continuing)", path);
491 cgroup_write_pid_fullpath(path, pid);
494 int lowmem_limit_set_app(unsigned int limit, struct proc_app_info *pai,
495 enum proc_action action)
497 _cleanup_free_ char *path = NULL;
500 unsigned int totalram = lowmem_get_totalram();
502 if (limit < MIN_LIMIT_VALUE || limit > totalram) {
503 _E("[DEBUG] It's meaningless to set memory limit with size (%d)", limit);
504 return RESOURCED_ERROR_INVALID_PARAMETER;
508 _E("process app information is NULL");
509 return RESOURCED_ERROR_INVALID_PARAMETER;
512 result = asprintf(&path, "%s/%s", MEMCG_HIGH_PP_PATH, pai->appid);
514 _E("not enough memory");
515 return RESOURCED_ERROR_OUT_OF_MEMORY;
518 result = cgroup_make_subdir(MEMCG_HIGH_PP_PATH, pai->appid, NULL);
520 _E("Failed to create cgroup subdir '%s/%s'",
521 MEMCG_HIGH_PP_PATH, pai->appid);
525 result = lowmem_reassign_limit(path, limit, action);
527 _W("Failed to reassign limit for %s", path);
531 result = cgroup_write_node_uint32(path, MEMCG_MOVE_CHARGE, 3U);
533 _W("Failed to set immigrate mode for %s (non-crucial, continuing)", path);
535 cgroup_write_pid_fullpath(path, pai->main_pid);
537 gslist_for_each_item(iter, pai->childs)
538 cgroup_write_pid_fullpath(path, GPOINTER_TO_PID(iter->data));
541 pai->memory.use_mem_limit = true;
543 return RESOURCED_ERROR_NONE;
546 static int lowmem_limit_app(void *data)
552 struct proc_limit_status *pls = (struct proc_limit_status *)data;
554 error = lowmem_limit_set_app(pls->limit, pls->ps.pai, pls->action);
556 pls->ps.pai->memory.memlimit_update_exclude = true;
557 return RESOURCED_ERROR_NONE;
560 static int lowmem_limit_system_service(void *data)
564 struct proc_limit_status *pls = (struct proc_limit_status *)data;
566 lowmem_limit_set_system_service(pls->ps.pid, pls->limit, pls->ps.pci->name, pls->action);
567 return RESOURCED_ERROR_NONE;
570 static int lowmem_limit_service(void *data)
574 struct proc_status *ps = (struct proc_status *)data;
576 if (ps->pai && ps->pai->memory.memlimit_update_exclude)
577 return RESOURCED_ERROR_NONE;
579 if (mem_service_limit) {
580 lowmem_limit_set_app(mem_service_limit, ps->pai, mem_service_action);
582 return RESOURCED_ERROR_NONE;
585 static int lowmem_limit_appwidget(void *data)
589 struct proc_status *ps = (struct proc_status *)data;
591 if (ps->pai && ps->pai->memory.memlimit_update_exclude)
592 return RESOURCED_ERROR_NONE;
594 if (mem_guiapp_limit && ps->pai->type == PROC_TYPE_GUI) {
595 lowmem_limit_set_app(mem_guiapp_limit, ps->pai, mem_guiapp_action);
597 if (mem_widget_limit && ps->pai->type == PROC_TYPE_WIDGET) {
598 lowmem_limit_set_app(mem_widget_limit, ps->pai, mem_widget_action);
601 return RESOURCED_ERROR_NONE;
604 static int lowmem_limit_bgapp(void *data)
608 struct proc_status *ps = (struct proc_status *)data;
610 if (ps->pai && ps->pai->memory.memlimit_update_exclude)
611 return RESOURCED_ERROR_NONE;
613 lowmem_limit_set_app(mem_bgapp_limit, ps->pai, mem_bgapp_action);
615 return RESOURCED_ERROR_NONE;
618 static int lowmem_limit_fgapp(void *data)
622 struct proc_status *ps = (struct proc_status *)data;
624 if ((mem_guiapp_limit && ps->pai->type == PROC_TYPE_GUI) ||
625 (mem_widget_limit && ps->pai->type == PROC_TYPE_WIDGET))
626 return lowmem_limit_appwidget(data);
628 _E("[DEBUG] Unable to set foreground app limit - app type not supported");
630 return RESOURCED_ERROR_NONE;
633 void lowmem_memory_init(unsigned int service_limit, unsigned int widget_limit,
634 unsigned int guiapp_limit, unsigned int bgapp_limit)
636 mem_service_limit = service_limit;
637 mem_widget_limit = widget_limit;
638 mem_guiapp_limit = guiapp_limit;
639 mem_bgapp_limit = bgapp_limit;
642 void lowmem_action_init(int service_action, int widget_action,
643 int guiapp_action, int bgapp_action)
645 if (service_action > 0)
646 mem_service_action = service_action;
648 if (widget_action > 0)
649 mem_widget_action = widget_action;
651 if (guiapp_action > 0)
652 mem_guiapp_action = guiapp_action;
654 if (bgapp_action > 0)
655 mem_bgapp_action = bgapp_action;
658 void lowmem_limit_init(void)
663 mem_limit = MEM_LIMIT_TRHESHOLD;
664 result = cgroup_read_node_uint32(MEMCG_PATH, MEMCG_SWAP_USAGE, &usage);
665 if (result == RESOURCED_ERROR_NONE)
666 registerpath = MEMCG_SWAP_USAGE;
668 registerpath = MEMCG_USAGE;
670 register_notifier(RESOURCED_NOTIFIER_LIMIT_SYSTEM_SERVICE, lowmem_limit_system_service);
671 register_notifier(RESOURCED_NOTIFIER_LIMIT_APP, lowmem_limit_app);
672 if (mem_limit == MEM_LIMIT_NONE)
675 if (mem_service_limit)
676 register_notifier(RESOURCED_NOTIFIER_SERVICE_LAUNCH, lowmem_limit_service);
677 if (mem_guiapp_limit || mem_widget_limit)
678 register_notifier(RESOURCED_NOTIFIER_APP_LAUNCH, lowmem_limit_appwidget);
679 if (mem_bgapp_limit) {
680 if (!mem_guiapp_limit || !mem_widget_limit) {
681 _W("Background app limit requires that both GUIApp and Widget limits to be set to work properly. Ignoring.");
683 register_notifier(RESOURCED_NOTIFIER_APP_BACKGRD, lowmem_limit_bgapp);
684 register_notifier(RESOURCED_NOTIFIER_APP_FOREGRD, lowmem_limit_fgapp);
685 register_notifier(RESOURCED_NOTIFIER_WIDGET_FOREGRD, lowmem_limit_fgapp);
686 register_notifier(RESOURCED_NOTIFIER_WIDGET_BACKGRD, lowmem_limit_bgapp);
691 void lowmem_limit_exit(void)
693 if (memory_limit_hash) {
694 g_hash_table_destroy(memory_limit_hash);
696 memory_limit_hash = NULL;
700 unregister_notifier(RESOURCED_NOTIFIER_LIMIT_SYSTEM_SERVICE, lowmem_limit_system_service);
701 unregister_notifier(RESOURCED_NOTIFIER_LIMIT_APP, lowmem_limit_app);
702 unregister_notifier(RESOURCED_NOTIFIER_SERVICE_LAUNCH, lowmem_limit_service);
703 unregister_notifier(RESOURCED_NOTIFIER_APP_LAUNCH, lowmem_limit_appwidget);
704 unregister_notifier(RESOURCED_NOTIFIER_APP_BACKGRD, lowmem_limit_bgapp);
705 unregister_notifier(RESOURCED_NOTIFIER_APP_FOREGRD, lowmem_limit_fgapp);
706 unregister_notifier(RESOURCED_NOTIFIER_WIDGET_FOREGRD, lowmem_limit_fgapp);
707 unregister_notifier(RESOURCED_NOTIFIER_WIDGET_BACKGRD, lowmem_limit_bgapp);