• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2011 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 
5 #include "base/process/process.h"
6 
7 #include <errno.h>
8 #include <linux/magic.h>
9 #include <sys/resource.h>
10 #include <sys/vfs.h>
11 
12 #include <cstring>
13 
14 #include "base/check.h"
15 #include "base/files/file_util.h"
16 #include "base/location.h"
17 #include "base/logging.h"
18 #include "base/notreached.h"
19 #include "base/posix/can_lower_nice_to.h"
20 #include "base/process/internal_linux.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/threading/thread_restrictions.h"
25 #include "build/build_config.h"
26 #include "build/chromeos_buildflags.h"
27 
28 #if BUILDFLAG(IS_CHROMEOS)
29 #include "base/feature_list.h"
30 #include "base/files/file_enumerator.h"
31 #include "base/files/file_path.h"
32 #include "base/functional/bind.h"
33 #include "base/process/process_handle.h"
34 #include "base/strings/strcat.h"
35 #include "base/strings/string_util.h"
36 #include "base/task/thread_pool.h"
37 #include "base/unguessable_token.h"
38 #endif  // BUILDFLAG(IS_CHROMEOS)
39 
40 namespace base {
41 
42 #if BUILDFLAG(IS_CHROMEOS)
43 BASE_FEATURE(kOneGroupPerRenderer,
44              "OneGroupPerRenderer",
45 #if BUILDFLAG(IS_CHROMEOS_LACROS)
46              FEATURE_ENABLED_BY_DEFAULT);
47 #else
48              FEATURE_DISABLED_BY_DEFAULT);
49 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
50 #endif  // BUILDFLAG(IS_CHROMEOS)
51 
52 namespace {
53 
54 const int kForegroundPriority = 0;
55 
56 #if BUILDFLAG(IS_CHROMEOS)
57 // We are more aggressive in our lowering of background process priority
58 // for chromeos as we have much more control over other processes running
59 // on the machine.
60 //
61 // TODO(davemoore) Refactor this by adding support for higher levels to set
62 // the foregrounding / backgrounding process so we don't have to keep
63 // chrome / chromeos specific logic here.
64 const int kBackgroundPriority = 19;
65 const char kControlPath[] = "/sys/fs/cgroup/cpu%s/cgroup.procs";
66 const char kFullRendererCgroupRoot[] = "/sys/fs/cgroup/cpu/chrome_renderers";
67 const char kForeground[] = "/chrome_renderers/foreground";
68 const char kBackground[] = "/chrome_renderers/background";
69 const char kProcPath[] = "/proc/%d/cgroup";
70 const char kUclampMinFile[] = "cpu.uclamp.min";
71 const char kUclampMaxFile[] = "cpu.uclamp.max";
72 
73 constexpr int kCgroupDeleteRetries = 3;
74 constexpr TimeDelta kCgroupDeleteRetryTime(Seconds(1));
75 
76 #if BUILDFLAG(IS_CHROMEOS_LACROS)
77 const char kCgroupPrefix[] = "l-";
78 #elif BUILDFLAG(IS_CHROMEOS_ASH)
79 const char kCgroupPrefix[] = "a-";
80 #endif
81 
PathIsCGroupFileSystem(const FilePath & path)82 bool PathIsCGroupFileSystem(const FilePath& path) {
83   struct statfs statfs_buf;
84   if (statfs(path.value().c_str(), &statfs_buf) < 0)
85     return false;
86   return statfs_buf.f_type == CGROUP_SUPER_MAGIC;
87 }
88 
89 struct CGroups {
90   // Check for cgroups files. ChromeOS supports these by default. It creates
91   // a cgroup mount in /sys/fs/cgroup and then configures two cpu task groups,
92   // one contains at most a single foreground renderer and the other contains
93   // all background renderers. This allows us to limit the impact of background
94   // renderers on foreground ones to a greater level than simple renicing.
95   bool enabled;
96   FilePath foreground_file;
97   FilePath background_file;
98 
99   // A unique token for this instance of the browser.
100   std::string group_prefix_token;
101 
102   // UCLAMP settings for the foreground cgroups.
103   std::string uclamp_min;
104   std::string uclamp_max;
105 
CGroupsbase::__anon2a35c14a0111::CGroups106   CGroups() {
107     foreground_file = FilePath(StringPrintf(kControlPath, kForeground));
108     background_file = FilePath(StringPrintf(kControlPath, kBackground));
109     enabled = PathIsCGroupFileSystem(foreground_file) &&
110               PathIsCGroupFileSystem(background_file);
111 
112     if (!enabled || !FeatureList::IsEnabled(kOneGroupPerRenderer)) {
113       return;
114     }
115 
116     // Generate a unique token for the full browser process
117     group_prefix_token =
118         StrCat({kCgroupPrefix, UnguessableToken::Create().ToString(), "-"});
119 
120     // Reads the ULCAMP settings from the foreground cgroup that will be used
121     // for each renderer's cgroup.
122     FilePath foreground_path = foreground_file.DirName();
123     ReadFileToString(foreground_path.Append(kUclampMinFile), &uclamp_min);
124     ReadFileToString(foreground_path.Append(kUclampMaxFile), &uclamp_max);
125   }
126 
127   // Returns the full path to a the cgroup dir of a process using
128   // the supplied token.
GetForegroundCgroupDirbase::__anon2a35c14a0111::CGroups129   static FilePath GetForegroundCgroupDir(const std::string& token) {
130     // Get individualized cgroup if the feature is enabled
131     std::string cgroup_path_str;
132     StrAppend(&cgroup_path_str, {kFullRendererCgroupRoot, "/", token});
133     return FilePath(cgroup_path_str);
134   }
135 
136   // Returns the path to the cgroup.procs file of the foreground cgroup.
GetForegroundCgroupFilebase::__anon2a35c14a0111::CGroups137   static FilePath GetForegroundCgroupFile(const std::string& token) {
138     // Processes with an empty token use the default foreground cgroup.
139     if (token.empty()) {
140       return CGroups::Get().foreground_file;
141     }
142 
143     FilePath cgroup_path = GetForegroundCgroupDir(token);
144     return cgroup_path.Append("cgroup.procs");
145   }
146 
Getbase::__anon2a35c14a0111::CGroups147   static CGroups& Get() {
148     static auto& groups = *new CGroups;
149     return groups;
150   }
151 };
152 
153 // Returns true if the 'OneGroupPerRenderer' feature is enabled. The feature
154 // is enabled if the kOneGroupPerRenderer feature flag is enabled and the
155 // system supports the chrome cgroups. Will block if this is the first call
156 // that will read the cgroup configs.
OneGroupPerRendererEnabled()157 bool OneGroupPerRendererEnabled() {
158   return FeatureList::IsEnabled(kOneGroupPerRenderer) && CGroups::Get().enabled;
159 }
160 #else
161 const int kBackgroundPriority = 5;
162 #endif  // BUILDFLAG(IS_CHROMEOS)
163 
164 }  // namespace
165 
CreationTime() const166 Time Process::CreationTime() const {
167   int64_t start_ticks = is_current()
168                             ? internal::ReadProcSelfStatsAndGetFieldAsInt64(
169                                   internal::VM_STARTTIME)
170                             : internal::ReadProcStatsAndGetFieldAsInt64(
171                                   Pid(), internal::VM_STARTTIME);
172 
173   if (!start_ticks)
174     return Time();
175 
176   TimeDelta start_offset = internal::ClockTicksToTimeDelta(start_ticks);
177   Time boot_time = internal::GetBootTime();
178   if (boot_time.is_null())
179     return Time();
180   return Time(boot_time + start_offset);
181 }
182 
183 // static
CanBackgroundProcesses()184 bool Process::CanBackgroundProcesses() {
185 #if BUILDFLAG(IS_CHROMEOS)
186   if (CGroups::Get().enabled)
187     return true;
188 #endif  // BUILDFLAG(IS_CHROMEOS)
189 
190   static const bool can_reraise_priority =
191       internal::CanLowerNiceTo(kForegroundPriority);
192   return can_reraise_priority;
193 }
194 
IsProcessBackgrounded() const195 bool Process::IsProcessBackgrounded() const {
196   DCHECK(IsValid());
197 
198 #if BUILDFLAG(IS_CHROMEOS)
199   if (CGroups::Get().enabled) {
200     // Used to allow reading the process priority from proc on thread launch.
201     ScopedAllowBlocking scoped_allow_blocking;
202     std::string proc;
203     if (ReadFileToString(FilePath(StringPrintf(kProcPath, process_)), &proc)) {
204       return IsProcessBackgroundedCGroup(proc);
205     }
206     return false;
207   }
208 #endif  // BUILDFLAG(IS_CHROMEOS)
209 
210   return GetPriority() == kBackgroundPriority;
211 }
212 
SetProcessBackgrounded(bool background)213 bool Process::SetProcessBackgrounded(bool background) {
214   DCHECK(IsValid());
215 
216 #if BUILDFLAG(IS_CHROMEOS)
217   if (CGroups::Get().enabled) {
218     std::string pid = NumberToString(process_);
219     const FilePath file =
220         background ? CGroups::Get().background_file
221                    : CGroups::Get().GetForegroundCgroupFile(unique_token_);
222     return WriteFile(file, pid);
223   }
224 #endif  // BUILDFLAG(IS_CHROMEOS)
225 
226   if (!CanBackgroundProcesses())
227     return false;
228 
229   int priority = background ? kBackgroundPriority : kForegroundPriority;
230   int result = setpriority(PRIO_PROCESS, static_cast<id_t>(process_), priority);
231   DPCHECK(result == 0);
232   return result == 0;
233 }
234 
235 #if BUILDFLAG(IS_CHROMEOS)
IsProcessBackgroundedCGroup(const StringPiece & cgroup_contents)236 bool IsProcessBackgroundedCGroup(const StringPiece& cgroup_contents) {
237   // The process can be part of multiple control groups, and for each cgroup
238   // hierarchy there's an entry in the file. We look for a control group
239   // named "/chrome_renderers/background" to determine if the process is
240   // backgrounded. crbug.com/548818.
241   std::vector<StringPiece> lines = SplitStringPiece(
242       cgroup_contents, "\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
243   for (const auto& line : lines) {
244     std::vector<StringPiece> fields =
245         SplitStringPiece(line, ":", TRIM_WHITESPACE, SPLIT_WANT_ALL);
246     if (fields.size() != 3U) {
247       NOTREACHED();
248       continue;
249     }
250     if (fields[2] == kBackground)
251       return true;
252   }
253 
254   return false;
255 }
256 #endif  // BUILDFLAG(IS_CHROMEOS)
257 
258 #if BUILDFLAG(IS_CHROMEOS_ASH)
259 // Reads /proc/<pid>/status and returns the PID in its PID namespace.
260 // If the process is not in a PID namespace or /proc/<pid>/status does not
261 // report NSpid, kNullProcessId is returned.
GetPidInNamespace() const262 ProcessId Process::GetPidInNamespace() const {
263   std::string status;
264   {
265     // Synchronously reading files in /proc does not hit the disk.
266     ScopedAllowBlocking scoped_allow_blocking;
267     FilePath status_file =
268         FilePath("/proc").Append(NumberToString(process_)).Append("status");
269     if (!ReadFileToString(status_file, &status)) {
270       return kNullProcessId;
271     }
272   }
273 
274   StringPairs pairs;
275   SplitStringIntoKeyValuePairs(status, ':', '\n', &pairs);
276   for (const auto& pair : pairs) {
277     const std::string& key = pair.first;
278     const std::string& value_str = pair.second;
279     if (key == "NSpid") {
280       std::vector<StringPiece> split_value_str = SplitStringPiece(
281           value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
282       if (split_value_str.size() <= 1) {
283         return kNullProcessId;
284       }
285       int value;
286       // The last value in the list is the PID in the namespace.
287       if (!StringToInt(split_value_str.back(), &value)) {
288         NOTREACHED();
289         return kNullProcessId;
290       }
291       return value;
292     }
293   }
294   return kNullProcessId;
295 }
296 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
297 
298 #if BUILDFLAG(IS_CHROMEOS)
299 // static
OneGroupPerRendererEnabledForTesting()300 bool Process::OneGroupPerRendererEnabledForTesting() {
301   return OneGroupPerRendererEnabled();
302 }
303 
304 // On Chrome OS, each renderer runs in its own cgroup when running in the
305 // foreground. After process creation the cgroup is created using a
306 // unique token.
InitializePriority()307 void Process::InitializePriority() {
308   if (!OneGroupPerRendererEnabled() || !IsValid() || !unique_token_.empty()) {
309     return;
310   }
311 
312   // The token has the following format:
313   //   {cgroup_prefix}{UnguessableToken}
314   // The cgroup prefix is to distinguish ash from lacros tokens for stale
315   // cgroup cleanup.
316   unique_token_ = StrCat({CGroups::Get().group_prefix_token,
317                           UnguessableToken::Create().ToString()});
318 
319   FilePath cgroup_path = CGroups::Get().GetForegroundCgroupDir(unique_token_);
320   // Note that CreateDirectoryAndGetError() does not fail if the directory
321   // already exits.
322   if (!CreateDirectoryAndGetError(cgroup_path, nullptr)) {
323     // If creating the directory fails, fall back to use the foreground group.
324     int saved_errno = errno;
325     LOG(ERROR) << "Failed to create cgroup, falling back to foreground"
326                << ", cgroup=" << cgroup_path
327                << ", errno=" << strerror(saved_errno);
328 
329     unique_token_.clear();
330     return;
331   }
332 
333   if (!CGroups::Get().uclamp_min.empty() &&
334       !WriteFile(cgroup_path.Append(kUclampMinFile),
335                  CGroups::Get().uclamp_min)) {
336     LOG(ERROR) << "Failed to write uclamp min file, cgroup_path="
337                << cgroup_path;
338   }
339   if (!CGroups::Get().uclamp_min.empty() &&
340       !WriteFile(cgroup_path.Append(kUclampMaxFile),
341                  CGroups::Get().uclamp_max)) {
342     LOG(ERROR) << "Failed to write uclamp max file, cgroup_path="
343                << cgroup_path;
344   }
345 }
346 
347 // static
CleanUpProcessScheduled(Process process,int remaining_retries)348 void Process::CleanUpProcessScheduled(Process process, int remaining_retries) {
349   process.CleanUpProcess(remaining_retries);
350 }
351 
CleanUpProcessAsync() const352 void Process::CleanUpProcessAsync() const {
353   if (!FeatureList::IsEnabled(kOneGroupPerRenderer) || unique_token_.empty()) {
354     return;
355   }
356 
357   ThreadPool::PostTask(FROM_HERE, {MayBlock(), TaskPriority::BEST_EFFORT},
358                        BindOnce(&Process::CleanUpProcessScheduled, Duplicate(),
359                                 kCgroupDeleteRetries));
360 }
361 
CleanUpProcess(int remaining_retries) const362 void Process::CleanUpProcess(int remaining_retries) const {
363   if (!OneGroupPerRendererEnabled() || unique_token_.empty()) {
364     return;
365   }
366 
367   // Try to delete the cgroup
368   // TODO(1322562): We can use notify_on_release to automoatically delete the
369   // cgroup when the process has left the cgroup.
370   FilePath cgroup = CGroups::Get().GetForegroundCgroupDir(unique_token_);
371   if (!DeleteFile(cgroup)) {
372     auto saved_errno = errno;
373     LOG(ERROR) << "Failed to delete cgroup " << cgroup
374                << ", errno=" << strerror(saved_errno);
375     // If the delete failed, then the process is still potentially in the
376     // cgroup. Move the process to background and schedule a callback to try
377     // again.
378     if (remaining_retries > 0) {
379       std::string pidstr = NumberToString(process_);
380       if (!WriteFile(CGroups::Get().background_file, pidstr)) {
381         // Failed to move the process, LOG a warning but try again.
382         saved_errno = errno;
383         LOG(WARNING) << "Failed to move the process to background"
384                      << ", pid=" << pidstr
385                      << ", errno=" << strerror(saved_errno);
386       }
387       ThreadPool::PostDelayedTask(FROM_HERE,
388                                   {MayBlock(), TaskPriority::BEST_EFFORT},
389                                   BindOnce(&Process::CleanUpProcessScheduled,
390                                            Duplicate(), remaining_retries - 1),
391                                   kCgroupDeleteRetryTime);
392     }
393   }
394 }
395 
396 // static
CleanUpStaleProcessStates()397 void Process::CleanUpStaleProcessStates() {
398   if (!OneGroupPerRendererEnabled()) {
399     return;
400   }
401 
402   FileEnumerator traversal(FilePath(kFullRendererCgroupRoot), false,
403                            FileEnumerator::DIRECTORIES);
404   for (FilePath path = traversal.Next(); !path.empty();
405        path = traversal.Next()) {
406     std::string dirname = path.BaseName().value();
407     if (dirname == FilePath(kForeground).BaseName().value() ||
408         dirname == FilePath(kBackground).BaseName().value()) {
409       continue;
410     }
411 
412     if (!StartsWith(dirname, kCgroupPrefix) ||
413         StartsWith(dirname, CGroups::Get().group_prefix_token)) {
414       continue;
415     }
416 
417     if (!DeleteFile(path)) {
418       auto saved_errno = errno;
419       LOG(ERROR) << "Failed to delete " << path
420                  << ", errno=" << strerror(saved_errno);
421     }
422   }
423 }
424 #endif  // BUILDFLAG(IS_CHROMEOS)
425 
426 }  // namespace base
427