• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright 2021 Google, Inc
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */
16 
17 #define LOG_TAG "lowmemorykiller"
18 
19 #include <dirent.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <log/log.h>
23 #include <signal.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <sys/epoll.h>
27 #include <sys/pidfd.h>
28 #include <sys/resource.h>
29 #include <sys/sysinfo.h>
30 #include <sys/types.h>
31 #include <time.h>
32 #include <unistd.h>
33 
34 #include <processgroup/processgroup.h>
35 #include <system/thread_defs.h>
36 
37 #include "reaper.h"
38 
39 #define NS_PER_MS (NS_PER_SEC / MS_PER_SEC)
40 #define THREAD_POOL_SIZE 2
41 
42 #ifndef __NR_process_mrelease
43 #define __NR_process_mrelease 448
44 #endif
45 
process_mrelease(int pidfd,unsigned int flags)46 static int process_mrelease(int pidfd, unsigned int flags) {
47     return syscall(__NR_process_mrelease, pidfd, flags);
48 }
49 
get_time_diff_ms(struct timespec * from,struct timespec * to)50 static inline long get_time_diff_ms(struct timespec *from,
51                                     struct timespec *to) {
52     return (to->tv_sec - from->tv_sec) * (long)MS_PER_SEC +
53            (to->tv_nsec - from->tv_nsec) / (long)NS_PER_MS;
54 }
55 
set_process_group_and_prio(uid_t uid,int pid,const std::vector<std::string> & profiles,int prio)56 static void set_process_group_and_prio(uid_t uid, int pid, const std::vector<std::string>& profiles,
57                                        int prio) {
58     DIR* d;
59     char proc_path[PATH_MAX];
60     struct dirent* de;
61 
62     if (!SetProcessProfilesCached(uid, pid, profiles)) {
63         ALOGW("Failed to set task profiles for the process (%d) being killed", pid);
64     }
65 
66     snprintf(proc_path, sizeof(proc_path), "/proc/%d/task", pid);
67     if (!(d = opendir(proc_path))) {
68         ALOGW("Failed to open %s; errno=%d: process pid(%d) might have died", proc_path, errno,
69               pid);
70         return;
71     }
72 
73     while ((de = readdir(d))) {
74         int t_pid;
75 
76         if (de->d_name[0] == '.') continue;
77         t_pid = atoi(de->d_name);
78 
79         if (!t_pid) {
80             ALOGW("Failed to get t_pid for '%s' of pid(%d)", de->d_name, pid);
81             continue;
82         }
83 
84         if (setpriority(PRIO_PROCESS, t_pid, prio) && errno != ESRCH) {
85             ALOGW("Unable to raise priority of killing t_pid (%d): errno=%d", t_pid, errno);
86         }
87     }
88     closedir(d);
89 }
90 
reaper_main(void * param)91 static void* reaper_main(void* param) {
92     Reaper *reaper = static_cast<Reaper*>(param);
93     struct timespec start_tm, end_tm;
94     struct Reaper::target_proc target;
95     pid_t tid = gettid();
96 
97     // Ensure the thread does not use little cores
98     if (!SetTaskProfiles(tid, {"CPUSET_SP_FOREGROUND"}, true)) {
99         ALOGE("Failed to assign cpuset to the reaper thread");
100     }
101 
102     if (setpriority(PRIO_PROCESS, tid, ANDROID_PRIORITY_HIGHEST)) {
103         ALOGW("Unable to raise priority of the reaper thread (%d): errno=%d", tid, errno);
104     }
105 
106     for (;;) {
107         target = reaper->dequeue_request();
108 
109         if (reaper->debug_enabled()) {
110             clock_gettime(CLOCK_MONOTONIC_COARSE, &start_tm);
111         }
112 
113         if (pidfd_send_signal(target.pidfd, SIGKILL, NULL, 0)) {
114             // Inform the main thread about failure to kill
115             reaper->notify_kill_failure(target.pid);
116             goto done;
117         }
118 
119         set_process_group_and_prio(target.uid, target.pid,
120                                    {"CPUSET_SP_FOREGROUND", "SCHED_SP_FOREGROUND"},
121                                    ANDROID_PRIORITY_NORMAL);
122 
123         if (process_mrelease(target.pidfd, 0)) {
124             ALOGE("process_mrelease %d failed: %s", target.pid, strerror(errno));
125             goto done;
126         }
127         if (reaper->debug_enabled()) {
128             clock_gettime(CLOCK_MONOTONIC_COARSE, &end_tm);
129             ALOGI("Process %d was reaped in %ldms", target.pid,
130                   get_time_diff_ms(&start_tm, &end_tm));
131         }
132 
133 done:
134         close(target.pidfd);
135         reaper->request_complete();
136     }
137 
138     return NULL;
139 }
140 
is_reaping_supported()141 bool Reaper::is_reaping_supported() {
142     static enum {
143         UNKNOWN,
144         SUPPORTED,
145         UNSUPPORTED
146     } reap_support = UNKNOWN;
147 
148     if (reap_support == UNKNOWN) {
149         if (process_mrelease(-1, 0) && errno == ENOSYS) {
150             reap_support = UNSUPPORTED;
151         } else {
152             reap_support = SUPPORTED;
153         }
154     }
155     return reap_support == SUPPORTED;
156 }
157 
init(int comm_fd)158 bool Reaper::init(int comm_fd) {
159     char name[16];
160     struct sched_param param = {
161         .sched_priority = 0,
162     };
163 
164     if (thread_cnt_ > 0) {
165         // init should not be called multiple times
166         return false;
167     }
168 
169     thread_pool_ = new pthread_t[THREAD_POOL_SIZE];
170     for (int i = 0; i < THREAD_POOL_SIZE; i++) {
171         if (pthread_create(&thread_pool_[thread_cnt_], NULL, reaper_main, this)) {
172             ALOGE("pthread_create failed: %s", strerror(errno));
173             continue;
174         }
175         // set normal scheduling policy for the reaper thread
176         if (pthread_setschedparam(thread_pool_[thread_cnt_], SCHED_OTHER, &param)) {
177             ALOGW("set SCHED_FIFO failed %s", strerror(errno));
178         }
179         snprintf(name, sizeof(name), "lmkd_reaper%d", thread_cnt_);
180         if (pthread_setname_np(thread_pool_[thread_cnt_], name)) {
181             ALOGW("pthread_setname_np failed: %s", strerror(errno));
182         }
183         thread_cnt_++;
184     }
185 
186     if (!thread_cnt_) {
187         delete[] thread_pool_;
188         return false;
189     }
190 
191     queue_.reserve(thread_cnt_);
192     comm_fd_ = comm_fd;
193     return true;
194 }
195 
async_kill(const struct target_proc & target)196 bool Reaper::async_kill(const struct target_proc& target) {
197     if (target.pidfd == -1) {
198         return false;
199     }
200 
201     if (!thread_cnt_) {
202         return false;
203     }
204 
205     mutex_.lock();
206     if (active_requests_ >= thread_cnt_) {
207         mutex_.unlock();
208         return false;
209     }
210     active_requests_++;
211 
212     // Duplicate pidfd instead of reusing the original one to avoid synchronization and refcounting
213     // when both reaper and main threads are using or closing the pidfd
214     queue_.push_back({ dup(target.pidfd), target.pid, target.uid });
215     // Wake up a reaper thread
216     cond_.notify_one();
217     mutex_.unlock();
218 
219     return true;
220 }
221 
kill(const struct target_proc & target,bool synchronous)222 int Reaper::kill(const struct target_proc& target, bool synchronous) {
223     /* CAP_KILL required */
224     if (target.pidfd < 0) {
225         return ::kill(target.pid, SIGKILL);
226     }
227 
228     if (!synchronous && async_kill(target)) {
229         // we assume the kill will be successful and if it fails we will be notified
230         return 0;
231     }
232 
233     int result = pidfd_send_signal(target.pidfd, SIGKILL, NULL, 0);
234     if (result) {
235         return result;
236     }
237 
238     return 0;
239 }
240 
dequeue_request()241 Reaper::target_proc Reaper::dequeue_request() {
242     struct target_proc target;
243     std::unique_lock<std::mutex> lock(mutex_);
244 
245     while (queue_.empty()) {
246         cond_.wait(lock);
247     }
248     target = queue_.back();
249     queue_.pop_back();
250 
251     return target;
252 }
253 
request_complete()254 void Reaper::request_complete() {
255     std::scoped_lock<std::mutex> lock(mutex_);
256     active_requests_--;
257 }
258 
notify_kill_failure(int pid)259 void Reaper::notify_kill_failure(int pid) {
260     std::scoped_lock<std::mutex> lock(mutex_);
261 
262     ALOGE("Failed to kill process %d", pid);
263     if (TEMP_FAILURE_RETRY(write(comm_fd_, &pid, sizeof(pid))) != sizeof(pid)) {
264         ALOGE("thread communication write failed: %s", strerror(errno));
265     }
266 }
267