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