#include "const.h"
#include "file-helper.h"
#include "proc-common.h"
+#include "proc-main.h"
#include "util.h"
#define MEMCG_PATH "/sys/fs/cgroup/memory/"
#define MEMCG_SIZE_LIMIT "memory.limit_in_bytes"
+#define SWAPCG_RECLAIM "memory.force_reclaim"
#define MOVE_CHARGE "memory.move_charge_at_immigrate"
#define SWAP_ON_EXEC_PATH "/sbin/swapon"
#define SWAP_CONF_FILE RD_CONFIG_FILE(swap)
#define SWAP_CONTROL_SECTION "CONTROL"
+#define SWAP_CONF_ENABLE "SWAP_ENABLE"
#define SWAP_CONF_STREAMS "MAX_COMP_STREAMS"
#define SWAP_CONF_ALGORITHM "COMP_ALGORITHM"
#define SWAP_CONF_RATIO "RATIO"
#define SWAP_RATIO 0.5
#define SWAP_FULLNESS_RATIO 0.8
#define SWAP_HARD_LIMIT_DEFAULT 0.5
+#define SWAP_FORCE_RECLAIM_NUM_MAX 5
+#define SWAP_RECLIAM_PAGES_MAX 2560
+#define SWAP_RECLIAM_PAGES_MIN 128
enum swap_thread_op {
SWAP_OP_ACTIVATE,
SWAP_OP_RECLAIM,
SWAP_OP_COMPACT,
+ SWAP_OP_MOVE_TO_SWAP_AND_RECLAIM,
SWAP_OP_END,
};
+enum swap_reclaim_node {
+ SWAP_NODE_HARD_LIMIT,
+ SWAP_NODE_FORCE_RECLAIM,
+ SWAP_NODE_END,
+};
+
struct swap_task {
struct proc_app_info *pai;
int size;
};
struct swap_zram_control {
+ int swap_enable;
int max_comp_streams;
char comp_algorithm[5];
float ratio;
unsigned long swap_size_bytes;
unsigned long swap_almost_full_bytes;
+ enum swap_state swap_status;
};
struct swap_safe_queue {
};
static struct swap_zram_control swap_control = {
+ .swap_enable = 0,
.max_comp_streams = -1,
.comp_algorithm = "lzo",
.ratio = SWAP_RATIO,
.swap_size_bytes = 0,
.swap_almost_full_bytes = 0,
+ .swap_status = SWAP_OFF,
};
static pthread_mutex_t swap_mutex;
static pthread_cond_t swap_cond;
static struct swap_safe_queue swap_thread_queue;
-static struct module_ops swap_modules_ops;
-static char *swap_thread_op_names[SWAP_OP_END] = {
- "ACTIVATE",
- "RECLAIM",
- "COMPACT",
-};
+static const struct module_ops swap_modules_ops;
static float swap_hard_limit_fraction = SWAP_HARD_LIMIT_DEFAULT;
+static enum swap_reclaim_node swap_node;
static int swap_compact_handler(void *data);
return "";
}
-static void swap_set_state(enum swap_state state)
+enum swap_state swap_get_state(void)
{
- struct shared_modules_data *modules_data = get_shared_modules_data();
-
- ret_msg_if(modules_data == NULL,
- "Invalid shared modules data\n");
+ return swap_control.swap_status;
+}
+static void swap_set_state(enum swap_state state)
+{
if ((state != SWAP_ON) && (state != SWAP_OFF))
return;
- modules_data->swap_data.swap_state = state;
+ swap_control.swap_status = state;
}
-
-static pid_t swap_change_state(enum swap_state state)
+static int swap_change_state(enum swap_state state)
{
- int status;
- pid_t child_pid;
- pid_t pid = fork();
-
- if (pid < 0) {
- _E("failed to fork");
- return RESOURCED_ERROR_FAIL;
- }
-
- /* child */
- if (pid == 0) {
- if (state == SWAP_ON)
- execl(SWAP_ON_EXEC_PATH, SWAP_ON_EXEC_PATH, "-d",
- SWAP_ZRAM_DEVICE, (char *)NULL);
- else if (state == SWAP_OFF)
- execl(SWAP_OFF_EXEC_PATH, SWAP_OFF_EXEC_PATH,
- SWAP_ZRAM_DEVICE, (char *)NULL);
- exit(0);
- }
-
- /* parent */
- child_pid = waitpid(pid, &status, 0);
- if (child_pid < 0) {
- _E("can't wait for a pid %d %d: %m", pid, status);
- return child_pid;
- }
-
- swap_set_state(state);
- return pid;
+ int ret;
+ const char *argv[4] = {NULL, };
+
+ if (state == SWAP_ON) {
+ argv[0] = SWAP_ON_EXEC_PATH;
+ argv[1] = "-d";
+ argv[2] = SWAP_ZRAM_DEVICE;
+ } else if (state == SWAP_OFF) {
+ argv[0] = SWAP_OFF_EXEC_PATH;
+ argv[1] = SWAP_ZRAM_DEVICE;
+ } else
+ return RESOURCED_ERROR_INVALID_PARAMETER;
+
+ ret = exec_cmd(ARRAY_SIZE(argv), argv);
+ if (!ret)
+ swap_set_state(state);
+ return ret;
}
static unsigned int swap_calculate_hard_limit_in_bytes(unsigned int mem_subcg_usage)
return ((int)(tb->size) - (int)(ta->size));
}
-static int swap_prepare_victims(GArray *candidates)
-{
- _cleanup_app_list_close_ GSList *proc_app_list = PAL_INIT_VALUE;
- GSList *iter = NULL;
- struct proc_app_info *pai = NULL;
- struct swap_task victim;
-
- /*
- * serch victims from proc_app_list
- * It was better than searching backround cgroup
- * because proc_app_list had already known current state and child processes
- */
- proc_app_list = proc_app_list_open();
- gslist_for_each_item(iter, proc_app_list) {
- pai = (struct proc_app_info *)iter->data;
- if (pai->memory.memcg_idx != MEMCG_BACKGROUND)
- continue;
- if (pai->lru_state <= PROC_BACKGROUND)
- continue;
-
- memset(&victim, 0, sizeof(struct swap_task));
- victim.pai = pai;
- g_array_append_val(candidates, victim);
- }
- return candidates->len;
-}
-
static int swap_reduce_victims(GArray *candidates, int max)
{
int index;
return RESOURCED_ERROR_NONE;
}
-static int swap_reclaim_memcg(struct swap_status_msg msg)
+static int swap_use_hard_limit(struct memcg_info *mi)
{
int ret;
- unsigned long swap_usage;
unsigned int usage, memcg_limit;
+ ret = memcg_get_usage(mi, &usage);
+ if (ret != RESOURCED_ERROR_NONE)
+ usage = 0;
+
+ memcg_limit = swap_calculate_hard_limit_in_bytes(usage);
+ _D("Swap request: %s cgroup usage is %lu, hard limit set to %lu (hard limit fraction %f)",
+ mi->name, usage, memcg_limit, swap_hard_limit_fraction);
+ ret = cgroup_write_node_uint32(mi->name, MEMCG_SIZE_LIMIT, memcg_limit);
+ if (ret != RESOURCED_ERROR_NONE)
+ _E("Not able to set hard limit of %s memory cgroup", mi->name);
+
+ return ret;
+}
+
+static int swap_use_force_reclaim(struct memcg_info *mi)
+{
+ int ret;
+ int try = SWAP_FORCE_RECLAIM_NUM_MAX;
+ unsigned int usage, usage_after_reclaim, nr_to_reclaim;
+
+ do {
+ /*
+ * Currently, we only move only anonymous pages to swap memcg by
+ * setting move_charge_at_immigrate as 0. However, there might
+ * be a little of inactive file pages in swap memcg.
+ * For this reason it's better to use '.stat' and calculate only
+ * anoynymous memory usage.
+ */
+ ret = memcg_get_anon_usage(mi, &usage);
+ if (ret != RESOURCED_ERROR_NONE)
+ usage = 0;
+
+ nr_to_reclaim = BYTE_TO_PAGE(usage);
+ if (nr_to_reclaim <= SWAP_RECLIAM_PAGES_MIN)
+ break; /* don't reclaim if little gain */
+ if (nr_to_reclaim > SWAP_RECLIAM_PAGES_MAX)
+ nr_to_reclaim = SWAP_RECLIAM_PAGES_MAX;
+
+ ret = cgroup_write_node_uint32(mi->name, SWAPCG_RECLAIM,
+ nr_to_reclaim);
+ if (ret != RESOURCED_ERROR_NONE)
+ break; /* if we can't reclaim don't continue */
+
+ ret = memcg_get_anon_usage(mi, &usage_after_reclaim);
+ if (ret != RESOURCED_ERROR_NONE)
+ usage_after_reclaim = 0;
+
+ if (usage_after_reclaim >= usage)
+ break; /* if we didn't reclaim more, let's stop */
+
+ _D("FORCE_RECLAIM try: %d, before: %d, after: %d",
+ try, usage, usage_after_reclaim);
+ try -= 1;
+ } while (try > 0);
+
+ return ret;
+}
+
+static int swap_reclaim_memcg(struct swap_status_msg msg)
+{
+ unsigned long swap_usage;
+
/* Test for restarted resourced, where zram already activated */
if (swap_control.swap_size_bytes == 0) {
swap_control.swap_size_bytes = swap_get_disksize_bytes();
return RESOURCED_ERROR_NONE;
}
- ret = memcg_get_usage(msg.info, &usage);
- if (ret != RESOURCED_ERROR_NONE)
- usage = 0;
-
- memcg_limit = swap_calculate_hard_limit_in_bytes(usage);
- _D("Swap request: %s cgroup usage is %lu, hard limit set to %lu (hard limit fraction %f)",
- msg.info->name, usage, memcg_limit, swap_hard_limit_fraction);
- ret = cgroup_write_node_uint32(msg.info->name, MEMCG_SIZE_LIMIT, memcg_limit);
- if (ret != RESOURCED_ERROR_NONE)
- _E("Not able to set hard limit of %s memory cgroup", msg.info->name);
-
- return ret;
+ if (swap_node == SWAP_NODE_FORCE_RECLAIM)
+ return swap_use_force_reclaim(msg.info);
+ else
+ return swap_use_hard_limit(msg.info);
}
static int swap_compact_zram(void)
return RESOURCED_ERROR_NONE;
}
-static int swap_move_background_to_swap(struct swap_status_msg *msg)
+static int swap_move_inactive_to_swap(struct swap_status_msg *msg)
{
- int max_victims, selected;
- int ret = RESOURCED_ERROR_NONE;
- GArray *candidates = NULL, *pids_array = NULL;
+ GSList *proc_app_list = NULL;
+ GSList *iter;
+ int ret, max_victims;
+ struct swap_task victim;
+ GArray *candidates = NULL;
struct memcg *memcg_swap = NULL;
-
- pids_array = g_array_new(false, false, sizeof(pid_t));
- if (!pids_array) {
- _E("failed to allocate memory");
- ret = RESOURCED_ERROR_OUT_OF_MEMORY;
- goto out;
- }
-
- /* Get procs to check for swap candidates */
- memcg_get_pids(msg->info, pids_array);
- if (pids_array->len == 0) {
- ret = RESOURCED_ERROR_NO_DATA;
- goto out;
- }
- /*
- * background cgroup finds victims and moves them to swap group
- */
- ret = lowmem_get_memcg(MEMCG_SWAP, &memcg_swap);
- if (ret != RESOURCED_ERROR_NONE)
- return RESOURCED_ERROR_FAIL;
+ struct proc_app_info *pai = NULL;
candidates = g_array_new(false, false, sizeof(struct swap_task));
if (!candidates) {
_E("failed to allocate memory");
- ret = RESOURCED_ERROR_OUT_OF_MEMORY;
- goto out;
+ return RESOURCED_ERROR_OUT_OF_MEMORY;
+ }
+ memset(&victim, 0, sizeof(struct swap_task));
+
+ proc_app_list = proc_app_list_open();
+ gslist_for_each_item(iter, proc_app_list) {
+ pai = (struct proc_app_info *)iter->data;
+ if ((!pai->main_pid) ||
+ (pai->memory.memcg_info != msg->info) ||
+ (pai->memory.oom_score_adj < OOMADJ_BACKGRD_UNLOCKED) ||
+ (pai->lru_state <= PROC_BACKGROUND))
+ continue;
+ victim.pai = pai;
+ g_array_append_val(candidates, victim);
}
+ proc_app_list_close();
/*
- * Let's consider 50% of background apps to be swappable. Using ZRAM
- * swap makes the operation on swap cheaper. Only anonymous memory
- * is swaped so the results are limited by size of allocations.
+ * Let's consider 50% of inactive apps to be swappable at once.
*/
- max_victims = pids_array->len >> 1;
- /* It makes no sense if we will have no candidates */
+ max_victims = candidates->len >> 1;
if (max_victims == 0) {
ret = RESOURCED_ERROR_NO_DATA;
goto out;
}
- if (max_victims > SWAP_SORT_MAX)
- max_victims = SWAP_SORT_MAX;
-
- selected = swap_prepare_victims(candidates);
- if (selected == 0) {
- ret = RESOURCED_ERROR_NO_DATA;
- _D("no victims from proc_app_list (pids: %d)", max_victims);
+ swap_reduce_victims(candidates, max_victims);
+ ret = lowmem_get_memcg(MEMCG_SWAP, &memcg_swap);
+ if (ret != RESOURCED_ERROR_NONE)
goto out;
- } else if (selected > max_victims)
- swap_reduce_victims(candidates, max_victims);
-
/*
- * change swap info from background cgroup to swap group
+ * change swap info from inactive cgroup to swap group
* for using same structure to move and swap it
*/
msg->info = memcg_swap->info;
msg->type = MEMCG_SWAP;
- swap_move_to_cgroup(msg->info, candidates);
+ ret = swap_move_to_cgroup(msg->info, candidates);
out:
if (candidates)
g_array_free(candidates, TRUE);
- if (pids_array)
- g_array_free(pids_array, TRUE);
- return ret;
+ return ret;
}
static int swap_size(void)
static int swap_mkswap(void)
{
- pid_t pid = fork();
-
- if (pid < 0) {
- _E("fork for mkswap failed");
- return pid;
- } else if (pid == 0) {
- _D("mkswap starts");
- execl(SWAP_MKSWAP_EXEC_PATH, SWAP_MKSWAP_EXEC_PATH,
- SWAP_ZRAM_DEVICE, (char *)NULL);
- exit(0);
- } else {
- wait(0);
- _D("mkswap ends");
- }
+ const char *argv[3] = {SWAP_MKSWAP_EXEC_PATH, SWAP_ZRAM_DEVICE, NULL};
- return pid;
+ return exec_cmd(ARRAY_SIZE(argv), argv);
}
static int swap_zram_activate(void)
static void swap_activate_in_module(void)
{
+ int ret;
int disksize;
+ unsigned int swap_size;
if (swap_get_state() == SWAP_ON)
return;
disksize = swap_get_disksize_bytes();
if (disksize <= 0) {
- if (swap_zram_activate() < 0) {
+ ret = swap_zram_activate();
+ if (ret < 0) {
_E("swap cannot be activated");
return;
}
}
- swap_change_state(SWAP_ON);
+
+ ret = swap_change_state(SWAP_ON);
+ if (ret != 0) {
+ /*
+ * It is possible that resourced was restared etc. and swap
+ * was already activated in the system earlier (the system wasn't
+ * restarted).
+ * Then 'swapon' in swap_change_state will return error, we
+ * should check if swap was already enabled and if yes handle it.
+ */
+ swap_size = proc_get_swap_total();
+ if (swap_size == 0) {
+ _E("swap state cannot be changed");
+ return;
+ }
+ swap_set_state(SWAP_ON);
+ }
_D("swap activated");
}
static void *swap_thread_main(void * data)
{
- int is_empty;
+ int is_empty, ret;
struct swap_thread_bundle *bundle;
setpriority(PRIO_PROCESS, 0, SWAP_PRIORITY);
case SWAP_OP_COMPACT:
swap_compact_zram();
break;
+ /* Move inactive procesess to swap, and reclaim after that. */
+ case SWAP_OP_MOVE_TO_SWAP_AND_RECLAIM:
+ ret = swap_move_inactive_to_swap(&(bundle->msg));
+ /* Check if any process was moved to swap. */
+ if (ret == RESOURCED_ERROR_NONE)
+ swap_reclaim_memcg(bundle->msg);
+ break;
case SWAP_OP_END:
default:
_D("wrong swap thread operation selected");
return NULL;
}
-static int swap_start_handler(void *data)
+static int swap_communicate_thread(struct swap_thread_bundle *bundle)
{
int ret;
- struct swap_thread_bundle *bundle;
-
- if (!data)
- return RESOURCED_ERROR_NO_DATA;
- bundle = malloc(sizeof(struct swap_thread_bundle));
if (!bundle)
- return RESOURCED_ERROR_OUT_OF_MEMORY;
-
- bundle->op = SWAP_OP_RECLAIM;
- memcpy(&(bundle->msg), data, sizeof(struct swap_status_msg));
+ return RESOURCED_ERROR_NO_DATA;
- if (bundle->msg.type == MEMCG_BACKGROUND) {
- ret = swap_move_background_to_swap(&(bundle->msg));
- /* add bundle only if some processes were moved into swap memcg */
- if (ret) {
- free(bundle);
- return RESOURCED_ERROR_NO_DATA;
- }
- }
swap_add_bundle(bundle);
-
/* Try to signal swap thread, that there is some work to do */
ret = pthread_mutex_trylock(&swap_mutex);
if (ret == 0) {
pthread_mutex_unlock(&swap_mutex);
_I("send signal to swap thread");
return RESOURCED_ERROR_NONE;
- }
-
- if (ret && ret == EBUSY) {
+ } else if (ret == EBUSY) {
_D("swap thread already active");
- } else {
- _E("pthread_mutex_trylock fail : %d, errno : %d", ret, errno);
- return RESOURCED_ERROR_FAIL;
+ return RESOURCED_ERROR_NONE;
}
- return RESOURCED_ERROR_NONE;
+ _E("pthread_mutex_trylock fail: %d, errno: %d", ret, errno);
+ return RESOURCED_ERROR_FAIL;
+
}
-static int swap_simple_bundle_sender(enum swap_thread_op operation)
+static int swap_start_handler(void *data)
{
int ret;
struct swap_thread_bundle *bundle;
+ if (!data)
+ return RESOURCED_ERROR_NO_DATA;
+
bundle = malloc(sizeof(struct swap_thread_bundle));
if (!bundle)
return RESOURCED_ERROR_OUT_OF_MEMORY;
- bundle->op = operation;
- swap_add_bundle(bundle);
-
- if (operation >= 0 && operation < SWAP_OP_END)
- _D("added %s operation to swap queue",
- swap_thread_op_names[operation]);
+ bundle->op = SWAP_OP_RECLAIM;
+ memcpy(&(bundle->msg), data, sizeof(struct swap_status_msg));
- /* Try to signal swap thread, that there is some work to do */
- ret = pthread_mutex_trylock(&swap_mutex);
- if (ret == 0) {
- pthread_cond_signal(&swap_cond);
- pthread_mutex_unlock(&swap_mutex);
- _I("send signal to swap thread");
- return RESOURCED_ERROR_NONE;
+ if (bundle->msg.type == MEMCG_APPS) {
+ /*
+ * Background tasks are concerned special way, we select
+ * tasks and move them to Swap cgroup. They are not there already.
+ */
+ bundle->op = SWAP_OP_MOVE_TO_SWAP_AND_RECLAIM;
}
+ ret = swap_communicate_thread(bundle);
+ return ret;
+}
- if (ret && ret == EBUSY) {
- _D("swap thread already active");
- } else {
- _E("pthread_mutex_trylock fail : %d, errno : %d", ret, errno);
- return RESOURCED_ERROR_FAIL;
- }
- return RESOURCED_ERROR_NONE;
+static int swap_internal_bundle_sender(enum swap_thread_op operation)
+{
+ int ret;
+ struct swap_thread_bundle *bundle;
+
+ bundle = malloc(sizeof(struct swap_thread_bundle));
+ if (!bundle)
+ return RESOURCED_ERROR_OUT_OF_MEMORY;
+ bundle->op = operation;
+ ret = swap_communicate_thread(bundle);
+ return ret;
}
static int swap_activate_handler(void *data)
{
- return swap_simple_bundle_sender(SWAP_OP_ACTIVATE);
+ return swap_internal_bundle_sender(SWAP_OP_ACTIVATE);
}
static int swap_compact_handler(void *data)
{
_I("compaction request. Reason: %s",
compact_reason_to_str((enum swap_compact_reason)data));
- return swap_simple_bundle_sender(SWAP_OP_COMPACT);
+ return swap_internal_bundle_sender(SWAP_OP_COMPACT);
}
/* This function is callback function for the notifier RESOURCED_NOTIFIER_SWAP_UNSET_LIMIT.
int ret, limit;
struct swap_status_msg *msg = data;
+ if (swap_node == SWAP_NODE_FORCE_RECLAIM)
+ return RESOURCED_ERROR_NONE;
+
limit = -1;
ret = cgroup_write_node_int32(msg->info->name, MEMCG_SIZE_LIMIT, limit);
if (ret != RESOURCED_ERROR_NONE)
if (strncmp(result->section, SWAP_CONTROL_SECTION, strlen(SWAP_CONTROL_SECTION)+1))
return RESOURCED_ERROR_NO_DATA;
- if (!strncmp(result->name, SWAP_CONF_STREAMS, strlen(SWAP_CONF_STREAMS)+1)) {
+ if (!strncmp(result->name, SWAP_CONF_ENABLE, strlen(SWAP_CONF_ENABLE)+1)) {
+ int value = atoi(result->value);
+ if (value > 0) {
+ swap_control.swap_enable = value;
+ _D("swap enable is %d", swap_control.swap_enable);
+ }
+ } else if (!strncmp(result->name, SWAP_CONF_STREAMS, strlen(SWAP_CONF_STREAMS)+1)) {
int value = atoi(result->value);
if (value > 0) {
swap_control.max_comp_streams = value;
int ret;
config_parse(SWAP_CONF_FILE, load_swap_config, NULL);
+
+ if (swap_control.swap_enable != 1) {
+ _I("swap module is disabled.");
+ return RESOURCED_ERROR_FAIL;
+ }
+
ret = swap_thread_create();
if (ret) {
_E("swap thread create failed");
return ret;
}
-static int swap_check_node(void)
+static int swap_check_node(char *path)
{
- FILE *fp;
+ _cleanup_fclose_ FILE *fp = NULL;
- fp = fopen(SWAP_ZRAM_DEVICE, "w");
+ fp = fopen(path, "w");
if (fp == NULL) {
- _E("%s open failed", SWAP_ZRAM_DEVICE);
+ _E("%s open failed", path);
return RESOURCED_ERROR_NO_DATA;
}
- fclose(fp);
-
return RESOURCED_ERROR_NONE;
}
static int resourced_swap_check_runtime_support(void *data)
{
- return swap_check_node();
+ return swap_check_node(SWAP_ZRAM_DEVICE);
}
/*
{
int ret;
struct memcg *memcg_swap = NULL;
+ char buf[MAX_PATH_LENGTH];
ret = lowmem_get_memcg(type, &memcg_swap);
if (ret != RESOURCED_ERROR_NONE)
return;
cgroup_write_node_uint32(memcg_swap->info->name, MOVE_CHARGE, 1);
+ snprintf(buf, sizeof(buf), "%s/%s", MEMCG_PATH, SWAPCG_RECLAIM);
+ ret = swap_check_node(buf);
+ if (ret == RESOURCED_ERROR_NONE) {
+ swap_node = SWAP_NODE_FORCE_RECLAIM;
+ _I("use %s node for swapping memory", SWAPCG_RECLAIM);
+ } else {
+ swap_node = SWAP_NODE_HARD_LIMIT;
+ _I("use %s node for swapping memory", MEMCG_SIZE_LIMIT);
+ }
}
static int resourced_swap_init(void *data)
{
int ret;
- cgroup_make_subdir(MEMCG_PATH, "swap", NULL);
resourced_swap_change_memcg_settings(MEMCG_SWAP);
- resourced_swap_change_memcg_settings(MEMCG_FAVORITE);
- resourced_swap_change_memcg_settings(MEMCG_PLATFORM);
swap_set_state(SWAP_OFF);
ret = swap_init();
return RESOURCED_ERROR_NONE;
}
-static struct module_ops swap_modules_ops = {
+static const struct module_ops swap_modules_ops = {
.priority = MODULE_PRIORITY_NORMAL,
.name = "swap",
.init = resourced_swap_init,