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