1
2/* libs/cutils/sched_policy.c
3**
4** Copyright 2007, The Android Open Source Project
5**
6** Licensed under the Apache License, Version 2.0 (the "License");
7** you may not use this file except in compliance with the License.
8** You may obtain a copy of the License at
9**
10**     http://www.apache.org/licenses/LICENSE-2.0
11**
12** Unless required by applicable law or agreed to in writing, software
13** distributed under the License is distributed on an "AS IS" BASIS,
14** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15** See the License for the specific language governing permissions and
16** limitations under the License.
17*/
18
19#define LOG_TAG "SchedPolicy"
20
21#include <stdio.h>
22#include <stdlib.h>
23#include <unistd.h>
24#include <string.h>
25#include <errno.h>
26#include <fcntl.h>
27#include <cutils/sched_policy.h>
28#include <cutils/log.h>
29
30/* Re-map SP_DEFAULT to the system default policy, and leave other values unchanged.
31 * Call this any place a SchedPolicy is used as an input parameter.
32 * Returns the possibly re-mapped policy.
33 */
34static inline SchedPolicy _policy(SchedPolicy p)
35{
36   return p == SP_DEFAULT ? SP_SYSTEM_DEFAULT : p;
37}
38
39#if defined(HAVE_ANDROID_OS) && defined(HAVE_SCHED_H) && defined(HAVE_PTHREADS)
40
41#include <sched.h>
42#include <pthread.h>
43
44#ifndef SCHED_NORMAL
45  #define SCHED_NORMAL 0
46#endif
47
48#ifndef SCHED_BATCH
49  #define SCHED_BATCH 3
50#endif
51
52#define POLICY_DEBUG 0
53
54#define CAN_SET_SP_SYSTEM 0 // non-zero means to implement set_sched_policy(tid, SP_SYSTEM)
55
56static pthread_once_t the_once = PTHREAD_ONCE_INIT;
57
58static int __sys_supports_schedgroups = -1;
59
60// File descriptors open to /dev/cpuctl/../tasks, setup by initialize, or -1 on error.
61static int bg_cgroup_fd = -1;
62static int fg_cgroup_fd = -1;
63#if CAN_SET_SP_SYSTEM
64static int system_cgroup_fd = -1;
65#endif
66
67/* Add tid to the scheduling group defined by the policy */
68static int add_tid_to_cgroup(int tid, SchedPolicy policy)
69{
70    int fd;
71
72    switch (policy) {
73    case SP_BACKGROUND:
74        fd = bg_cgroup_fd;
75        break;
76    case SP_FOREGROUND:
77    case SP_AUDIO_APP:
78    case SP_AUDIO_SYS:
79        fd = fg_cgroup_fd;
80        break;
81#if CAN_SET_SP_SYSTEM
82    case SP_SYSTEM:
83        fd = system_cgroup_fd;
84        break;
85#endif
86    default:
87        fd = -1;
88        break;
89    }
90
91    if (fd < 0) {
92        SLOGE("add_tid_to_cgroup failed; policy=%d\n", policy);
93        return -1;
94    }
95
96    // specialized itoa -- works for tid > 0
97    char text[22];
98    char *end = text + sizeof(text) - 1;
99    char *ptr = end;
100    *ptr = '\0';
101    while (tid > 0) {
102        *--ptr = '0' + (tid % 10);
103        tid = tid / 10;
104    }
105
106    if (write(fd, ptr, end - ptr) < 0) {
107        /*
108         * If the thread is in the process of exiting,
109         * don't flag an error
110         */
111        if (errno == ESRCH)
112                return 0;
113        SLOGW("add_tid_to_cgroup failed to write '%s' (%s); policy=%d\n",
114              ptr, strerror(errno), policy);
115        return -1;
116    }
117
118    return 0;
119}
120
121static void __initialize(void) {
122    char* filename;
123    if (!access("/dev/cpuctl/tasks", F_OK)) {
124        __sys_supports_schedgroups = 1;
125
126#if CAN_SET_SP_SYSTEM
127        filename = "/dev/cpuctl/tasks";
128        system_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
129        if (system_cgroup_fd < 0) {
130            SLOGV("open of %s failed: %s\n", filename, strerror(errno));
131        }
132#endif
133
134        filename = "/dev/cpuctl/apps/tasks";
135        fg_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
136        if (fg_cgroup_fd < 0) {
137            SLOGE("open of %s failed: %s\n", filename, strerror(errno));
138        }
139
140        filename = "/dev/cpuctl/apps/bg_non_interactive/tasks";
141        bg_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
142        if (bg_cgroup_fd < 0) {
143            SLOGE("open of %s failed: %s\n", filename, strerror(errno));
144        }
145    } else {
146        __sys_supports_schedgroups = 0;
147    }
148}
149
150/*
151 * Try to get the scheduler group.
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 */
161static int getSchedulerGroup(int tid, char* buf, size_t bufLen)
162{
163#ifdef HAVE_ANDROID_OS
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 *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 (!(subsys = strsep(&next, ":"))) {
185            goto out_bad_data;
186        }
187
188        if (strcmp(subsys, "cpu")) {
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 cpu 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
222int get_sched_policy(int tid, SchedPolicy *policy)
223{
224#ifdef HAVE_GETTID
225    if (tid == 0) {
226        tid = gettid();
227    }
228#endif
229    pthread_once(&the_once, __initialize);
230
231    if (__sys_supports_schedgroups) {
232        char grpBuf[32];
233        if (getSchedulerGroup(tid, grpBuf, sizeof(grpBuf)) < 0)
234            return -1;
235        if (grpBuf[0] == '\0') {
236            *policy = SP_SYSTEM;
237        } else if (!strcmp(grpBuf, "apps/bg_non_interactive")) {
238            *policy = SP_BACKGROUND;
239        } else if (!strcmp(grpBuf, "apps")) {
240            *policy = SP_FOREGROUND;
241        } else {
242            errno = ERANGE;
243            return -1;
244        }
245    } else {
246        int rc = sched_getscheduler(tid);
247        if (rc < 0)
248            return -1;
249        else if (rc == SCHED_NORMAL)
250            *policy = SP_FOREGROUND;
251        else if (rc == SCHED_BATCH)
252            *policy = SP_BACKGROUND;
253        else {
254            errno = ERANGE;
255            return -1;
256        }
257    }
258    return 0;
259}
260
261int set_sched_policy(int tid, SchedPolicy policy)
262{
263#ifdef HAVE_GETTID
264    if (tid == 0) {
265        tid = gettid();
266    }
267#endif
268    policy = _policy(policy);
269    pthread_once(&the_once, __initialize);
270
271#if POLICY_DEBUG
272    char statfile[64];
273    char statline[1024];
274    char thread_name[255];
275    int fd;
276
277    sprintf(statfile, "/proc/%d/stat", tid);
278    memset(thread_name, 0, sizeof(thread_name));
279
280    fd = open(statfile, O_RDONLY);
281    if (fd >= 0) {
282        int rc = read(fd, statline, 1023);
283        close(fd);
284        statline[rc] = 0;
285        char *p = statline;
286        char *q;
287
288        for (p = statline; *p != '('; p++);
289        p++;
290        for (q = p; *q != ')'; q++);
291
292        strncpy(thread_name, p, (q-p));
293    }
294    switch (policy) {
295    case SP_BACKGROUND:
296        SLOGD("vvv tid %d (%s)", tid, thread_name);
297        break;
298    case SP_FOREGROUND:
299    case SP_AUDIO_APP:
300    case SP_AUDIO_SYS:
301        SLOGD("^^^ tid %d (%s)", tid, thread_name);
302        break;
303    case SP_SYSTEM:
304        SLOGD("/// tid %d (%s)", tid, thread_name);
305        break;
306    default:
307        SLOGD("??? tid %d (%s)", tid, thread_name);
308        break;
309    }
310#endif
311
312    if (__sys_supports_schedgroups) {
313        if (add_tid_to_cgroup(tid, policy)) {
314            if (errno != ESRCH && errno != ENOENT)
315                return -errno;
316        }
317    } else {
318        struct sched_param param;
319
320        param.sched_priority = 0;
321        sched_setscheduler(tid,
322                           (policy == SP_BACKGROUND) ?
323                            SCHED_BATCH : SCHED_NORMAL,
324                           &param);
325    }
326
327    return 0;
328}
329
330#else
331
332/* Stubs for non-Android targets. */
333
334int set_sched_policy(int tid, SchedPolicy policy)
335{
336    return 0;
337}
338
339int get_sched_policy(int tid, SchedPolicy *policy)
340{
341    *policy = SP_SYSTEM_DEFAULT;
342    return 0;
343}
344
345#endif
346
347const char *get_sched_policy_name(SchedPolicy policy)
348{
349    policy = _policy(policy);
350    static const char * const strings[SP_CNT] = {
351       [SP_BACKGROUND] = "bg",
352       [SP_FOREGROUND] = "fg",
353       [SP_SYSTEM]     = "  ",
354       [SP_AUDIO_APP]  = "aa",
355       [SP_AUDIO_SYS]  = "as",
356    };
357    if ((policy < SP_CNT) && (strings[policy] != NULL))
358        return strings[policy];
359    else
360        return "error";
361}
362
363