8066072af7c176af7dd10ea94526d01fe31db385
[platform/core/system/resourced.git] / src / resource-limiter / memory / lowmem-controller.c
1 /**
2  * resourced
3  *
4  * Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved.
5  *
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
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
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 excontroller or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 /**
20  * @file lowmem-controller.c
21  * @desc Provides controller functionalities to kill procs/apps
22  */
23
24 #include <string.h>
25 #include <limits.h>
26 #include <vconf.h>
27 #include <bundle.h>
28 #include <eventsystem.h>
29
30 #include "lowmem.h"
31 #include "lowmem-limit.h"
32 #include "proc-common.h"
33 #include "proc-main.h"
34 #include "resourced.h"
35 #include "procfs.h"
36 #include "trace.h"
37 #include "util.h"
38 #include "safe-kill.h"
39 #include "module.h"
40 #include "notifier.h"
41 #include "freezer.h"
42 #include "swap-common.h"
43 #include "dedup-common.h"
44
45 #define MAX_VICTIMS_BETWEEN_CHECK  3
46
47 static int num_vict_between_check = MAX_VICTIMS_BETWEEN_CHECK;
48
49 static int lowmem_kill_victim(const struct task_info *tsk, int flags,
50                                 int memps_log, unsigned int *victim_size,
51                                 void(*oom_popup)(void))
52 {
53         pid_t pid;
54         int ret;
55         char appname[PATH_MAX];
56         int sigterm = 0;
57
58         pid = tsk->pid;
59
60         if (pid <= 0 || pid == getpid())
61                 return RESOURCED_ERROR_FAIL;
62
63         ret = proc_get_cmdline(pid, appname, sizeof appname);
64         if (ret == RESOURCED_ERROR_FAIL)
65                 return RESOURCED_ERROR_FAIL;
66
67         if (!strcmp("memps", appname) ||
68                         !strcmp("crash-worker", appname) ||
69                         !strcmp("system-syspopup", appname)) {
70                 _E("%s(%d) was selected, skip it", appname, pid);
71                 return RESOURCED_ERROR_FAIL;
72         }
73
74         /**
75          * if tsk is an app, then proc_app_info_oom_killed is not NULL,
76          * else proc_app_info_oom_killed is NULL.
77          */
78         if (tsk->proc_app_info_oom_killed != NULL) {
79                 resourced_proc_status_change(PROC_CGROUP_SET_TERMINATE_REQUEST,
80                         pid, NULL, NULL, PROC_TYPE_NONE);
81
82                 if (tsk->oom_score_lru <= OOMADJ_BACKGRD_LOCKED) {
83                         sigterm = 1;
84                 } else if (tsk->oom_score_lru > OOMADJ_BACKGRD_LOCKED &&
85                                 tsk->oom_score_lru < OOMADJ_BACKGRD_UNLOCKED) {
86                         sigterm = tsk->proc_app_info_flags & PROC_SIGTERM;
87                 }
88
89                 if (*(tsk->proc_app_info_oom_killed))
90                         sigterm = 0;
91
92                 *(tsk->proc_app_info_oom_killed) = true;
93         }
94
95         if (sigterm)
96                 safe_kill(pid, SIGTERM);
97         else
98                 safe_kill(pid, SIGKILL);
99
100         _D("[LMK] we killed, force(%d), %d (%s) score = %d, size: rss = %u KB, sigterm = %d\n",
101                         flags & OOM_FORCE, pid, appname, tsk->oom_score_adj,
102                         tsk->size, sigterm);
103         *victim_size = tsk->size;
104
105         if (tsk->oom_score_lru > OOMADJ_FOREGRD_UNLOCKED)
106                 return RESOURCED_ERROR_NONE;
107
108         oom_popup();
109
110         return RESOURCED_ERROR_NONE;
111 }
112
113 /* return LOWMEM_RECLAIM_CONT when killing should be continued */
114 static int lowmem_check_kill_continued(struct task_info *tsk, int flags,
115                                         unsigned lmk_start_threshold_mb)
116 {
117         unsigned int available_mb = 0;
118
119         /**
120          * Processes with the priority higher than perceptible are killed
121          * only when the available memory is less than dynamic oom threshold.
122          */
123         if (tsk->oom_score_lru > OOMADJ_BACKGRD_PERCEPTIBLE)
124                 return LOWMEM_RECLAIM_CONT;
125
126         if (flags & (OOM_FORCE | OOM_SINGLE_SHOT)) {
127                 _I("[LMK] %d is dropped during force kill, flag=%d",
128                         tsk->pid, flags);
129                 return LOWMEM_RECLAIM_DROP;
130         }
131         available_mb = proc_get_mem_available();
132         if (available_mb > lmk_start_threshold_mb) {
133                 _I("[LMK] available=%d MB, larger than %u MB, do not kill foreground",
134                         available_mb, lmk_start_threshold_mb);
135                 return LOWMEM_RECLAIM_RETRY;
136         }
137         return LOWMEM_RECLAIM_CONT;
138 }
139
140 static int lowmem_controller_kill_candidates(GArray *candidates,
141                         unsigned should_be_freed, unsigned int threshold,
142                         int max_victims, int flags,
143                         int *status, unsigned int *total_victim_size,
144                         unsigned lmk_start_threshold_mb,
145                         void(*oom_popup)(void))
146 {
147         int ret_kill = 0;
148         int victim_cnt = 0;
149         unsigned int victim_size = 0;
150         unsigned int victim_size_sum = 0;
151         int killer_status = *status;
152         int should_be_freed_kb = MBYTE_TO_KBYTE(should_be_freed);
153
154         for (int i = 0; i < candidates->len; i++) {
155                 struct task_info *tsk;
156
157                 if (i >= max_victims) {
158                         killer_status = LOWMEM_RECLAIM_NEXT_TYPE;
159                         break;
160                 }
161
162                 /**
163                  * Available memory is checking only every
164                  * num_vict_between_check process for reducing burden.
165                  */
166                 if (!(i % num_vict_between_check) &&
167                                 proc_get_mem_available() > threshold) {
168                         killer_status = LOWMEM_RECLAIM_DONE;
169                         break;
170                 }
171
172                 if (!(flags & OOM_NOMEMORY_CHECK) &&
173                                 victim_size_sum >= should_be_freed_kb) {
174                         _D("[LMK] victim=%d, max_victims=%d, total_size=%uKB",
175                                 victim_cnt, max_victims, victim_size_sum);
176                         killer_status = LOWMEM_RECLAIM_DONE;
177                         break;
178                 }
179
180                 tsk = g_array_index(candidates, struct task_info *, i);
181
182                 killer_status = lowmem_check_kill_continued(tsk, flags,
183                                                         lmk_start_threshold_mb);
184                 if (killer_status != LOWMEM_RECLAIM_CONT)
185                         break;
186
187                 _I("[LMK] select victims from proc_app_list pid(%d) with oom_score_adj(%d)\n",
188                                 tsk->pid, tsk->oom_score_adj);
189
190                 ret_kill = lowmem_kill_victim(tsk, flags, i, &victim_size,
191                                                 oom_popup);
192                 if (ret_kill != RESOURCED_ERROR_NONE)
193                         continue;
194                 victim_cnt++;
195                 victim_size_sum += victim_size;
196         }
197
198         *status = killer_status;
199         *total_victim_size = victim_size_sum;
200
201         return victim_cnt;
202 }
203
204 static int dedup_prepare(void)
205 {
206         if (dedup_get_state() != DEDUP_ONE_SHOT)
207                 return RESOURCED_ERROR_FAIL;
208
209         if (proc_get_freezer_status() == CGROUP_FREEZER_PAUSED)
210                 resourced_notify(RESOURCED_NOTIFIER_FREEZER_CGROUP_STATE,
211                                 (void *)CGROUP_FREEZER_ENABLED);
212
213         return RESOURCED_ERROR_NONE;
214 }
215
216 static void swap_activate_act(void)
217 {
218         int ret, status;
219
220         ret = vconf_get_int(VCONFKEY_SYSMAN_LOW_MEMORY, &status);
221         if (ret)
222                 _E("vconf get failed %s", VCONFKEY_SYSMAN_LOW_MEMORY);
223
224         if (status != VCONFKEY_SYSMAN_LOW_MEMORY_NORMAL) {
225                 vconf_set_int(VCONFKEY_SYSMAN_LOW_MEMORY,
226                                 VCONFKEY_SYSMAN_LOW_MEMORY_NORMAL);
227                 lowmem_memory_level_send_system_event(MEM_LEVEL_LOW);
228         }
229         lowmem_change_lowmem_state(MEM_LEVEL_LOW);
230         if (proc_get_freezer_status() == CGROUP_FREEZER_PAUSED)
231                 resourced_notify(RESOURCED_NOTIFIER_FREEZER_CGROUP_STATE,
232                         (void *)CGROUP_FREEZER_ENABLED);
233
234         if (swap_get_state() != SWAP_ON)
235                 resourced_notify(RESOURCED_NOTIFIER_SWAP_ACTIVATE, NULL);
236 }
237
238 static void lowmem_swap_memory(char *path)
239 {
240         /* The services cannot call this function but the apps can. */
241         unsigned int available_mb;
242         unsigned int cur_mem_state;
243
244         cur_mem_state = lowmem_get_lowmem_state();
245         if (cur_mem_state == MEM_LEVEL_HIGH)
246                 return;
247
248         if (swap_get_state() != SWAP_ON)
249                 return;
250
251         available_mb = proc_get_mem_available();
252         if (cur_mem_state != MEM_LEVEL_LOW &&
253             available_mb <= get_root_memcg_info()->threshold_mb[MEM_LEVEL_LOW])
254                 swap_activate_act();
255
256         resourced_notify(RESOURCED_NOTIFIER_SWAP_START, path);
257         lowmem_set_memcg_swap_status(true);
258 }
259
260 static void lowmem_move_memcgroup(int pid, int next_oom_score_adj, struct proc_app_info *pai)
261 {
262         int cur_oom_score_adj;
263         int cur_memcg_idx;
264         struct memcg_info *mi;
265         int next_memcg_idx = cgroup_get_type(next_oom_score_adj);
266
267         mi = get_memcg_info(next_memcg_idx);
268
269         if (!mi)
270                 return;
271
272         if (!pai) {
273                 cgroup_write_pid_fullpath(mi->name, pid);
274                 return;
275         }
276
277         if (pai->main_pid == pid) {
278                 /* parent pid */
279                 cur_oom_score_adj = pai->memory.oom_score_adj;
280                 cur_memcg_idx = cgroup_get_type(cur_oom_score_adj);
281
282                 if (cur_oom_score_adj == next_oom_score_adj) {
283                         _D("next oom_score_adj (%d) is same with current one", next_oom_score_adj);
284                         return;
285                 }
286
287                 proc_set_process_memory_state(pai, next_memcg_idx, mi, next_oom_score_adj);
288
289                 if (!lowmem_limit_move_cgroup(pai))
290                         return;
291
292                 if(cur_memcg_idx == next_memcg_idx)
293                         return;
294
295                 _I("app (%s) memory cgroup move from %s to %s", pai->appid,
296                         lowmem_convert_cgroup_type_to_str(cur_memcg_idx),
297                         lowmem_convert_cgroup_type_to_str(next_memcg_idx));
298
299                 cgroup_write_pid_fullpath(mi->name, pid);
300                 if (next_memcg_idx == MEMCG_THROTTLING)
301                         lowmem_swap_memory(get_memcg_info(MEMCG_THROTTLING)->name);
302         } else {
303                 /* child pid */
304                 if (pai->memory.use_mem_limit)
305                         return;
306
307                 cgroup_write_pid_fullpath(mi->name, pid);
308         }
309 }
310
311 int lowmem_control_handler(void *data)
312 {
313         struct lowmem_control_data *lowmem_data = (struct lowmem_control_data *)data;
314
315         switch (lowmem_data->control_type) {
316         case LOWMEM_MOVE_CGROUP:
317                 lowmem_move_memcgroup((pid_t)lowmem_data->pid,
318                                         lowmem_data->oom_score_adj,
319                                         lowmem_data->pai);
320                 break;
321         default:
322                 break;
323         }
324
325         return RESOURCED_ERROR_NONE;
326 }
327
328 /* lowmem actions */
329 static int high_mem_act(void *data)
330 {
331         int ret, status;
332
333         ret = vconf_get_int(VCONFKEY_SYSMAN_LOW_MEMORY, &status);
334         if (ret)
335                 _D("vconf_get_int fail %s", VCONFKEY_SYSMAN_LOW_MEMORY);
336         if (status != VCONFKEY_SYSMAN_LOW_MEMORY_NORMAL) {
337                 vconf_set_int(VCONFKEY_SYSMAN_LOW_MEMORY,
338                                 VCONFKEY_SYSMAN_LOW_MEMORY_NORMAL);
339                 lowmem_memory_level_send_system_event(MEM_LEVEL_HIGH);
340         }
341
342         lowmem_change_lowmem_state(MEM_LEVEL_HIGH);
343
344         if (swap_get_state() == SWAP_ON && lowmem_get_memcg_swap_status()) {
345                 resourced_notify(RESOURCED_NOTIFIER_SWAP_UNSET_LIMIT, get_memcg_info(MEMCG_THROTTLING));
346                 lowmem_set_memcg_swap_status(false);
347         }
348         if (proc_get_freezer_status() == CGROUP_FREEZER_PAUSED)
349                 resourced_notify(RESOURCED_NOTIFIER_FREEZER_CGROUP_STATE,
350                         (void *)CGROUP_FREEZER_ENABLED);
351
352         return RESOURCED_ERROR_NONE;
353 }
354
355 static int medium_mem_act(void *data)
356 {
357         int status = 0;
358         int scan_mode = KSM_SCAN_PARTIAL;
359
360         if (dedup_prepare() < 0)
361                 return RESOURCED_ERROR_FAIL;
362
363         if (vconf_get_int(VCONFKEY_SYSMAN_LOW_MEMORY, &status) != 0)
364                 _E("vconf get failed %s", VCONFKEY_SYSMAN_LOW_MEMORY);
365
366         if (status != VCONFKEY_SYSMAN_LOW_MEMORY_NORMAL) {
367                 vconf_set_int(VCONFKEY_SYSMAN_LOW_MEMORY,
368                                 VCONFKEY_SYSMAN_LOW_MEMORY_NORMAL);
369                 lowmem_memory_level_send_system_event(MEM_LEVEL_MEDIUM);
370         }
371         lowmem_change_lowmem_state(MEM_LEVEL_MEDIUM);
372
373         resourced_notify(RESOURCED_NOTIFIER_DEDUP_SCAN, &scan_mode);
374
375         return RESOURCED_ERROR_NONE;
376 }
377 static int low_mem_act(void *data)
378 {
379         swap_activate_act();
380
381         return RESOURCED_ERROR_NONE;
382 }
383 static int critical_mem_act(void *data)
384 {
385         int scan_mode = KSM_SCAN_FULL;
386
387         if (dedup_prepare() < 0)
388                 return RESOURCED_ERROR_FAIL;
389
390         resourced_notify(RESOURCED_NOTIFIER_DEDUP_SCAN, &scan_mode);
391
392         return RESOURCED_ERROR_NONE;
393 }
394
395
396 static int lowmem_controller_initialize(void *data)
397 {
398         /**
399          * FIXME: Consider to use one function to initialize the actions
400          * (killer for the lowmem killer and actions for the memory levels)
401          */
402
403         /* Initialize the action(killer) for the lowmem_worker(lowmem killer) */
404         lowmem_initialize_kill_candidates(lowmem_controller_kill_candidates);
405
406         /* Initialize actions for the memory levels */
407         lowmem_initialize_controller_ops_action(MEM_LEVEL_HIGH, high_mem_act);
408         lowmem_initialize_controller_ops_action(MEM_LEVEL_MEDIUM, medium_mem_act);
409         lowmem_initialize_controller_ops_action(MEM_LEVEL_LOW, low_mem_act);
410         lowmem_initialize_controller_ops_action(MEM_LEVEL_CRITICAL, critical_mem_act);
411
412         register_notifier(RESOURCED_NOTIFIER_MEM_CONTROL, lowmem_control_handler);
413
414         return RESOURCED_ERROR_NONE;
415 }
416
417 static int lowmem_controller_finalize(void *data)
418 {
419         unregister_notifier(RESOURCED_NOTIFIER_MEM_CONTROL, lowmem_control_handler);
420
421         return RESOURCED_ERROR_NONE;
422 }
423
424 static struct module_ops lowmem_controller_module_ops = {
425         .priority       = MODULE_PRIORITY_INITIAL,
426         .name           = "lowmem-controller",
427         .init           = lowmem_controller_initialize,
428         .exit           = lowmem_controller_finalize,
429 };
430
431 MODULE_REGISTER(&lowmem_controller_module_ops)