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(HAVE_ANDROID_OS)
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 // File descriptors open to /dev/cpuset/../tasks, setup by initialize, or -1 on error
64 static int bg_cpuset_fd = -1;
65 static int fg_cpuset_fd = -1;
66 
67 /* Add tid to the scheduling group defined by the policy */
add_tid_to_cgroup(int tid,int fd)68 static int add_tid_to_cgroup(int tid, int fd)
69 {
70     if (fd < 0) {
71         SLOGE("add_tid_to_cgroup failed; fd=%d\n", fd);
72         errno = EINVAL;
73         return -1;
74     }
75 
76     // specialized itoa -- works for tid > 0
77     char text[22];
78     char *end = text + sizeof(text) - 1;
79     char *ptr = end;
80     *ptr = '\0';
81     while (tid > 0) {
82         *--ptr = '0' + (tid % 10);
83         tid = tid / 10;
84     }
85 
86     if (write(fd, ptr, end - ptr) < 0) {
87         /*
88          * If the thread is in the process of exiting,
89          * don't flag an error
90          */
91         if (errno == ESRCH)
92                 return 0;
93         SLOGW("add_tid_to_cgroup failed to write '%s' (%s); fd=%d\n",
94               ptr, strerror(errno), fd);
95         errno = EINVAL;
96         return -1;
97     }
98 
99     return 0;
100 }
101 
__initialize(void)102 static void __initialize(void) {
103     char* filename;
104     if (!access("/dev/cpuctl/tasks", F_OK)) {
105         __sys_supports_schedgroups = 1;
106 
107         filename = "/dev/cpuctl/tasks";
108         fg_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
109         if (fg_cgroup_fd < 0) {
110             SLOGE("open of %s failed: %s\n", filename, strerror(errno));
111         }
112 
113         filename = "/dev/cpuctl/bg_non_interactive/tasks";
114         bg_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
115         if (bg_cgroup_fd < 0) {
116             SLOGE("open of %s failed: %s\n", filename, strerror(errno));
117         }
118     } else {
119         __sys_supports_schedgroups = 0;
120     }
121 
122 #ifdef USE_CPUSETS
123     if (!access("/dev/cpuset/tasks", F_OK)) {
124 
125         filename = "/dev/cpuset/foreground/tasks";
126         fg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
127         filename = "/dev/cpuset/background/tasks";
128         bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
129     }
130 #endif
131 
132 }
133 
134 /*
135  * Try to get the scheduler group.
136  *
137  * The data from /proc/<pid>/cgroup looks (something) like:
138  *  2:cpu:/bg_non_interactive
139  *  1:cpuacct:/
140  *
141  * We return the part after the "/", which will be an empty string for
142  * the default cgroup.  If the string is longer than "bufLen", the string
143  * will be truncated.
144  */
getSchedulerGroup(int tid,char * buf,size_t bufLen)145 static int getSchedulerGroup(int tid, char* buf, size_t bufLen)
146 {
147 #ifdef HAVE_ANDROID_OS
148     char pathBuf[32];
149     char lineBuf[256];
150     FILE *fp;
151 
152     snprintf(pathBuf, sizeof(pathBuf), "/proc/%d/cgroup", tid);
153     if (!(fp = fopen(pathBuf, "r"))) {
154         return -1;
155     }
156 
157     while(fgets(lineBuf, sizeof(lineBuf) -1, fp)) {
158         char *next = lineBuf;
159         char *subsys;
160         char *grp;
161         size_t len;
162 
163         /* Junk the first field */
164         if (!strsep(&next, ":")) {
165             goto out_bad_data;
166         }
167 
168         if (!(subsys = strsep(&next, ":"))) {
169             goto out_bad_data;
170         }
171 
172         if (strcmp(subsys, "cpu")) {
173             /* Not the subsys we're looking for */
174             continue;
175         }
176 
177         if (!(grp = strsep(&next, ":"))) {
178             goto out_bad_data;
179         }
180         grp++; /* Drop the leading '/' */
181         len = strlen(grp);
182         grp[len-1] = '\0'; /* Drop the trailing '\n' */
183 
184         if (bufLen <= len) {
185             len = bufLen - 1;
186         }
187         strncpy(buf, grp, len);
188         buf[len] = '\0';
189         fclose(fp);
190         return 0;
191     }
192 
193     SLOGE("Failed to find cpu subsys");
194     fclose(fp);
195     return -1;
196  out_bad_data:
197     SLOGE("Bad cgroup data {%s}", lineBuf);
198     fclose(fp);
199     return -1;
200 #else
201     errno = ENOSYS;
202     return -1;
203 #endif
204 }
205 
get_sched_policy(int tid,SchedPolicy * policy)206 int get_sched_policy(int tid, SchedPolicy *policy)
207 {
208     if (tid == 0) {
209         tid = gettid();
210     }
211     pthread_once(&the_once, __initialize);
212 
213     if (__sys_supports_schedgroups) {
214         char grpBuf[32];
215         if (getSchedulerGroup(tid, grpBuf, sizeof(grpBuf)) < 0)
216             return -1;
217         if (grpBuf[0] == '\0') {
218             *policy = SP_FOREGROUND;
219         } else if (!strcmp(grpBuf, "bg_non_interactive")) {
220             *policy = SP_BACKGROUND;
221         } else {
222             errno = ERANGE;
223             return -1;
224         }
225     } else {
226         int rc = sched_getscheduler(tid);
227         if (rc < 0)
228             return -1;
229         else if (rc == SCHED_NORMAL)
230             *policy = SP_FOREGROUND;
231         else if (rc == SCHED_BATCH)
232             *policy = SP_BACKGROUND;
233         else {
234             errno = ERANGE;
235             return -1;
236         }
237     }
238     return 0;
239 }
240 
set_cpuset_policy(int tid,SchedPolicy policy)241 int set_cpuset_policy(int tid, SchedPolicy policy)
242 {
243     // in the absence of cpusets, use the old sched policy
244 #ifndef USE_CPUSETS
245     return set_sched_policy(tid, policy);
246 #else
247     if (tid == 0) {
248         tid = gettid();
249     }
250     policy = _policy(policy);
251     pthread_once(&the_once, __initialize);
252 
253     int fd;
254     switch (policy) {
255     case SP_BACKGROUND:
256         fd = bg_cpuset_fd;
257         break;
258     case SP_FOREGROUND:
259     case SP_AUDIO_APP:
260     case SP_AUDIO_SYS:
261         fd = fg_cpuset_fd;
262         break;
263     default:
264         fd = -1;
265         break;
266     }
267 
268     if (add_tid_to_cgroup(tid, fd) != 0) {
269         if (errno != ESRCH && errno != ENOENT)
270             return -errno;
271     }
272 
273     return 0;
274 #endif
275 }
276 
set_sched_policy(int tid,SchedPolicy policy)277 int set_sched_policy(int tid, SchedPolicy policy)
278 {
279     if (tid == 0) {
280         tid = gettid();
281     }
282     policy = _policy(policy);
283     pthread_once(&the_once, __initialize);
284 
285 #if POLICY_DEBUG
286     char statfile[64];
287     char statline[1024];
288     char thread_name[255];
289     int fd;
290 
291     sprintf(statfile, "/proc/%d/stat", tid);
292     memset(thread_name, 0, sizeof(thread_name));
293 
294     fd = open(statfile, O_RDONLY);
295     if (fd >= 0) {
296         int rc = read(fd, statline, 1023);
297         close(fd);
298         statline[rc] = 0;
299         char *p = statline;
300         char *q;
301 
302         for (p = statline; *p != '('; p++);
303         p++;
304         for (q = p; *q != ')'; q++);
305 
306         strncpy(thread_name, p, (q-p));
307     }
308     switch (policy) {
309     case SP_BACKGROUND:
310         SLOGD("vvv tid %d (%s)", tid, thread_name);
311         break;
312     case SP_FOREGROUND:
313     case SP_AUDIO_APP:
314     case SP_AUDIO_SYS:
315         SLOGD("^^^ tid %d (%s)", tid, thread_name);
316         break;
317     case SP_SYSTEM:
318         SLOGD("/// tid %d (%s)", tid, thread_name);
319         break;
320     default:
321         SLOGD("??? tid %d (%s)", tid, thread_name);
322         break;
323     }
324 #endif
325 
326     if (__sys_supports_schedgroups) {
327         int fd;
328         switch (policy) {
329         case SP_BACKGROUND:
330             fd = bg_cgroup_fd;
331             break;
332         case SP_FOREGROUND:
333         case SP_AUDIO_APP:
334         case SP_AUDIO_SYS:
335             fd = fg_cgroup_fd;
336             break;
337         default:
338             fd = -1;
339             break;
340         }
341 
342 
343         if (add_tid_to_cgroup(tid, fd) != 0) {
344             if (errno != ESRCH && errno != ENOENT)
345                 return -errno;
346         }
347     } else {
348         struct sched_param param;
349 
350         param.sched_priority = 0;
351         sched_setscheduler(tid,
352                            (policy == SP_BACKGROUND) ?
353                            SCHED_BATCH : SCHED_NORMAL,
354                            &param);
355     }
356 
357     prctl(PR_SET_TIMERSLACK_PID,
358           policy == SP_BACKGROUND ? TIMER_SLACK_BG : TIMER_SLACK_FG, tid);
359 
360     return 0;
361 }
362 
363 #else
364 
365 /* Stubs for non-Android targets. */
366 
set_sched_policy(int tid UNUSED,SchedPolicy policy UNUSED)367 int set_sched_policy(int tid UNUSED, SchedPolicy policy UNUSED)
368 {
369     return 0;
370 }
371 
get_sched_policy(int tid UNUSED,SchedPolicy * policy)372 int get_sched_policy(int tid UNUSED, SchedPolicy *policy)
373 {
374     *policy = SP_SYSTEM_DEFAULT;
375     return 0;
376 }
377 
378 #endif
379 
get_sched_policy_name(SchedPolicy policy)380 const char *get_sched_policy_name(SchedPolicy policy)
381 {
382     policy = _policy(policy);
383     static const char * const strings[SP_CNT] = {
384        [SP_BACKGROUND] = "bg",
385        [SP_FOREGROUND] = "fg",
386        [SP_SYSTEM]     = "  ",
387        [SP_AUDIO_APP]  = "aa",
388        [SP_AUDIO_SYS]  = "as",
389     };
390     if ((policy < SP_CNT) && (strings[policy] != NULL))
391         return strings[policy];
392     else
393         return "error";
394 }
395