本篇文档以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. 现在就启用 HTTPS,免费的!

    现在就启用 HTTPS,免费的! 现在,你应该能在访问https://konklone.com的时候,在地址栏里看到一个漂亮的小绿锁了,因为我把这个网站换成了HTTPS协议.一分钱没花就搞定了. 为什 ...

  2. ElasticSearch5.0——IK词库加载

    Dictionary ConfigurationIKAnalyzer.cfg.xml can be located at {conf}/analysis-ik/config/IKAnalyzer.cf ...

  3. 【FAQ】tomcat启动jdk版本不一致

    一.tomcat7.exe与startup.bat的区别: 1.这两个都可以启动tomcat,但tomcat7.exe必须安装了服务才能启动,而startup.bat不需要 2.另外一个区别是它们启动 ...

  4. oracle常用cmd命令

    登陆 sqlplus username/password; 切换: conn username/password; 显示当前登陆用户: show user; 查看用户列表 select usernam ...

  5. Java网络编程以及简单的聊天程序

    网络编程技术是互联网技术中的主流编程技术之一,懂的一些基本的操作是非常必要的.这章主要讲解网络编程,UDP和Socket编程,以及使用Socket做一个简单的聊天软件. 全部代码下载:链接 1.网络编 ...

  6. QuantLib 金融计算——QuantLib 入门

    目录 QuantLib 金融计算--QuantLib 入门 简介 主要功能 安装与使用 学习指南 The HARD Way The EASY Way QuantLib 金融计算--QuantLib 入 ...

  7. Maven 问题笔记汇总

    Web项目通过Maven部署到Tomcat的错误. Maven环境下面多项目之间的引用 Maven 远程仓库下载慢的的解决方案 Intellij IDEA 像eclipse那样给maven添加依赖 I ...

  8. OpenERP 干掉 产品计量单位中的 search more 和 create and edit

    实际操作中特别容易点错而新建了重复的单位,通过下边的方法可以将“search more”和 “create and edit”干掉 在新继承product.product的模块中,修改xml文件 这样 ...

  9. (转).NET技术大系概览 (迄今为止最全的.NET技术栈)

    前言 .Net推出13年了,Visual Studio 2015 / .NET Framework 4.6昨天也发布了. 从2002年的.NET 1.0开始,1.1,2.x,3.x,4.x,每个新版本 ...

  10. hibernate3.3.2搭建Junit日志环境

    搭建好log4j日志环境后,再来搭建Junit测试环境: 测试代码放在另外一个目录下,项目右键,new一个source folder,源代码目录,放我们的测试代码,名字test.src放源代码. 比较 ...