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 ¶m); 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