chrome源码学习之启动流程简介

先说明一下,我这里采用的chrome源代码版本是4.1.249.1064。如果你采用的不是此版本,则可能和我描述的源代码文件名、代码位置不一致,后续关于chrome的文章均采用此版本,不再另作说明。采用此版本没有任何特殊理由,仅仅是当我开始学习chrome的那个时间点的最新版本而已。

另外虽然chrome的版本升级非常快,但其核心体系架构是没有变化的。升级的变更的内容主要体现在下面几个方面:

1.安全性、稳定性、速度提升

2.新功能点增加

3.HTML5相关内容

4.整合集成google自己的产品服务

因此从学习角度看,如果不是特别需要对某些新增加的特性感兴趣,如果重点是想学习其基础体系结构和web核心相关模块,那么选择一个较早的版本来学习反而更好。因为这样代码量少,重点突出,干扰少些。当掌握好基本体系结构后,后续的版本自然就很容易上手了。

下面简要说明chrome的启动流程,这里重点不会分析代码细节,而是快速定位几个比较关键的位置,可以在这些位置设置断点,然后你可以查看调用栈以了解具体的调用流程。这样可以先初步进入开始单步跟踪。

为了便于跟踪说明,请在浏览器选项菜单中将启动的主页地址设置为一个具体地址比如http://www.baidu.com/

注意:因为chrome是多进程结构,所以在调试的时候需要附加到对应的进程才能看到断点效果。如果不想那么麻烦,也可以以单进程模式启动调试,具体是在chrome exe的工程属性中加命令行参数 --single-process。我将以多进程模式为例来讲解。

EXE启动入口

应用程序执行文件的WinMain入口函数在chrome exe工程的chrome_exe_main.cc文件中,在一些初始化代码后,加载

chrome.dll内核文件,代码如下:

...
MainDllLoader* loader = MakeMainDllLoader(); 
int rc = loader->Launch(instance, &sandbox_info);
...

MainDllLoader::Launch的实现在client_util.cc中,其中有代码(…为我忽略掉的代码):

int MainDllLoader::Launch(HINSTANCE instance,
                          sandbox::SandboxInterfaceInfo* sbox_info) {
  ...
  dll_ = Load(&version, &file);
  ...
  DLL_MAIN entry_point =
      reinterpret_cast<DLL_MAIN>(::GetProcAddress(dll_, "ChromeMain"));
  ...
  
  int rc = entry_point(instance, sbox_info, ::GetCommandLineW());
  return OnBeforeExit(rc);
}

通过调用Win32 API GetProcAddress来动态获取chrome.dll中的入口函数“ChromeMain”的地址然后调用进入。

DLL入口函数

chrome.dll的入口函数是ChromeMain,其工程名为chrome_dll。

ChromeMain的实现在chrome_dll_main.cc中:

DLLEXPORT int __cdecl ChromeMain(HINSTANCE instance,
                                 sandbox::SandboxInterfaceInfo* sandbox_info,
                                 TCHAR* command_line) 
{
  ...
  int rv = -1;
  if (process_type == switches::kRendererProcess) {
    rv = RendererMain(main_params);
  } else if (process_type == switches::kExtensionProcess) {
    // An extension process is just a renderer process. We use a different
    // command line argument to differentiate crash reports.
    rv = RendererMain(main_params);
  } else if (process_type == switches::kPluginProcess) {
    rv = PluginMain(main_params);
  } else if (process_type == switches::kUtilityProcess) {
    rv = UtilityMain(main_params);
  } else if (process_type == switches::kProfileImportProcess) {
    ...
  } else if (process_type == switches::kWorkerProcess) {
    rv = WorkerMain(main_params);
   ...
  } else if (process_type == switches::kZygoteProcess) {
   ...
  } else if (process_type.empty()) {
   ...
    ScopedOleInitializer ole_initializer;
    rv = BrowserMain(main_params);
  } else {
    NOTREACHED() << "Unknown process type";
  }
  ...
}
                                 

我摘录了上面需要重点说明的一段代码,在ChromeMain中,前面进行一些初始化代码后,上述通过进程类型来判断是进入何种类型的进程入口。从上面的代码我们看到了Render进程入口RendererMain,插件进程入口PluginMain,浏览器主进程入口BrowserMain等。对于我们分析chrome主体结构最重要就是BrowserMain和RendererMain。当我们打开浏览器,这里显然进入到浏览器主进程BroserMain入口。可见chrome所有不同类型的进程启动入口都通过ChromeMain来统一处理。具体入口函数是通过命令行参数传递进程类型参数来决定。

注:renderer进程的启动是在Browser主进程代码中动态启动chrome.exe,以传入命令行参数使其进程类型参数为render,然后进入RenderderMain入口从而启动Render内核进程与主进程协作通信。

Browser主进程入口

BrowserMain入口的实现在browser工程的browser_main.cc中:

int BrowserMain(const MainFunctionParams& parameters) {
  ...
  MessageLoop main_message_loop(MessageLoop::TYPE_UI);
  ...
  scoped_ptr<BrowserProcessImpl> browser_process;
  if (parsed_command_line.HasSwitch(switches::kImport)) {
    ...
  } else {
    browser_process.reset(new BrowserProcessImpl(parsed_command_line));
  }
  ...
  
  BrowserInit browser_init;
  browser_process->db_thread();
  browser_process->file_thread();
  browser_process->process_launcher_thread();
  browser_process->io_thread();
  int result_code = ResultCodes::NORMAL_EXIT;
  if (parameters.ui_task) {
    ...
  } else {
    // We are in regular browser boot sequence. Open initial stabs and enter
    // the main message loop.
    if (browser_init.Start(parsed_command_line, std::wstring(), profile,
                           &result_code)) {
      ...
      RunUIMessageLoop(browser_process.get());
    }
  }
   ...
  browser_process.release();
  browser_shutdown::Shutdown();
  return result_code;
}
上面是经过我简化后的代码,其中主要完成了:
1.生成UI类型的消息循环对象main_message_loop
2.生成browser进程对象browser_process,并初始化(启动)文件、数据库、io等辅助线程。
3.调用browser_init.Start启动浏览器主UI界面,该句执行后,浏览器窗口就显示出来了。
4.调用RunUIMessageLoop进入浏览器UI主线程消息循环,后续工作完全基于消息驱动。
RunUIMessageLoop的实现仅仅是调用MessageLoopForUI::current()->Run()进入消息循环而已。

浏览器窗口启动过程

浏览器窗口的显示过程在browser_init.Start中完成。具体我们再看一下这个函数的调用过程,该函数最终会运行到
browser_init.cc文件中的BrowserInit::LaunchWithProfile::OpenURLsInBrowser(),整理如下:
Browser* BrowserInit::LaunchWithProfile::OpenURLsInBrowser(
    Browser* browser,
    bool process_startup,
    const std::vector<GURL>& urls) 
{
  ...
  if (!browser || browser->type() != Browser::TYPE_NORMAL)
    browser = Browser::Create(profile_);
  for (size_t i = 0; i < urls.size(); ++i) {
    if (!process_startup && !URLRequest::IsHandledURL(urls[i]))
      continue;
    TabContents* tab = browser->AddTabWithURL(
        urls[i], GURL(), PageTransition::START_PAGE, (i == 0), -1, false, NULL);
    if (i < static_cast<size_t>(pin_count))
      browser->tabstrip_model()->SetTabPinned(browser->tab_count() - 1, true);
    if (profile_ && i == 0 && process_startup)
      AddCrashedInfoBarIfNecessary(tab);
  }
  browser->window()->Show();
  ...
  return browser;
}
该函数会创建一个Browser对象,每一个浏览器窗口都对应一个Browser对象。然后调用browser对象的AddTabWithURL
方法来打开相应的URL,我们的主页地址也将在AddTabWithURL中被打开。最后调用browser->window()->Show()
显示浏览器UI窗口,对应的url页面也将被显示。

Renderer进程的启动

作为多进程架构,所谓Renderer进程就是内核的渲染进程,每个页面的显示都有Renderer进程中相应的内核对象与之对应。
Renderer进程主要包含了Webkit和V8两大内核对象。

Renderer进程的启动过程是随着URL的打开而启动,即browser->AddTabWithURL调用会触发Renderer进程的启动,具体在browser_render_process_host.cc中的Init方法:

bool BrowserRenderProcessHost::Init(bool is_extensions_process) {
  ...
  base::Thread* io_thread = g_browser_process->io_thread();
  scoped_refptr<ResourceMessageFilter> resource_message_filter =
      new ResourceMessageFilter(g_browser_process->resource_dispatcher_host(),
                                id(),
                                audio_renderer_host_.get(),
                                PluginService::GetInstance(),
                                g_browser_process->print_job_manager(),
                                profile(),
                                widget_helper_,
                                profile()->GetSpellChecker());
 
  const std::string channel_id =
      ChildProcessInfo::GenerateRandomChannelID(this);
  channel_.reset(
      new IPC::SyncChannel(channel_id, IPC::Channel::MODE_SERVER, this,
                           resource_message_filter,
                           io_thread->message_loop(), true,
                           g_browser_process->shutdown_event()));
 
  if (run_renderer_in_process()) {
    ...
  } else {
    ...
    child_process_.reset(new ChildProcessLauncher(
#if defined(OS_WIN)
        FilePath(),
#elif defined(POSIX)
        base::environment_vector(),
        channel_->GetClientFileDescriptor(),
#endif
        cmd_line,
        this));
    fast_shutdown_started_ = false;
  }
  return true;
}
browser_render_process_host.cc文件是理解体系结构很重要的一个文件,其中的Init方法是关键。可以在该方法中
设置断点,通过查看调用栈可以了解详细的调用路径。在Init中,先构造一个资源消息过滤器(ResourceMessageFilter)
然后构造和Renderer进程通信的IPC通道,并将资源消息过滤器安装在该通道上。最后通过构造一个ChildProcessLauncher
对象来启动Renderer进程,注意ChildProcessLauncher构造函数传递了执行文件路径,命令行等参数。
下面看ChildProcessLauncher构造函数的实现,构造函数最终会调用到child_process_launcher.cc中的
Context嵌套类的Launch函数:
  void Launch(
#if defined(OS_WIN)
      const FilePath& exposed_dir,
#elif defined(OS_POSIX)
      const base::environment_vector& environ,
      int ipcfd,
#endif
      CommandLine* cmd_line,
      Client* client) {
    client_ = client;
    CHECK(ChromeThread::GetCurrentThreadIdentifier(&client_thread_id_));
    ChromeThread::PostTask(
        ChromeThread::PROCESS_LAUNCHER, FROM_HERE,
        NewRunnableMethod(
            this,
            &Context::LaunchInternal,
#if defined(OS_WIN)
            exposed_dir,
#elif defined(POSIX)
            environ,
            ipcfd,
#endif
            cmd_line));
  }

这里的关键是ChromeThread::PostTask()的调用。该函数是chrome中线程调度体系的一个重要助手函数。当一个线程需要向另外一个线程投递一个任务时(在另外一个线程上运行指定的代码)调用该函数很方便,第一个参数是目标线程的标识符(chrome的每种线程都有对应的标识符,通过标识符就可以获取到线程对象)。第三个参数是需要在目标线程上运行的代码。chrome提供了很多灵活的模板方法(可以参考base项目中的task.h实现)来构造第三个参数,第三个参数即可以是一个独立的函数入口,又可以是某个类对象的公有方法,且该方法可以有任意的参数数目和数据类型。

在这里,主UI线程把启动Render进程的任务投递给独立的“进程启动者线程”(PROCESS_LAUNCHER)来完成,具体的代码是调用Context::LaunchInternal()方法:

void LaunchInternal(
      const FilePath& exposed_dir,
      CommandLine* cmd_line) 
 {
   ...
    scoped_ptr<CommandLine> cmd_line_deleter(cmd_line);
    base::ProcessHandle handle = base::kNullProcessHandle;
    handle = sandbox::StartProcessWithAccess(cmd_line, exposed_dir);
    ...
 }

这里通过调用sandbox::StartProcessWithAccess() Renderer子进程就启动起来了。注意上述代码不是运行在主UI线程中,而是运行在前面已经初始化的“进程启动器线程”中。在前面的BrowserMain中有语句:

browser_process->process_launcher_thread(); 即是启动了“进程启动器线程”。

Renderer进程入口

renderer进程的入口如前所述,统一在dll的ChromeMain函数中处理,根据类型是renderer而进入RendererMain函数。

该函数在Renderer工程的render_main.cc中实现。由于采用多进程模式,Render是作为子进程方式以命令行参数方式启动,要跟踪Renderer进程就需要附加到Renderer进程,要附加进程则必须先启动进程,因此对RendererMain的顶点入口我们无法设定断点跟踪进去(进程启动后,RenderMain已经执行)。如果要单步跟踪每个详细步骤,则可以考虑用单进程模式启动。多于多进程模式,虽然无法在RenderMain设点断点,但好在该函数并不复杂,主要也是一个消息循环而已。

所以为了理解Renderer内部的初始化过程,我们需要在Renderer工程的render_view.cc这个关键文件中设定断点来查看调用栈。注意:为了及早跟踪必须尽早附加到启动后的Render进程,所以最早的时机是执行了上面LaunchInternal函数中的语句handle = sandbox::StartProcessWithAccess(cmd_line, exposed_dir); 该语句执行后Render进程就生成了,此时通过vs2008 ”Tools“菜单中的“Attach to Process”来附加到Render进程(chrome.exe)。

附加后,我们在RenderView::Init中设置断点:

void RenderView::Init(gfx::NativeViewId parent_hwnd,
                      int32 opener_id,
                      const RendererPreferences& renderer_prefs,
                      SharedRenderViewCounter* counter,
                      int32 routing_id) {
  ...
  webwidget_ = WebView::create(this);
  Singleton<ViewMap>::get()->insert(std::make_pair(webview(), this));
  webkit_preferences_.Apply(webview());
  webview()->initializeMainFrame(this);
  ...
  OnSetRendererPrefs(renderer_prefs);
  routing_id_ = routing_id;
  render_thread_->AddRoute(routing_id_, this);
  ...
}

通过调用栈可以看到该函数的调用路径,从调用路径中可以看出该初始化过程是由一个IPC消息触发,消息通过IPC通道最终被分派到void ChildThread::OnMessageReceived(…),通过消息映射,该消息ViewMsg_New由相应的函数来处理,并最终调用到RenderView::Init完成内核初始化。在RenderView::Init中,webview()->initializeMainFrame(this)此句完成了webkit内核的初始化。 以后的过程就是主进程和Renderer进程通过IPC通道进行消息交互的协作过程。

ViewMsg_New消息是浏览器主进程在打开新页面的时候发送给Renderer进程的控制消息,其目的是打开一个对应的RenderView对象完成内核初始化,在主进程中与之对应的通信对象是RenderViewHost。后续我会详细解释。

 

总结

本文简要介绍了chrome的启动过程,包括浏览器主进程和内核Renderer进程的启动。可能你会觉得这些内容都是泛泛而谈,让人摸不着头脑,并没有什么实质内容。确实,由于chrome的庞大,要详细深入阐述每个步骤是非常繁琐的,我想最好还是由粗到细逐步推进,且重点是掌握其体系架构。本文的目的主要是为那些想迫不及待的开始跟踪chrome代码进行学习的朋友提供一点简单的线索。后续我将从原理上较深入的讲解chrome的体系架构相关的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值