1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
|
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/process/process.h"
#include <mach/mach.h>
#include <stddef.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <unistd.h>
#include <iterator>
#include <memory>
#include <optional>
#include <utility>
#include "base/apple/mach_logging.h"
#include "base/feature_list.h"
#include "base/memory/free_deleter.h"
namespace base {
namespace {
// Returns the `task_role_t` of the process whose task port is `task_port`.
std::optional<task_role_t> GetTaskCategoryPolicyRole(mach_port_t task_port) {
task_category_policy_data_t category_policy;
mach_msg_type_number_t task_info_count = TASK_CATEGORY_POLICY_COUNT;
boolean_t get_default = FALSE;
kern_return_t result =
task_policy_get(task_port, TASK_CATEGORY_POLICY,
reinterpret_cast<task_policy_t>(&category_policy),
&task_info_count, &get_default);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_get TASK_CATEGORY_POLICY";
return std::nullopt;
}
CHECK(!get_default);
return category_policy.role;
}
// Sets the task role for `task_port`.
bool SetTaskCategoryPolicy(mach_port_t task_port, task_role_t task_role) {
task_category_policy task_category_policy{.role = task_role};
kern_return_t result =
task_policy_set(task_port, TASK_CATEGORY_POLICY,
reinterpret_cast<task_policy_t>(&task_category_policy),
TASK_CATEGORY_POLICY_COUNT);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_set TASK_CATEGORY_POLICY";
return false;
}
return true;
}
// Taken from task_policy_private.h.
struct task_suppression_policy {
integer_t active;
integer_t lowpri_cpu;
integer_t timer_throttle;
integer_t disk_throttle;
integer_t cpu_limit;
integer_t suspend;
integer_t throughput_qos;
integer_t suppressed_cpu;
integer_t background_sockets;
integer_t reserved[7];
};
// Taken from task_policy_private.h.
#define TASK_SUPPRESSION_POLICY_COUNT \
((mach_msg_type_number_t)(sizeof(struct task_suppression_policy) / \
sizeof(integer_t)))
// Activates or deactivates the suppression policy to match the effect of App
// Nap.
bool SetTaskSuppressionPolicy(mach_port_t task_port, bool activate) {
task_suppression_policy suppression_policy = {
.active = activate,
.lowpri_cpu = activate,
.timer_throttle =
activate ? LATENCY_QOS_TIER_5 : LATENCY_QOS_TIER_UNSPECIFIED,
.disk_throttle = activate,
.cpu_limit = 0, /* unused */
.suspend = false, /* unused */
.throughput_qos = THROUGHPUT_QOS_TIER_UNSPECIFIED, /* unused */
.suppressed_cpu = activate,
.background_sockets = activate,
};
kern_return_t result =
task_policy_set(task_port, TASK_SUPPRESSION_POLICY,
reinterpret_cast<task_policy_t>(&suppression_policy),
TASK_SUPPRESSION_POLICY_COUNT);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_set TASK_SUPPRESSION_POLICY";
return false;
}
return true;
}
// Returns true if the task suppression policy is active for `task_port`.
bool IsTaskSuppressionPolicyActive(mach_port_t task_port) {
task_suppression_policy suppression_policy = {
.active = false,
};
mach_msg_type_number_t task_info_count = TASK_SUPPRESSION_POLICY_COUNT;
boolean_t get_default = FALSE;
kern_return_t result =
task_policy_get(task_port, TASK_SUPPRESSION_POLICY,
reinterpret_cast<task_policy_t>(&suppression_policy),
&task_info_count, &get_default);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_get TASK_SUPPRESSION_POLICY";
return false;
}
CHECK(!get_default);
// Only check the `active` property as it is sufficient to discern the state,
// even though other properties could be used.
return suppression_policy.active;
}
// Sets the task role and the suppression policy for `task_port`.
bool SetPriorityImpl(mach_port_t task_port,
task_role_t task_role,
bool activate_suppression_policy) {
// Do both operations, even if the first one fails.
bool succeeded = SetTaskCategoryPolicy(task_port, task_role);
succeeded &= SetTaskSuppressionPolicy(task_port, activate_suppression_policy);
return succeeded;
}
} // namespace
Time Process::CreationTime() const {
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, Pid()};
size_t len = 0;
if (sysctl(mib, std::size(mib), NULL, &len, NULL, 0) < 0) {
return Time();
}
std::unique_ptr<struct kinfo_proc, base::FreeDeleter> proc(
static_cast<struct kinfo_proc*>(malloc(len)));
if (sysctl(mib, std::size(mib), proc.get(), &len, NULL, 0) < 0) {
return Time();
}
return Time::FromTimeVal(proc->kp_proc.p_un.__p_starttime);
}
bool Process::CanSetPriority() {
return true;
}
Process::Priority Process::GetPriority(PortProvider* port_provider) const {
CHECK(IsValid());
CHECK(port_provider);
mach_port_t task_port = port_provider->TaskForHandle(Handle());
if (task_port == TASK_NULL) {
// Upon failure, return the default value.
return Priority::kUserBlocking;
}
std::optional<task_role_t> task_role = GetTaskCategoryPolicyRole(task_port);
if (!task_role) {
// Upon failure, return the default value.
return Priority::kUserBlocking;
}
bool is_suppression_policy_active = IsTaskSuppressionPolicyActive(task_port);
if (*task_role == TASK_BACKGROUND_APPLICATION &&
is_suppression_policy_active) {
return Priority::kBestEffort;
} else if (*task_role == TASK_BACKGROUND_APPLICATION &&
!is_suppression_policy_active) {
return Priority::kUserVisible;
} else if (*task_role == TASK_FOREGROUND_APPLICATION &&
!is_suppression_policy_active) {
return Priority::kUserBlocking;
}
// It is possible to get a different state very early in the process lifetime,
// before SetCurrentTaskDefaultRole() has been invoked. Assume highest
// priority then.
return Priority::kUserBlocking;
}
bool Process::SetPriority(PortProvider* port_provider, Priority priority) {
CHECK(IsValid());
CHECK(port_provider);
if (!CanSetPriority()) {
return false;
}
mach_port_t task_port = port_provider->TaskForHandle(Handle());
if (task_port == TASK_NULL) {
return false;
}
switch (priority) {
case Priority::kBestEffort:
// Activate the suppression policy.
// Note:
// App Nap keeps the task role to TASK_FOREGROUND_APPLICATION when it
// activates the suppression policy. Here TASK_BACKGROUND_APPLICATION is
// used instead to keep the kBestEffort role consistent with the value for
// kUserVisible (so that its is not greater than kUserVisible). This
// difference is unlikely to matter.
return SetPriorityImpl(task_port, TASK_BACKGROUND_APPLICATION, true);
case Priority::kUserVisible:
// Set a task role with a lower priority than kUserBlocking, but do not
// activate the suppression policy.
return SetPriorityImpl(task_port, TASK_BACKGROUND_APPLICATION, false);
case Priority::kUserBlocking:
default:
// Set the highest priority with the suppression policy inactive.
return SetPriorityImpl(task_port, TASK_FOREGROUND_APPLICATION, false);
}
}
// static
void Process::SetCurrentTaskDefaultRole() {
SetTaskCategoryPolicy(mach_task_self(), TASK_FOREGROUND_APPLICATION);
// Set the QoS settings to tier 0, to match the default value given to App Nap
// enabled applications.
task_qos_policy task_qos_policy = {
.task_latency_qos_tier = LATENCY_QOS_TIER_0,
.task_throughput_qos_tier = THROUGHPUT_QOS_TIER_0,
};
task_policy_set(mach_task_self(), TASK_BASE_QOS_POLICY,
reinterpret_cast<task_policy_t>(&task_qos_policy),
TASK_QOS_POLICY_COUNT);
}
} // namespace base
|