chrome源碼學習之啓動流程簡介

http://blog.csdn.net/ciml/article/details/5730209

先說明一下,我這裏採用的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的體系架構相關的內容。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章