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)
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
55static pthread_once_t the_once = PTHREAD_ONCE_INIT;
56
57static int __sys_supports_schedgroups = -1;
58
59// File descriptors open to /dev/cpuctl/../tasks, setup by initialize, or -1 on error.
60static int bg_cgroup_fd = -1;
61static int fg_cgroup_fd = -1;
62
63// File descriptors open to /dev/cpuset/../tasks, setup by initialize, or -1 on error
64static int bg_cpuset_fd = -1;
65static int fg_cpuset_fd = -1;
66
67/* Add tid to the scheduling group defined by the policy */
68static 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
102static 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 */
145static 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
206int 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
241int 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
277int 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
367int set_sched_policy(int tid UNUSED, SchedPolicy policy UNUSED)
368{
369    return 0;
370}
371
372int get_sched_policy(int tid UNUSED, SchedPolicy *policy)
373{
374    *policy = SP_SYSTEM_DEFAULT;
375    return 0;
376}
377
378#endif
379
380const 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