【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 采用了这种方式去初始化进程,我还需要多多学习啊
【Chromium】GPU进程启动流程的更多相关文章
- Chromium的GPU进程启动流程
转载请注明出处:http://www.cnblogs.com/fangkm/p/3960327.html 硬件渲染依赖计算机的GPU,GPU种类繁多,兼容这么多种类的硬件,稳定性是个大问题,虽然Chr ...
- ORACLE11G R2 RAC的进程启动流程
简要说明ORACLE11GR2 RAC的进程启动流程: 1.启动流程概览图: 二.RAC启动流程的梳理: 第一层:OHASD 启动:(OHASD派生) 1.CSSDAGENT负责启动CSSD的AGEN ...
- broadcom代码中httpd进程启动流程介绍
Broadcom代码中包含WEB配置管理媒介, 在嵌入式WEB服务器min_httpd基础上改造实现, 其bin名称为httpd,此httpd可以由管理进程有连接后动态启动,并且当一段时间内没有连接到 ...
- ARM-Linux移植之(三)——init进程启动流程分析
我们通常使用Busybox来构建根文件系统的必要的应用程序.Busybox通过传入的参数来决定执行何种操作.当init进程启动时,实际上调用的是Busybox的init_main()函数,下面我们来分 ...
- 内核启动流程3--Busybox的init进程
Busybox是用来制作文件系统的一个工具集,可以用来替换GNU fileutils shellutils等工具集,它为各种小型的或者嵌入式系统提供了比较完全的工具集. 它提供的核心程序中包括了用户空 ...
- Android系统开机启动流程及init进程浅析
Android系统启动概述 Android系统开机流程基于Linux系统,总体可分为三个阶段: Boot Loader引导程序启动Linux内核启动Android系统启动,Launcher/app启动 ...
- Android系统启动流程(二)解析Zygote进程启动过程
1.Zygote简介 在Android系统中,DVM(Dalvik虚拟机).应用程序进程以及运行系统的关键服务的SystemServer进程都是由Zygote进程来创建的,我们也将它称为孵化器.它通过 ...
- Android系统启动流程(一)解析init进程启动过程
整体流程大致如下: 1.init简介 init进程是Android系统中用户空间的第一个进程,作为第一个进程,它被赋予了很多极其重要的工作职责,比如创建zygote(孵化器)和属性服务等.in ...
- chromium for android GPU进程结构分析
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/jaylinzhou/article/details/27517471 GPU进程的client(Br ...
随机推荐
- python 爬虫入门之爬小说
##第一步 导包from bs4 import BeautifulSoupimport requestsimport sys ##准备class downloder(object): def __in ...
- python基础知识梳理----3基本数据类型,int,bool,str ,for 循环,迭代
一:python的基本类型 1.int -----整数,主要进行数学运算 2.str -----字符串,可以保存少量数据,并进行相关操作, 3. bool ---布尔类型,判断真假 4.list ...
- 回归到jquery
最近在做一个公司的老产品的新功能,使用原来的技术框架,jquery和一堆插件,使用jquery的话,灵活性是有了,但是对于一个工作了3年多的我来说,很low,没什么成就感,技术本身比较简单,但是业务的 ...
- leetcode-852-山脉数组的峰顶索引
题目描述: 我们把符合下列属性的数组 A 称作山脉: A.length >= 3 存在 0 < i < A.length - 1 使得A[0] < A[1] < ... ...
- 多线程:GCD
多线程是程序开发中非常基础的一个概念,大家在开发过程中应该或多或少用过相关的东西.同时这恰恰又是一个比较棘手的概念,一切跟多线程挂钩的东西都会变得复杂.如果使用过程中对多线程不够熟悉,很可能会埋下一些 ...
- python学习,day4:装饰器的使用示例
---恢复内容开始--- 装饰器:本质是函数,(装饰其他函数)就是为其他函数添加附加功能 装饰器有其独特的原则:1.不能修改被装饰的函数的源代码 2.不能修改被装饰的函数的调用方式 例子 import ...
- Linux终端没有GUI,使用matplotlib绘图
一.解决警告信息 ... _tkinter.TclError: no display name and no $DISPLAY environment variable 两种解决方法: 1.pytho ...
- po'j2559 Largest Rectangle in a Histogram 单调栈(递增)
Largest Rectangle in a Histogram Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 29498 ...
- MySQL 创建schema
create schema zzz default character set utf8 collate utf8_general_ci;--创建数据库 grant select,insert,upd ...
- 关于Arduino串口读写HEX
每次和硬件层进行调试的时候,就容易遇到数据格式问题.这不,继上次Matlab上的hex发送后,又遇到了Arduino上接收hex,并进行对比处理的问题.由于单片机级别的处理器只能以字节形式接收,因此无 ...