1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 // Description: Linux specific functionality. Other Linux-derivatives layer on
5 // top of this translation unit.
6
7 #include "base/no_destructor.h"
8 #include "base/threading/platform_thread.h"
9
10 #include <errno.h>
11 #include <sched.h>
12 #include <stddef.h>
13 #include <cstdint>
14 #include <atomic>
15
16 #include "base/base_switches.h"
17 #include "base/command_line.h"
18 #include "base/compiler_specific.h"
19 #include "base/feature_list.h"
20 #include "base/files/file_util.h"
21 #include "base/lazy_instance.h"
22 #include "base/logging.h"
23 #include "base/metrics/field_trial_params.h"
24 #include "base/notreached.h"
25 #include "base/process/internal_linux.h"
26 #include "base/strings/string_number_conversions.h"
27 #include "base/strings/stringprintf.h"
28 #include "base/threading/platform_thread_internal_posix.h"
29 #include "base/threading/thread_id_name_manager.h"
30 #include "base/threading/thread_type_delegate.h"
31 #include "build/build_config.h"
32 #include "third_party/abseil-cpp/absl/types/optional.h"
33
34 #include <pthread.h>
35 #include <sys/prctl.h>
36 #include <sys/resource.h>
37 #include <sys/time.h>
38 #include <sys/types.h>
39 #include <unistd.h>
40
41 namespace base {
42
43 namespace {
44
45 ThreadTypeDelegate* g_thread_type_delegate = nullptr;
46
47 const FilePath::CharType kCgroupDirectory[] =
48 FILE_PATH_LITERAL("/sys/fs/cgroup");
49
ThreadTypeToCgroupDirectory(const FilePath & cgroup_filepath,ThreadType thread_type)50 FilePath ThreadTypeToCgroupDirectory(const FilePath& cgroup_filepath,
51 ThreadType thread_type) {
52 switch (thread_type) {
53 case ThreadType::kBackground:
54 case ThreadType::kUtility:
55 case ThreadType::kResourceEfficient:
56 return cgroup_filepath.Append(FILE_PATH_LITERAL("non-urgent"));
57 case ThreadType::kDefault:
58 return cgroup_filepath;
59 case ThreadType::kCompositing:
60 #if BUILDFLAG(IS_CHROMEOS)
61 // On ChromeOS, kCompositing is also considered urgent.
62 return cgroup_filepath.Append(FILE_PATH_LITERAL("urgent"));
63 #else
64 // TODO(1329208): Experiment with bringing IS_LINUX inline with
65 // IS_CHROMEOS.
66 return cgroup_filepath;
67 #endif
68 case ThreadType::kDisplayCritical:
69 case ThreadType::kRealtimeAudio:
70 return cgroup_filepath.Append(FILE_PATH_LITERAL("urgent"));
71 }
72 NOTREACHED();
73 return FilePath();
74 }
75
SetThreadCgroup(PlatformThreadId thread_id,const FilePath & cgroup_directory)76 void SetThreadCgroup(PlatformThreadId thread_id,
77 const FilePath& cgroup_directory) {
78 FilePath tasks_filepath = cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
79 std::string tid = NumberToString(thread_id);
80 // TODO(crbug.com/1333521): Remove cast.
81 const int size = static_cast<int>(tid.size());
82 int bytes_written = WriteFile(tasks_filepath, tid.data(), size);
83 if (bytes_written != size) {
84 DVLOG(1) << "Failed to add " << tid << " to " << tasks_filepath.value();
85 }
86 }
87
SetThreadCgroupForThreadType(PlatformThreadId thread_id,const FilePath & cgroup_filepath,ThreadType thread_type)88 void SetThreadCgroupForThreadType(PlatformThreadId thread_id,
89 const FilePath& cgroup_filepath,
90 ThreadType thread_type) {
91 // Append "chrome" suffix.
92 FilePath cgroup_directory = ThreadTypeToCgroupDirectory(
93 cgroup_filepath.Append(FILE_PATH_LITERAL("chrome")), thread_type);
94
95 // Silently ignore request if cgroup directory doesn't exist.
96 if (!DirectoryExists(cgroup_directory))
97 return;
98
99 SetThreadCgroup(thread_id, cgroup_directory);
100 }
101
102 } // namespace
103
104 namespace internal {
105
106 const ThreadPriorityToNiceValuePairForTest
107 kThreadPriorityToNiceValueMapForTest[7] = {
108 {ThreadPriorityForTest::kRealtimeAudio, -10},
109 {ThreadPriorityForTest::kDisplay, -8},
110 #if BUILDFLAG(IS_CHROMEOS)
111 {ThreadPriorityForTest::kCompositing, -8},
112 #else
113 // TODO(1329208): Experiment with bringing IS_LINUX inline with
114 // IS_CHROMEOS.
115 {ThreadPriorityForTest::kCompositing, -1},
116 #endif
117 {ThreadPriorityForTest::kNormal, 0},
118 {ThreadPriorityForTest::kResourceEfficient, 1},
119 {ThreadPriorityForTest::kUtility, 2},
120 {ThreadPriorityForTest::kBackground, 10},
121 };
122
123 // These nice values are shared with ChromeOS platform code
124 // (platform_thread_cros.cc) and have to be unique as ChromeOS has a unique
125 // type -> nice value mapping. An exception is kCompositing and
126 // kDisplayCritical where aliasing is OK as they have the same scheduler
127 // attributes (cpusets, latency_sensitive etc) including nice value.
128 // The uniqueness of the nice value per-type helps to change and restore the
129 // scheduling params of threads when their process toggles between FG and BG.
130 const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[7] = {
131 {ThreadType::kBackground, 10}, {ThreadType::kUtility, 2},
132 {ThreadType::kResourceEfficient, 1}, {ThreadType::kDefault, 0},
133 #if BUILDFLAG(IS_CHROMEOS)
134 {ThreadType::kCompositing, -8},
135 #else
136 // TODO(1329208): Experiment with bringing IS_LINUX inline with IS_CHROMEOS.
137 {ThreadType::kCompositing, -1},
138 #endif
139 {ThreadType::kDisplayCritical, -8}, {ThreadType::kRealtimeAudio, -10},
140 };
141
CanSetThreadTypeToRealtimeAudio()142 bool CanSetThreadTypeToRealtimeAudio() {
143 // Check if root
144 if (geteuid() == 0) {
145 return true;
146 }
147
148 // A non-zero soft-limit on RLIMIT_RTPRIO is required to be allowed to invoke
149 // pthread_setschedparam in SetCurrentThreadTypeForPlatform().
150 struct rlimit rlim;
151 return getrlimit(RLIMIT_RTPRIO, &rlim) != 0 && rlim.rlim_cur != 0;
152 }
153
SetCurrentThreadTypeForPlatform(ThreadType thread_type,MessagePumpType pump_type_hint)154 bool SetCurrentThreadTypeForPlatform(ThreadType thread_type,
155 MessagePumpType pump_type_hint) {
156 const PlatformThreadId tid = PlatformThread::CurrentId();
157
158 if (g_thread_type_delegate &&
159 g_thread_type_delegate->HandleThreadTypeChange(tid, thread_type)) {
160 return true;
161 }
162
163 PlatformThread::SetThreadType(getpid(), tid, thread_type, IsViaIPC(false));
164 return true;
165 }
166
167 absl::optional<ThreadPriorityForTest>
GetCurrentThreadPriorityForPlatformForTest()168 GetCurrentThreadPriorityForPlatformForTest() {
169 int maybe_sched_rr = 0;
170 struct sched_param maybe_realtime_prio = {0};
171 if (pthread_getschedparam(pthread_self(), &maybe_sched_rr,
172 &maybe_realtime_prio) == 0 &&
173 maybe_sched_rr == SCHED_RR &&
174 maybe_realtime_prio.sched_priority ==
175 PlatformThreadLinux::kRealTimeAudioPrio.sched_priority) {
176 return absl::make_optional(ThreadPriorityForTest::kRealtimeAudio);
177 }
178 return absl::nullopt;
179 }
180
181 } // namespace internal
182
183 // Determine if thread_id is a background thread by looking up whether
184 // it is in the urgent or non-urgent cpuset.
IsThreadBackgroundedForTest(PlatformThreadId thread_id)185 bool PlatformThreadLinux::IsThreadBackgroundedForTest(
186 PlatformThreadId thread_id) {
187 FilePath cgroup_filepath(kCgroupDirectory);
188
189 FilePath urgent_cgroup_directory =
190 cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset"))
191 .Append(FILE_PATH_LITERAL("chrome"))
192 .Append(FILE_PATH_LITERAL("urgent"));
193 FilePath non_urgent_cgroup_directory =
194 cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset"))
195 .Append(FILE_PATH_LITERAL("chrome"))
196 .Append(FILE_PATH_LITERAL("non-urgent"));
197
198 // Silently ignore request if cgroup directory doesn't exist.
199 if (!DirectoryExists(urgent_cgroup_directory) ||
200 !DirectoryExists(non_urgent_cgroup_directory)) {
201 return false;
202 }
203
204 FilePath urgent_tasks_filepath =
205 urgent_cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
206 FilePath non_urgent_tasks_filepath =
207 non_urgent_cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
208
209 std::string tid = NumberToString(thread_id);
210 // Check if thread_id is in the urgent cpuset
211 std::string urgent_tasks;
212 if (!ReadFileToString(urgent_tasks_filepath, &urgent_tasks)) {
213 return false;
214 }
215 if (urgent_tasks.find(tid) != std::string::npos) {
216 return false;
217 }
218
219 // Check if thread_id is in the non-urgent cpuset
220 std::string non_urgent_tasks;
221 if (!ReadFileToString(non_urgent_tasks_filepath, &non_urgent_tasks)) {
222 return false;
223 }
224 if (non_urgent_tasks.find(tid) != std::string::npos) {
225 return true;
226 }
227
228 return false;
229 }
230
SetName(const std::string & name)231 void PlatformThreadBase::SetName(const std::string& name) {
232 SetNameCommon(name);
233
234 // On linux we can get the thread names to show up in the debugger by setting
235 // the process name for the LWP. We don't want to do this for the main
236 // thread because that would rename the process, causing tools like killall
237 // to stop working.
238 if (PlatformThread::CurrentId() == getpid())
239 return;
240
241 // http://0pointer.de/blog/projects/name-your-threads.html
242 // Set the name for the LWP (which gets truncated to 15 characters).
243 // Note that glibc also has a 'pthread_setname_np' api, but it may not be
244 // available everywhere and it's only benefit over using prctl directly is
245 // that it can set the name of threads other than the current thread.
246 int err = prctl(PR_SET_NAME, name.c_str());
247 // We expect EPERM failures in sandboxed processes, just ignore those.
248 if (err < 0 && errno != EPERM)
249 DPLOG(ERROR) << "prctl(PR_SET_NAME)";
250 }
251
252 // static
SetThreadTypeDelegate(ThreadTypeDelegate * delegate)253 void PlatformThreadLinux::SetThreadTypeDelegate(ThreadTypeDelegate* delegate) {
254 // A component cannot override a delegate set by another component, thus
255 // disallow setting a delegate when one already exists.
256 DCHECK(!g_thread_type_delegate || !delegate);
257
258 g_thread_type_delegate = delegate;
259 }
260
261 // static
SetThreadCgroupsForThreadType(PlatformThreadId thread_id,ThreadType thread_type)262 void PlatformThreadLinux::SetThreadCgroupsForThreadType(
263 PlatformThreadId thread_id,
264 ThreadType thread_type) {
265 FilePath cgroup_filepath(kCgroupDirectory);
266 SetThreadCgroupForThreadType(
267 thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset")),
268 thread_type);
269 SetThreadCgroupForThreadType(
270 thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("schedtune")),
271 thread_type);
272 }
273
274 // static
SetThreadType(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type,IsViaIPC via_ipc)275 void PlatformThreadLinux::SetThreadType(ProcessId process_id,
276 PlatformThreadId thread_id,
277 ThreadType thread_type,
278 IsViaIPC via_ipc) {
279 SetThreadCgroupsForThreadType(thread_id, thread_type);
280
281 // Some scheduler syscalls require thread ID of 0 for current thread.
282 // This prevents us from requiring to translate the NS TID to
283 // global TID.
284 PlatformThreadId syscall_tid = thread_id;
285 if (thread_id == PlatformThread::CurrentId()) {
286 syscall_tid = 0;
287 }
288
289 if (thread_type == ThreadType::kRealtimeAudio) {
290 if (sched_setscheduler(syscall_tid, SCHED_RR,
291 &PlatformThreadLinux::kRealTimeAudioPrio) == 0) {
292 return;
293 }
294 // If failed to set to RT, fallback to setpriority to set nice value.
295 DPLOG(ERROR) << "Failed to set realtime priority for thread " << thread_id;
296 }
297
298 const int nice_setting = internal::ThreadTypeToNiceValue(thread_type);
299 if (setpriority(PRIO_PROCESS, static_cast<id_t>(syscall_tid), nice_setting)) {
300 DVPLOG(1) << "Failed to set nice value of thread (" << thread_id << ") to "
301 << nice_setting;
302 }
303 }
304
305 } // namespace base
306