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