本篇文档以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. mybaits中"#"和"$"的区别

    动态 sql 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 mybatis 会对其进行动态解析.mybatis 为我们提供了两种支持动态 sql ...

  2. 前端视频插件Aliplayer播放器简单使用(基于地址播放)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http ...

  3. postman小结

    1.get和post请求,get有限制2k,post没有限制post安全 在选择的时候别把get post选错然后,run 2.data 选成txt文件  utf-8 ip ip,result12.1 ...

  4. Codeforces Round #555 (Div. 3) E. Minimum Array 【数据结构 + 贪心】

    一 题面 E. Minimum Array 二 分析 注意前提条件:$0 \le  a_{i} \lt n$ 并且 $0 \le  b_{i} \lt n$.那么,我们可以在$a_{i}$中任取一个数 ...

  5. 【性能测试】脚本开发,最普通的http协议脚本2

    Action() { lr_start_transaction("FM0075基金购买"); web_submit_data("ehouse_ehGetPwdRandom ...

  6. docker + nginx 部署vuejs3.0项目

    1:用指令 npm run build 打包vusjs项目(该项目是在github上下载的).打包成功后会生成一个目录dist. 2:把该文件夹拷贝到腾讯云服务器(操作系统 centos7)下的/us ...

  7. mono for android读书笔记之硬件编程(转)

    本章将会介绍: 传感器的API 加速器编程,设备的方向,近场检测 网络编程 蓝牙编程 上述的技术的应用场景很多,比如: 1.检测当前的网络是否可用,并提醒用户,检测当前的网络类型,比如Wifi.3G. ...

  8. value power two

    /** *topPower2 use to set unsigned int to power of two *@param value input value *@return return the ...

  9. Java网络编程基础之TCP粘包拆包

    TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想象河里的流水,他们是连成一片的,其间并没有分界线.TCP底层并不了解上层业务数据的具体含义,他会根据TCP缓冲区的实 ...

  10. 《LeetBook》leetcode题解(6): ZigZag Conversion[E]

    我现在在做一个叫<leetbook>的免费开源书项目,力求提供最易懂的中文思路,目前把解题思路都同步更新到gitbook上了,需要的同学可以去看看 书的地址:https://hk029.g ...