本篇文档以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 采用了这种方式去初始化进程,我还需要多多学习啊

【Chromium】GPU进程启动流程的更多相关文章

  1. Chromium的GPU进程启动流程

    转载请注明出处:http://www.cnblogs.com/fangkm/p/3960327.html 硬件渲染依赖计算机的GPU,GPU种类繁多,兼容这么多种类的硬件,稳定性是个大问题,虽然Chr ...

  2. ORACLE11G R2 RAC的进程启动流程

    简要说明ORACLE11GR2 RAC的进程启动流程: 1.启动流程概览图: 二.RAC启动流程的梳理: 第一层:OHASD 启动:(OHASD派生) 1.CSSDAGENT负责启动CSSD的AGEN ...

  3. broadcom代码中httpd进程启动流程介绍

    Broadcom代码中包含WEB配置管理媒介, 在嵌入式WEB服务器min_httpd基础上改造实现, 其bin名称为httpd,此httpd可以由管理进程有连接后动态启动,并且当一段时间内没有连接到 ...

  4. ARM-Linux移植之(三)——init进程启动流程分析

    我们通常使用Busybox来构建根文件系统的必要的应用程序.Busybox通过传入的参数来决定执行何种操作.当init进程启动时,实际上调用的是Busybox的init_main()函数,下面我们来分 ...

  5. 内核启动流程3--Busybox的init进程

    Busybox是用来制作文件系统的一个工具集,可以用来替换GNU fileutils shellutils等工具集,它为各种小型的或者嵌入式系统提供了比较完全的工具集. 它提供的核心程序中包括了用户空 ...

  6. Android系统开机启动流程及init进程浅析

    Android系统启动概述 Android系统开机流程基于Linux系统,总体可分为三个阶段: Boot Loader引导程序启动Linux内核启动Android系统启动,Launcher/app启动 ...

  7. Android系统启动流程(二)解析Zygote进程启动过程

    1.Zygote简介 在Android系统中,DVM(Dalvik虚拟机).应用程序进程以及运行系统的关键服务的SystemServer进程都是由Zygote进程来创建的,我们也将它称为孵化器.它通过 ...

  8. Android系统启动流程(一)解析init进程启动过程

    整体流程大致如下:     1.init简介 init进程是Android系统中用户空间的第一个进程,作为第一个进程,它被赋予了很多极其重要的工作职责,比如创建zygote(孵化器)和属性服务等.in ...

  9. chromium for android GPU进程结构分析

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/jaylinzhou/article/details/27517471 GPU进程的client(Br ...

随机推荐

  1. 【javascript】—— JS判断浏览器类型、操作系统

    navigator.userAgent : userAgent 属性是一个只读的字符串,声明了浏览器用于 HTTP 请求的用户代理头的值. navigator.platform : platform ...

  2. C++与C的区别一

    1. C++风格数组初始化: #include <iostream> #include <array> using namespace std; void main() { / ...

  3. Cassandra2.0.8导入到eclipse运行

    如果想通过eclipse来调试或者查看Cassandra的代码,将其project导入到eclipse之中不愧是个好选择.下面将讲述将2.0.8 版本导入elcipse的过程. 该篇文章主要参考的官方 ...

  4. QuantLib 金融计算——随机过程之一般 Black Scholes 过程

    目录 QuantLib 金融计算--随机过程之一般 Black Scholes 过程 一般 Black Scholes 过程 如果未做特别说明,文中的程序都是 Python3 代码. QuantLib ...

  5. Welcome! This is the documentation for Python 3.6.8

    The Zen of Python, by Tim Peters Beautiful is better than ugly.Explicit is better than implicit.Simp ...

  6. 生成allure测试报告之后,服务器端口无法访问查看生成的report,可能是这样引起的。

    1. 检查防火墙 2. 如果机器有安装ADsafe,请关闭adsafe后重试

  7. SpringMVC初写(四)上传和下载功能的实现

    一.文件上传 流程: 导入包commons-fileuplad组件和依赖包commons-io组件 配置springmvc支持上传的组件: 启动SpringMVC注解支持 配置上传解释器 构建一个上传 ...

  8. Mac 10.12安装Windows远程桌面工具Microsoft Remote Desktop

    说明:之前Office自带的Windows远程桌面工具虽然简便,但是保存的服务器列表有限.而这个微软推出的自家工具可以完美解决这些问题. 下载: (链接:https://pan.baidu.com/s ...

  9. (转)MySQL自带的性能压力测试工具mysqlslap详解

    mysqlslap 是 Mysql 自带的压力测试工具,可以模拟出大量客户端同时操作数据库的情况,通过结果信息来了解数据库的性能状况 mysqlslap 的一个主要工作场景就是对数据库服务器做基准测试 ...

  10. WPF中使用RenderTransformOrigin来控制动画的起点

    Render :渲染:Transform:动画:Origin:起点 RenderTransformOrigin:渲染动画的起点 取值为一个坐标的形式  取值范围: 0,0 到 1,1 0,0:表示左上 ...