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