【Chromium】GPU進程啟動流程


本篇文檔以gpu進程的創建和啟動為例,講述chormium如何啟動一個browser進程的子進程
PS:本文使用的chromium代碼版本為71

前言

GPU進程的啟動時機是由browser進程負責的,browser進程會在進入message loop之前啟動兩個進程,先是啟動zygote進程,然后是gpu進程

GPU進程的創建和命令行參數的准備

下面是在文件browser_main_loop.cc中的函數BrowserThreadsStarted的代碼片段

int BrowserMainLoop::BrowserThreadsStarted() {
...
  if (GpuDataManagerImpl::GetInstance()->GpuProcessStartAllowed() &&
      !established_gpu_channel && always_uses_gpu && browser_is_viz_host) {
    TRACE_EVENT_INSTANT0("gpu", "Post task to launch GPU process",
                         TRACE_EVENT_SCOPE_THREAD);
    base::PostTaskWithTraits(
        FROM_HERE, {BrowserThread::IO},
        base::BindOnce(base::IgnoreResult(&GpuProcessHost::Get),
                       GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
                       true /* force_create */));
  }
...
}

其中GpuProcessHost::Get函數就是一切的開始,但是在開始之前先說下BrowserThreadStarted這個函數所處的位置和地位,從文件名可以看出BrowserMainLoop這個類負責browser進程的主要工作:即主循環,在主循環剛啟動時,需要啟動一些必需的任務,負責啟動這些任務的函數是BrowserMainLoop::CreateStartupTasks,說到這,其實還不太清楚是什么時機啟動的,負責調用這個函數的是BrowserMainRunnerImp;(可以看成是browser進程的入口函數的等價)

// Main routine for running as the Browser process.
int BrowserMain(const MainFunctionParams& parameters) {
  ScopedBrowserMainEvent scoped_browser_main_event;

  base::trace_event::TraceLog::GetInstance()->set_process_name("Browser");
  base::trace_event::TraceLog::GetInstance()->SetProcessSortIndex(
      kTraceEventBrowserProcessSortIndex);

  std::unique_ptr<BrowserMainRunnerImpl> main_runner(
      BrowserMainRunnerImpl::Create());

  int exit_code = main_runner->Initialize(parameters);
  if (exit_code >= 0)
    return exit_code;

  exit_code = main_runner->Run();

  main_runner->Shutdown();

  return exit_code;
}
int BrowserMainRunnerImpl::Initialize(const MainFunctionParams& parameters) {
...
  main_loop_->CreateStartupTasks();
...
}

那么繼續聊方才提到的GpuProcessHost::Get,他干了點什么呢?主要是初始化GpuProcessHost對象並調用GpuProcessHost::Init函數,下面是Init函數片段

bool GpuProcessHost::Init() {
  init_start_time_ = base::TimeTicks::Now();
...
  if (in_process_) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    DCHECK(GetGpuMainThreadFactory());
    gpu::GpuPreferences gpu_preferences = GetGpuPreferencesFromCommandLine();
    GpuDataManagerImpl::GetInstance()->UpdateGpuPreferences(&gpu_preferences);
    in_process_gpu_thread_.reset(GetGpuMainThreadFactory()(
        InProcessChildThreadParams(
            base::ThreadTaskRunnerHandle::Get(),
            process_->GetInProcessMojoInvitation(),
            process_->child_connection()->service_token()),
        gpu_preferences));
    base::Thread::Options options;
#if defined(OS_WIN) || defined(OS_MACOSX)
    // WGL needs to create its own window and pump messages on it.
    options.message_loop_type = base::MessageLoop::TYPE_UI;
#endif
#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
    options.priority = base::ThreadPriority::DISPLAY;
#endif
    in_process_gpu_thread_->StartWithOptions(options);

    OnProcessLaunched();  // Fake a callback that the process is ready.
  } else if (!LaunchGpuProcess()) {
    return false;
  }
...

其中LaunchGpuProcess就是啟動gpu進程的元凶,而這個函數的主要任務是構造進程使用的參數,也就是cmd_line,然后把cmd_line交給真正啟動進程的BrowserChildProcessHostImpl對象,調用BrowserChildProcessHostImpl::Launch啟動一個browser的子進程

子進程創建

為什么要把這個部分獨立出來呢?google除了browser以外的進程都是用下面的流程創建出來的,因此獨立出來作為通用部分講解。

void BrowserChildProcessHostImpl::Launch(
    std::unique_ptr<SandboxedProcessLauncherDelegate> delegate,
    std::unique_ptr<base::CommandLine> cmd_line,
    bool terminate_on_shutdown) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  GetContentClient()->browser()->AppendExtraCommandLineSwitches(cmd_line.get(),
                                                                data_.id);

  const base::CommandLine& browser_command_line =
      *base::CommandLine::ForCurrentProcess();
  static const char* const kForwardSwitches[] = {
      service_manager::switches::kDisableInProcessStackTraces,
      switches::kDisableBackgroundTasks,
      switches::kDisableLogging,
      switches::kEnableLogging,
      switches::kIPCConnectionTimeout,
      switches::kLoggingLevel,
      switches::kTraceToConsole,
      switches::kV,
      switches::kVModule,
  };
  cmd_line->CopySwitchesFrom(browser_command_line, kForwardSwitches,
                             arraysize(kForwardSwitches));

  if (child_connection_) {
    cmd_line->AppendSwitchASCII(
        service_manager::switches::kServiceRequestChannelToken,
        child_connection_->service_token());
  }

  // All processes should have a non-empty metrics name.
  DCHECK(!data_.metrics_name.empty());

  notify_child_disconnected_ = true;
  child_process_.reset(new ChildProcessLauncher(
      std::move(delegate), std::move(cmd_line), data_.id, this,
      std::move(mojo_invitation_),
      base::Bind(&BrowserChildProcessHostImpl::OnMojoError,
                 weak_factory_.GetWeakPtr(),
                 base::ThreadTaskRunnerHandle::Get()),
      terminate_on_shutdown));
  ShareMetricsAllocatorToProcess();
}

先不看該函數的第一個參數std::unique_ptr<SandboxedProcessLauncherDelegate> delegate(和沙盒有關,所有的子進程多多少少都被有沙盒所限制),重點在該函數的最后ChildProcessLauncher,這個類的構造函數中會構造另一個類ChildProcessLauncherHelper的實例

  helper_ = new ChildProcessLauncherHelper(
      child_process_id, client_thread_id_, std::move(command_line),
      std::move(delegate), weak_factory_.GetWeakPtr(), terminate_on_shutdown,
      std::move(mojo_invitation), process_error_callback);
  helper_->StartLaunchOnClientThread();

關鍵就是這個helper的StartLaunchOnClientThread()函數,這個函數會在client線程上啟動一個新的進程,但是在,71目前的版本中browser中已經移除了名稱為client的線程,這說明什么?說明google可能還沒給這個函數改名字,在64版本的chromium代碼中確實是由client線程創建進程,但是在71中則是交給了一個線程池的worker去創建進程了

void ChildProcessLauncherHelper::LaunchOnLauncherThread() {
  DCHECK(CurrentlyOnProcessLauncherTaskRunner());

  begin_launch_time_ = base::TimeTicks::Now();

  std::unique_ptr<FileMappedForLaunch> files_to_register = GetFilesToMap();

  bool is_synchronous_launch = true;
  int launch_result = LAUNCH_RESULT_FAILURE;
  base::LaunchOptions options;

  Process process;
  if (BeforeLaunchOnLauncherThread(*files_to_register, &options)) {
    process =
        LaunchProcessOnLauncherThread(options, std::move(files_to_register),
                                      &is_synchronous_launch, &launch_result);

    AfterLaunchOnLauncherThread(process, options);
  }

  if (is_synchronous_launch) {
    PostLaunchOnLauncherThread(std::move(process), launch_result);
  }
}

從該函數就能明顯的看出LaunchProcessOnLauncherThread就是最主要的部分,剩下的部分就是在創建進程之前的准備和創建進程后的處理,下面是該函數的實現

ChildProcessLauncherHelper::Process
ChildProcessLauncherHelper::LaunchProcessOnLauncherThread(
    const base::LaunchOptions& options,
    std::unique_ptr<FileMappedForLaunch> files_to_register,
    bool* is_synchronous_launch,
    int* launch_result) {
  DCHECK(CurrentlyOnProcessLauncherTaskRunner());
  DCHECK(mojo_channel_);
  DCHECK(mojo_channel_->remote_endpoint().is_valid());

  // TODO(750938): Implement sandboxed/isolated subprocess launching.
  Process child_process;
  child_process.process = base::LaunchProcess(*command_line(), options);
  return child_process;
}

可以看到進程的創建其實是使用了base庫的LaunchProcess函數,熟悉chromium代碼的話會知道,base庫是基礎庫,提供一些常用組件(例如智能指針,字符串等等結構),那么到這步的話就能知道真的要開始見到熟悉的進程創建代碼了。因為我是在linux環境下運行的chromium,因此要在base/process/launch_posix.cc文件中看函數實現,如果是windows環境可以在base/process/launch_windows.cc文件中看該函數的實現。

Process LaunchProcess(const std::vector<std::string>& argv,
                      const LaunchOptions& options) {
  TRACE_EVENT0("base", "LaunchProcess");
...
  {
    pid = fork();
  }

  // Always restore the original signal mask in the parent.
  if (pid != 0) {
    base::TimeTicks after_fork = TimeTicks::Now();
    SetSignalMask(orig_sigmask);

    base::TimeDelta fork_time = after_fork - before_fork;
    UMA_HISTOGRAM_TIMES("MPArch.ForkTime", fork_time);
  }

  if (pid < 0) {
    DPLOG(ERROR) << "fork";
    return Process();
  }
  if (pid == 0) {
    // Child process
...
    const char* executable_path = !options.real_path.empty() ?
        options.real_path.value().c_str() : argv_cstr[0];

    execvp(executable_path, argv_cstr.data());

    RAW_LOG(ERROR, "LaunchProcess: failed to execvp:");
    RAW_LOG(ERROR, argv_cstr[0]);
    _exit(127);
  } else {
    // Parent process
    if (options.wait) {
      // While this isn't strictly disk IO, waiting for another process to
      // finish is the sort of thing ThreadRestrictions is trying to prevent.
      ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
      pid_t ret = HANDLE_EINTR(waitpid(pid, nullptr, 0));
      DPCHECK(ret > 0);
    }
  }

  return Process(pid);
}

可以看到fork出來的子進程會去執行一個程序並將之前准備的cmd_line放入argv_cstr中,execvp(executable_path, argv_cstr.data());那么executable_path就成為子進程執行什么程序的關鍵。子進程到這里就創建完畢了。在調試browser進程的時候是無法調試到if (pid == 0)的子進程的部分的┓( ´∀` )┏,還是用日志打來看吧。

創建gpu進程時關鍵參數executable_path的值是/proc/self/exe,而對應的參數是

--type=gpu-process
--field-trial-handle=...
--user-data-dir=...
--homedir=...
--gpu-preferences=...
--service-request-channel-token=...

其中...代表一些具體設置的值

chromium子進程創建流程

以為到這里就結束了?還沒呢!難道對/proc/self/exe不感興趣么?這明顯不是個gpu程序吧?在chromium代碼中有個content/gpu/gpu_main.cc文件,其中有個int GpuMain(const MainFunctionParams& parameters)函數,這看着才像是gpu進程的入口啊(事實證明也是如此),那么是如何完成這個跳轉的?

首先先看/proc/self/exe,這個東西的功能是再執行自己一次,沒錯自己執行自己,例如你在bash下執行這個可執行程序就會又進入一個bash。那么google讓browser進程的子進程執行這個東西是為了讓子進程走一遍主入口函數的流程進行同樣的初始化,然后在入口后不久就區分進程類型,這就是這個--type=gpu-process參數的意義用於區分進程類型,然后確定子進程執行的入口,比如gpu就去執行GpuMain,renderer進程執行RendererMain等等。沒錯,browser進程也是在這部分區分為主進程的,主進程在啟動時沒有--type參數,所以在區分會被命名為browser進程

那么這個誰都會走的流程是什么樣的呢?下面是運行堆棧

#3 0x7fe228646e77 content::ContentMainRunnerImpl::Run()
#4 0x7fe22863cbac content::ContentServiceManagerMainDelegate::RunEmbedderProcess()
#5 0x7fe22e442bb1 service_manager::Main()
#6 0x7fe228642d25 content::ContentMain()
#7 0x55d02587b566 ChromeMain
#8 0x55d02587b472 main
#9 0x7fe1fdbe9830 __libc_start_main
#10 0x55d02587b34a _start

其中main就是熟悉的入口啦,那么區分進程類型的關鍵就在content::ContentMainRunnerImpl::Run()

int ContentMainRunnerImpl::Run(bool start_service_manager_only) {
  DCHECK(is_initialized_);
  DCHECK(!is_shutdown_);
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();
  std::string process_type =
      command_line.GetSwitchValueASCII(switches::kProcessType);
...
  if (process_type.empty()) {
...
      return RunBrowserProcessMain(main_params, delegate_);
  } // if (process_type.empty())
...
  return RunOtherNamedProcessTypeMain(process_type, main_params, delegate_);
}

這里區分了browser進程和其他類型進程,RunOtherNamedProcessTypeMain這個函數會完成gpu進程的區分。

PS:如果是fork出來的進程的話,這里是已經在子進程中了,也就是說除了browser進程,一般都是RunOtherNamedProcessTypeMain

int RunOtherNamedProcessTypeMain(const std::string& process_type,
                                 const MainFunctionParams& main_function_params,
                                 ContentMainDelegate* delegate) {
#if !defined(CHROME_MULTIPLE_DLL_BROWSER)
  static const MainFunction kMainFunctions[] = {
#if BUILDFLAG(ENABLE_PLUGINS)
    {switches::kPpapiPluginProcess, PpapiPluginMain},
    {switches::kPpapiBrokerProcess, PpapiBrokerMain},
#endif  // ENABLE_PLUGINS
    {switches::kUtilityProcess, UtilityMain},
    {switches::kRendererProcess, RendererMain},
    {switches::kGpuProcess, GpuMain},
  };

  for (size_t i = 0; i < base::size(kMainFunctions); ++i) {
    if (process_type == kMainFunctions[i].name) {
      int exit_code = delegate->RunProcess(process_type, main_function_params);
      if (exit_code >= 0)
        return exit_code;
      return kMainFunctions[i].function(main_function_params);
    }
  }
#endif  // !CHROME_MULTIPLE_DLL_BROWSER

#if BUILDFLAG(USE_ZYGOTE_HANDLE)
  // Zygote startup is special -- see RunZygote comments above
  // for why we don't use ZygoteMain directly.
  if (process_type == service_manager::switches::kZygoteProcess)
    return RunZygote(delegate);
#endif  // BUILDFLAG(USE_ZYGOTE_HANDLE)

  // If it's a process we don't know about, the embedder should know.
  return delegate->RunProcess(process_type, main_function_params);
}

從整個流程就能看出GpuMain並不能算是gpu子進程的入口函數,只是個被調用的函數而已。delegate->RunProcess(process_type, main_function_params);這句代碼十分有迷惑性,其實並不是在跑進程,而是和kMainFunctions[i].function(main_function_params);一樣在執行函數,但是由於delegate並不處理gpu,所以暫且不看RunProcess的實現了,GpuMain的執行交給了MainFunction這個結構體,結構體如下

// We dispatch to a process-type-specific FooMain() based on a command-line
// flag.  This struct is used to build a table of (flag, main function) pairs.
struct MainFunction {
  const char* name;
  int (*function)(const MainFunctionParams&);
};

一個很簡單的函數指針,所以直接執行就完事了。

chromium 采用了這種方式去初始化進程,我還需要多多學習啊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM