1 /*
2 ** Copyright 2007, The Android Open Source Project
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 "SchedPolicy"
18
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include <log/log.h>
27 #include <cutils/sched_policy.h>
28
29 #define UNUSED __attribute__((__unused__))
30
31 #ifndef SLOGE
32 #define SLOGE ALOGE
33 #endif
34 #ifndef SLOGW
35 #define SLOGW ALOGW
36 #endif
37
38 /* Re-map SP_DEFAULT to the system default policy, and leave other values unchanged.
39 * Call this any place a SchedPolicy is used as an input parameter.
40 * Returns the possibly re-mapped policy.
41 */
_policy(SchedPolicy p)42 static inline SchedPolicy _policy(SchedPolicy p)
43 {
44 return p == SP_DEFAULT ? SP_SYSTEM_DEFAULT : p;
45 }
46
47 #if defined(__ANDROID__)
48
49 #include <pthread.h>
50 #include <sched.h>
51 #include <sys/prctl.h>
52
53 #define POLICY_DEBUG 0
54
55 // timer slack value in nS enforced when the thread moves to background
56 #define TIMER_SLACK_BG 40000000
57 #define TIMER_SLACK_FG 50000
58
59 static pthread_once_t the_once = PTHREAD_ONCE_INIT;
60
61 static int __sys_supports_timerslack = -1;
62
63 // File descriptors open to /dev/cpuset/../tasks, setup by initialize, or -1 on error
64 static int system_bg_cpuset_fd = -1;
65 static int bg_cpuset_fd = -1;
66 static int fg_cpuset_fd = -1;
67 static int ta_cpuset_fd = -1; // special cpuset for top app
68
69 // File descriptors open to /dev/stune/../tasks, setup by initialize, or -1 on error
70 static int bg_schedboost_fd = -1;
71 static int fg_schedboost_fd = -1;
72 static int ta_schedboost_fd = -1;
73
74 /* Add tid to the scheduling group defined by the policy */
add_tid_to_cgroup(int tid,int fd)75 static int add_tid_to_cgroup(int tid, int fd)
76 {
77 if (fd < 0) {
78 SLOGE("add_tid_to_cgroup failed; fd=%d\n", fd);
79 errno = EINVAL;
80 return -1;
81 }
82
83 // specialized itoa -- works for tid > 0
84 char text[22];
85 char *end = text + sizeof(text) - 1;
86 char *ptr = end;
87 *ptr = '\0';
88 while (tid > 0) {
89 *--ptr = '0' + (tid % 10);
90 tid = tid / 10;
91 }
92
93 if (write(fd, ptr, end - ptr) < 0) {
94 /*
95 * If the thread is in the process of exiting,
96 * don't flag an error
97 */
98 if (errno == ESRCH)
99 return 0;
100 SLOGW("add_tid_to_cgroup failed to write '%s' (%s); fd=%d\n",
101 ptr, strerror(errno), fd);
102 errno = EINVAL;
103 return -1;
104 }
105
106 return 0;
107 }
108
109 /*
110 If CONFIG_CPUSETS for Linux kernel is set, "tasks" can be found under
111 /dev/cpuset mounted in init.rc; otherwise, that file does not exist
112 even though the directory, /dev/cpuset, is still created (by init.rc).
113
114 A couple of other candidates (under cpuset mount directory):
115 notify_on_release
116 release_agent
117
118 Yet another way to decide if cpuset is enabled is to parse
119 /proc/self/status and search for lines begin with "Mems_allowed".
120
121 If CONFIG_PROC_PID_CPUSET is set, the existence "/proc/self/cpuset" can
122 be used to decide if CONFIG_CPUSETS is set, so we don't have a dependency
123 on where init.rc mounts cpuset. That's why we'd better require this
124 configuration be set if CONFIG_CPUSETS is set.
125
126 With runtime check using the following function, build time
127 variables like ENABLE_CPUSETS (used in Android.mk) or cpusets (used
128 in Android.bp) are not needed.
129 */
130
cpusets_enabled()131 bool cpusets_enabled() {
132 static bool enabled = (access("/dev/cpuset/tasks", F_OK) == 0);
133
134 return enabled;
135 }
136
137 /*
138 Similar to CONFIG_CPUSETS above, but with a different configuration
139 CONFIG_SCHEDTUNE that's in Android common Linux kernel and Linaro
140 Stable Kernel (LSK), but not in mainline Linux as of v4.9.
141
142 With runtime check using the following function, build time
143 variables like ENABLE_SCHEDBOOST (used in Android.mk) or schedboost
144 (used in Android.bp) are not needed.
145
146 */
147
schedboost_enabled()148 bool schedboost_enabled() {
149 static bool enabled = (access("/dev/stune/tasks", F_OK) == 0);
150
151 return enabled;
152 }
153
__initialize()154 static void __initialize() {
155 const char* filename;
156
157 if (cpusets_enabled()) {
158 if (!access("/dev/cpuset/tasks", W_OK)) {
159
160 filename = "/dev/cpuset/foreground/tasks";
161 fg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
162 filename = "/dev/cpuset/background/tasks";
163 bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
164 filename = "/dev/cpuset/system-background/tasks";
165 system_bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
166 filename = "/dev/cpuset/top-app/tasks";
167 ta_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
168
169 if (schedboost_enabled()) {
170 filename = "/dev/stune/top-app/tasks";
171 ta_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
172 filename = "/dev/stune/foreground/tasks";
173 fg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
174 filename = "/dev/stune/background/tasks";
175 bg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
176 }
177 }
178 }
179
180 char buf[64];
181 snprintf(buf, sizeof(buf), "/proc/%d/timerslack_ns", getpid());
182 __sys_supports_timerslack = !access(buf, W_OK);
183 }
184
185 /*
186 * Returns the path under the requested cgroup subsystem (if it exists)
187 *
188 * The data from /proc/<pid>/cgroup looks (something) like:
189 * 2:cpu:/bg_non_interactive
190 * 1:cpuacct:/
191 *
192 * We return the part after the "/", which will be an empty string for
193 * the default cgroup. If the string is longer than "bufLen", the string
194 * will be truncated.
195 */
getCGroupSubsys(int tid,const char * subsys,char * buf,size_t bufLen)196 static int getCGroupSubsys(int tid, const char* subsys, char* buf, size_t bufLen)
197 {
198 #if defined(__ANDROID__)
199 char pathBuf[32];
200 char lineBuf[256];
201 FILE *fp;
202
203 snprintf(pathBuf, sizeof(pathBuf), "/proc/%d/cgroup", tid);
204 if (!(fp = fopen(pathBuf, "re"))) {
205 return -1;
206 }
207
208 while(fgets(lineBuf, sizeof(lineBuf) -1, fp)) {
209 char *next = lineBuf;
210 char *found_subsys;
211 char *grp;
212 size_t len;
213
214 /* Junk the first field */
215 if (!strsep(&next, ":")) {
216 goto out_bad_data;
217 }
218
219 if (!(found_subsys = strsep(&next, ":"))) {
220 goto out_bad_data;
221 }
222
223 if (strcmp(found_subsys, subsys)) {
224 /* Not the subsys we're looking for */
225 continue;
226 }
227
228 if (!(grp = strsep(&next, ":"))) {
229 goto out_bad_data;
230 }
231 grp++; /* Drop the leading '/' */
232 len = strlen(grp);
233 grp[len-1] = '\0'; /* Drop the trailing '\n' */
234
235 if (bufLen <= len) {
236 len = bufLen - 1;
237 }
238 strncpy(buf, grp, len);
239 buf[len] = '\0';
240 fclose(fp);
241 return 0;
242 }
243
244 SLOGE("Failed to find subsys %s", subsys);
245 fclose(fp);
246 return -1;
247 out_bad_data:
248 SLOGE("Bad cgroup data {%s}", lineBuf);
249 fclose(fp);
250 return -1;
251 #else
252 errno = ENOSYS;
253 return -1;
254 #endif
255 }
256
get_sched_policy(int tid,SchedPolicy * policy)257 int get_sched_policy(int tid, SchedPolicy *policy)
258 {
259 if (tid == 0) {
260 tid = gettid();
261 }
262 pthread_once(&the_once, __initialize);
263
264 char grpBuf[32];
265
266 if (cpusets_enabled()) {
267 if (getCGroupSubsys(tid, "cpuset", grpBuf, sizeof(grpBuf)) < 0) return -1;
268 if (grpBuf[0] == '\0') {
269 *policy = SP_FOREGROUND;
270 } else if (!strcmp(grpBuf, "foreground")) {
271 *policy = SP_FOREGROUND;
272 } else if (!strcmp(grpBuf, "background")) {
273 *policy = SP_BACKGROUND;
274 } else if (!strcmp(grpBuf, "top-app")) {
275 *policy = SP_TOP_APP;
276 } else {
277 errno = ERANGE;
278 return -1;
279 }
280 } else {
281 // In b/34193533, we removed bg_non_interactive cgroup, so now
282 // all threads are in FOREGROUND cgroup
283 *policy = SP_FOREGROUND;
284 }
285 return 0;
286 }
287
set_cpuset_policy(int tid,SchedPolicy policy)288 int set_cpuset_policy(int tid, SchedPolicy policy)
289 {
290 // in the absence of cpusets, use the old sched policy
291 if (!cpusets_enabled()) {
292 return set_sched_policy(tid, policy);
293 }
294
295 if (tid == 0) {
296 tid = gettid();
297 }
298 policy = _policy(policy);
299 pthread_once(&the_once, __initialize);
300
301 int fd = -1;
302 int boost_fd = -1;
303 switch (policy) {
304 case SP_BACKGROUND:
305 fd = bg_cpuset_fd;
306 boost_fd = bg_schedboost_fd;
307 break;
308 case SP_FOREGROUND:
309 case SP_AUDIO_APP:
310 case SP_AUDIO_SYS:
311 fd = fg_cpuset_fd;
312 boost_fd = fg_schedboost_fd;
313 break;
314 case SP_TOP_APP :
315 fd = ta_cpuset_fd;
316 boost_fd = ta_schedboost_fd;
317 break;
318 case SP_SYSTEM:
319 fd = system_bg_cpuset_fd;
320 break;
321 default:
322 boost_fd = fd = -1;
323 break;
324 }
325
326 if (add_tid_to_cgroup(tid, fd) != 0) {
327 if (errno != ESRCH && errno != ENOENT)
328 return -errno;
329 }
330
331 if (schedboost_enabled()) {
332 if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
333 if (errno != ESRCH && errno != ENOENT)
334 return -errno;
335 }
336 }
337
338 return 0;
339 }
340
set_timerslack_ns(int tid,unsigned long long slack)341 static void set_timerslack_ns(int tid, unsigned long long slack) {
342 // v4.6+ kernels support the /proc/<tid>/timerslack_ns interface.
343 // TODO: once we've backported this, log if the open(2) fails.
344 char buf[64];
345 snprintf(buf, sizeof(buf), "/proc/%d/timerslack_ns", tid);
346 int fd = open(buf, O_WRONLY | O_CLOEXEC);
347 if (fd != -1) {
348 int len = snprintf(buf, sizeof(buf), "%llu", slack);
349 if (write(fd, buf, len) != len) {
350 SLOGE("set_timerslack_ns write failed: %s\n", strerror(errno));
351 }
352 close(fd);
353 return;
354 }
355 }
356
set_sched_policy(int tid,SchedPolicy policy)357 int set_sched_policy(int tid, SchedPolicy policy)
358 {
359 if (tid == 0) {
360 tid = gettid();
361 }
362 policy = _policy(policy);
363 pthread_once(&the_once, __initialize);
364
365 #if POLICY_DEBUG
366 char statfile[64];
367 char statline[1024];
368 char thread_name[255];
369
370 snprintf(statfile, sizeof(statfile), "/proc/%d/stat", tid);
371 memset(thread_name, 0, sizeof(thread_name));
372
373 int fd = open(statfile, O_RDONLY | O_CLOEXEC);
374 if (fd >= 0) {
375 int rc = read(fd, statline, 1023);
376 close(fd);
377 statline[rc] = 0;
378 char *p = statline;
379 char *q;
380
381 for (p = statline; *p != '('; p++);
382 p++;
383 for (q = p; *q != ')'; q++);
384
385 strncpy(thread_name, p, (q-p));
386 }
387 switch (policy) {
388 case SP_BACKGROUND:
389 SLOGD("vvv tid %d (%s)", tid, thread_name);
390 break;
391 case SP_FOREGROUND:
392 case SP_AUDIO_APP:
393 case SP_AUDIO_SYS:
394 case SP_TOP_APP:
395 SLOGD("^^^ tid %d (%s)", tid, thread_name);
396 break;
397 case SP_SYSTEM:
398 SLOGD("/// tid %d (%s)", tid, thread_name);
399 break;
400 default:
401 SLOGD("??? tid %d (%s)", tid, thread_name);
402 break;
403 }
404 #endif
405
406 if (schedboost_enabled()) {
407 int boost_fd = -1;
408 switch (policy) {
409 case SP_BACKGROUND:
410 boost_fd = bg_schedboost_fd;
411 break;
412 case SP_FOREGROUND:
413 case SP_AUDIO_APP:
414 case SP_AUDIO_SYS:
415 boost_fd = fg_schedboost_fd;
416 break;
417 case SP_TOP_APP:
418 boost_fd = ta_schedboost_fd;
419 break;
420 default:
421 boost_fd = -1;
422 break;
423 }
424
425 if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
426 if (errno != ESRCH && errno != ENOENT)
427 return -errno;
428 }
429
430 }
431
432 if (__sys_supports_timerslack) {
433 set_timerslack_ns(tid, policy == SP_BACKGROUND ?
434 TIMER_SLACK_BG : TIMER_SLACK_FG);
435 }
436
437 return 0;
438 }
439
440 #else
441
442 /* Stubs for non-Android targets. */
443
set_sched_policy(int tid UNUSED,SchedPolicy policy UNUSED)444 int set_sched_policy(int tid UNUSED, SchedPolicy policy UNUSED)
445 {
446 return 0;
447 }
448
get_sched_policy(int tid UNUSED,SchedPolicy * policy)449 int get_sched_policy(int tid UNUSED, SchedPolicy *policy)
450 {
451 *policy = SP_SYSTEM_DEFAULT;
452 return 0;
453 }
454
455 #endif
456
get_sched_policy_name(SchedPolicy policy)457 const char *get_sched_policy_name(SchedPolicy policy)
458 {
459 policy = _policy(policy);
460 static const char * const strings[SP_CNT] = {
461 [SP_BACKGROUND] = "bg",
462 [SP_FOREGROUND] = "fg",
463 [SP_SYSTEM] = " ",
464 [SP_AUDIO_APP] = "aa",
465 [SP_AUDIO_SYS] = "as",
466 [SP_TOP_APP] = "ta",
467 };
468 if ((policy < SP_CNT) && (strings[policy] != NULL))
469 return strings[policy];
470 else
471 return "error";
472 }
473