blob: 334cec3b93c707ea1bb4a27cbb835f6544fef2ff [file] [log] [blame]
// Copyright 2016 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/pthread/thread_create_priority.h"
#include <sched.h>
#include <sys/resource.h>
#include "starboard/common/log.h"
namespace starboard {
namespace shared {
namespace pthread {
// This is the maximum priority that will be passed to SetRoundRobinScheduler().
const int kMaxRoundRobinPriority = 2;
// Permissions are needed to allow usage non-default thread schedulers and
// thread priorities. Specifically, /etc/security/limits.conf should set
// "rtprio" and "nice" limits. "rtprio" will allow use of the real-time
// schedulers, and "nice" will allow use of nice priorities as well as allow
// SCHED_IDLE threads to increase their priority. If the user is 'pi', then
// limits.conf should have the following lines:
// @pi hard rtprio 99
// @pi soft rtprio 99
// @pi hard nice -20
// @pi soft nice -20
const char kSchedulerErrorMessage[] =
"Unable to set scheduler. Please update "
"limits.conf to set 'rtprio' limit to 99 and 'nice' limit to -20.";
// Note that use of sched_setscheduler() has been found to be more reliably
// supported than pthread_setschedparam(), so we are using that.
void SetIdleScheduler() {
struct sched_param thread_sched_param;
thread_sched_param.sched_priority = 0;
int result = sched_setscheduler(0, SCHED_IDLE, &thread_sched_param);
SB_CHECK(result == 0) << kSchedulerErrorMessage;
}
void SetOtherScheduler() {
struct sched_param thread_sched_param;
thread_sched_param.sched_priority = 0;
int result = sched_setscheduler(0, SCHED_OTHER, &thread_sched_param);
SB_CHECK(result == 0) << kSchedulerErrorMessage;
}
// Here |priority| is a number >= 0, where the higher the number, the
// higher the priority.
//
// If real time priorities are not allowed, then fall back to SCHED_OTHER.
// Otherwise, use SCHED_RR with the desired priority (or RLIMIT_RTPRIO --
// whichever is lower).
void SetRoundRobinScheduler(int priority) {
// Determine the minimum and maximum priorities that will be used.
int min_priority = sched_get_priority_min(SCHED_RR);
int max_used_priority = min_priority + kMaxRoundRobinPriority;
struct rlimit rlimit_rtprio;
getrlimit(RLIMIT_RTPRIO, &rlimit_rtprio);
if (rlimit_rtprio.rlim_cur < min_priority) {
// Can't use the real-time scheduler at all. Use SCHED_OTHER instead.
// Performance will be noticeably worse than ideal.
SB_LOG(ERROR) << "Unable to use real-time round-robin scheduler because "
<< "the maximum real-time scheduling priority is too low ("
<< rlimit_rtprio.rlim_cur << " < " << max_used_priority
<< "). Update setting using `ulimit -r` or limits.conf file.";
SetOtherScheduler();
return;
}
if (rlimit_rtprio.rlim_cur < max_used_priority) {
// The maximum desired priority will be clamped.
// Performance will be slightly worse than ideal.
SB_LOG(WARNING) << "The maximum real-time scheduling priority is too low ("
<< rlimit_rtprio.rlim_cur << " < " << max_used_priority
<< "). Update setting using `ulimit -r` or limits.conf "
<< "file.";
}
struct sched_param thread_sched_param;
thread_sched_param.sched_priority = std::min(
min_priority + priority, static_cast<int>(rlimit_rtprio.rlim_cur));
int result = sched_setscheduler(0, SCHED_RR, &thread_sched_param);
SB_CHECK(result == 0) << kSchedulerErrorMessage;
}
void ThreadSetPriority(SbThreadPriority priority) {
if (!kSbHasThreadPrioritySupport)
return;
// Use different schedulers according to priority. This is preferred over
// using SCHED_RR for all threads because the scheduler time slice is too
// high (defaults to 100ms) for the desired threading behavior.
switch (priority) {
case kSbThreadPriorityLowest:
case kSbThreadPriorityLow:
SetIdleScheduler();
break;
case kSbThreadNoPriority:
case kSbThreadPriorityNormal:
SetOtherScheduler();
break;
case kSbThreadPriorityHigh:
SetRoundRobinScheduler(0);
break;
case kSbThreadPriorityHighest:
SetRoundRobinScheduler(1);
break;
case kSbThreadPriorityRealTime:
SetRoundRobinScheduler(kMaxRoundRobinPriority);
break;
default:
SB_NOTREACHED();
break;
}
}
} // namespace pthread
} // namespace shared
} // namespace starboard
bool SbThreadSetPriority(SbThreadPriority priority) {
::starboard::shared::pthread::ThreadSetPriority(priority);
return true;
}
bool SbThreadGetPriority(SbThreadPriority* priority) {
int scheduler = sched_getscheduler(0);
if (scheduler == -1) {
return false;
}
switch (scheduler) {
case SCHED_IDLE:
*priority = kSbThreadPriorityLowest;
return true;
case SCHED_OTHER:
*priority = kSbThreadPriorityNormal;
return true;
}
if (scheduler == SCHED_RR) {
struct sched_param param;
int res = sched_getparam(0, &param);
if (res == -1) {
return false;
}
switch (param.sched_priority) {
case 0:
*priority = kSbThreadPriorityHigh;
return true;
case 1:
*priority = kSbThreadPriorityHighest;
return true;
case 2:
*priority = kSbThreadPriorityRealTime;
return true;
default:
return false;
}
}
return false;
}