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