您的位置:首页 > 其它

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

2011-07-07 16:59 453 查看
先说明一下,我这里采用的chrome源代码版本是4.1.249.1064。如果你采用的不是此版本,则可能和我描述的源代码文件名、代码位置不一致,后续关于chrome的文章均采用此版本,不再另作说明。采用此版本没有任何特殊理由,仅仅是当我开始学习chrome的那个时间点的最新版本而已。
另外虽然chrome的版本升级非常快,但其核心体系架构是没有变化的。升级的变更的内容主要体现在下面几个方面:
1.安全性、稳定性、速度提升
2.新功能点增加
3.HTML5相关内容
4.整合集成google自己的产品服务
因此从学习角度看,如果不是特别需要对某些新增加的特性感兴趣,如果重点是想学习其基础体系结构和web核心相关模块,那么选择一个较早的版本来学习反而更好。因为这样代码量少,重点突出,干扰少些。当掌握好基本体系结构后,后续的版本自然就很容易上手了。
下面简要说明chrome的启动流程,这里重点不会分析代码细节,而是快速定位几个比较关键的位置,可以在这些位置设置断点,然后你可以查看调用栈以了解具体的调用流程。这样可以先初步进入开始单步跟踪。
为了便于跟踪说明,请在浏览器选项菜单中将启动的主页地址设置为一个具体地址比如http://www.baidu.com/。
注意:因为chrome是多进程结构,所以在调试的时候需要附加到对应的进程才能看到断点效果。如果不想那么麻烦,也可以以单进程模式启动调试,具体是在chromeexe的工程属性中加命令行参数--single-process。我将以多进程模式为例来讲解。

EXE启动入口

应用程序执行文件的WinMain入口函数在chromeexe工程的chrome_exe_main.cc文件中,在一些初始化代码后,加载
chrome.dll内核文件,代码如下:
...
MainDllLoader*loader=MakeMainDllLoader();
intrc=loader->Launch(instance,&sandbox_info);
...
MainDllLoader::Launch的实现在client_util.cc中,其中有代码(…为我忽略掉的代码):
intMainDllLoader::Launch(HINSTANCEinstance,
sandbox::SandboxInterfaceInfo*sbox_info){
...
dll_=Load(&version,&file);
...
DLL_MAINentry_point=
reinterpret_cast<DLL_MAIN>(::GetProcAddress(dll_,"ChromeMain"));
...
intrc=entry_point(instance,sbox_info,::GetCommandLineW());
returnOnBeforeExit(rc);
}
通过调用Win32APIGetProcAddress来动态获取chrome.dll中的入口函数“ChromeMain”的地址然后调用进入。

DLL入口函数

chrome.dll的入口函数是ChromeMain,其工程名为chrome_dll。
ChromeMain的实现在chrome_dll_main.cc中:
DLLEXPORTint__cdeclChromeMain(HINSTANCEinstance,
sandbox::SandboxInterfaceInfo*sandbox_info,
TCHAR*command_line)
{
...
intrv=-1;
if(process_type==switches::kRendererProcess){
rv=RendererMain(main_params);
}elseif(process_type==switches::kExtensionProcess){
//Anextensionprocessisjustarendererprocess.Weuseadifferent
//commandlineargumenttodifferentiatecrashreports.
rv=RendererMain(main_params);
}elseif(process_type==switches::kPluginProcess){
rv=PluginMain(main_params);
}elseif(process_type==switches::kUtilityProcess){
rv=UtilityMain(main_params);
}elseif(process_type==switches::kProfileImportProcess){
...
}elseif(process_type==switches::kWorkerProcess){
rv=WorkerMain(main_params);
...
}elseif(process_type==switches::kZygoteProcess){
...
}elseif(process_type.empty()){
...
ScopedOleInitializerole_initializer;
rv=BrowserMain(main_params);
}else{
NOTREACHED()<<"Unknownprocesstype";
}
...
}
我摘录了上面需要重点说明的一段代码,在ChromeMain中,前面进行一些初始化代码后,上述通过进程类型来判断是进入何种类型的进程入口。从上面的代码我们看到了Render进程入口RendererMain,插件进程入口PluginMain,浏览器主进程入口BrowserMain等。对于我们分析chrome主体结构最重要就是BrowserMain和RendererMain。当我们打开浏览器,这里显然进入到浏览器主进程BroserMain入口。可见chrome所有不同类型的进程启动入口都通过ChromeMain来统一处理。具体入口函数是通过命令行参数传递进程类型参数来决定。
注:renderer进程的启动是在Browser主进程代码中动态启动chrome.exe,以传入命令行参数使其进程类型参数为render,然后进入RenderderMain入口从而启动Render内核进程与主进程协作通信。

Browser主进程入口

BrowserMain入口的实现在browser工程的browser_main.cc中:
intBrowserMain(constMainFunctionParams¶meters){
...
MessageLoopmain_message_loop(MessageLoop::TYPE_UI);
...
scoped_ptr<BrowserProcessImpl>browser_process;
if(parsed_command_line.HasSwitch(switches::kImport)){
...
}else{
browser_process.reset(newBrowserProcessImpl(parsed_command_line));
}
...
BrowserInitbrowser_init;
browser_process->db_thread();
browser_process->file_thread();
browser_process->process_launcher_thread();
browser_process->io_thread();
intresult_code=ResultCodes::NORMAL_EXIT;
if(parameters.ui_task){
...
}else{
//Weareinregularbrowserbootsequence.Openinitialstabsandenter
//themainmessageloop.
if(browser_init.Start(parsed_command_line,std::wstring(),profile,
&result_code)){
...
RunUIMessageLoop(browser_process.get());
}
}
...
browser_process.release();
browser_shutdown::Shutdown();
returnresult_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,
boolprocess_startup,
conststd::vector<GURL>&urls)
{
...
if(!browser||browser->type()!=Browser::TYPE_NORMAL)
browser=Browser::Create(profile_);
for(size_ti=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();
...
returnbrowser;
}
该函数会创建一个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方法:
boolBrowserRenderProcessHost::Init(boolis_extensions_process){
...
base::Thread*io_thread=g_browser_process->io_thread();
scoped_refptr<ResourceMessageFilter>resource_message_filter=
newResourceMessageFilter(g_browser_process->resource_dispatcher_host(),
id(),
audio_renderer_host_.get(),
PluginService::GetInstance(),
g_browser_process->print_job_manager(),
profile(),
widget_helper_,
profile()->GetSpellChecker());
conststd::stringchannel_id=
ChildProcessInfo::GenerateRandomChannelID(this);
channel_.reset(
newIPC::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(newChildProcessLauncher(
#ifdefined(OS_WIN)
FilePath(),
#elifdefined(POSIX)
base::environment_vector(),
channel_->GetClientFileDescriptor(),
#endif
cmd_line,
this));
fast_shutdown_started_=false;
}
returntrue;
}
browser_render_process_host.cc文件是理解体系结构很重要的一个文件,其中的Init方法是关键。可以在该方法中
设置断点,通过查看调用栈可以了解详细的调用路径。在Init中,先构造一个资源消息过滤器(ResourceMessageFilter)
然后构造和Renderer进程通信的IPC通道,并将资源消息过滤器安装在该通道上。最后通过构造一个ChildProcessLauncher
对象来启动Renderer进程,注意ChildProcessLauncher构造函数传递了执行文件路径,命令行等参数。
下面看ChildProcessLauncher构造函数的实现,构造函数最终会调用到child_process_launcher.cc中的
Context嵌套类的Launch函数:
voidLaunch(
#ifdefined(OS_WIN)
constFilePath&exposed_dir,
#elifdefined(OS_POSIX)
constbase::environment_vector&environ,
intipcfd,
#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,
#ifdefined(OS_WIN)
exposed_dir,
#elifdefined(POSIX)
environ,
ipcfd,
#endif
cmd_line));
}
这里的关键是ChromeThread::PostTask()的调用。该函数是chrome中线程调度体系的一个重要助手函数。当一个线程需要向另外一个线程投递一个任务时(在另外一个线程上运行指定的代码)调用该函数很方便,第一个参数是目标线程的标识符(chrome的每种线程都有对应的标识符,通过标识符就可以获取到线程对象)。第三个参数是需要在目标线程上运行的代码。chrome提供了很多灵活的模板方法(可以参考base项目中的task.h实现)来构造第三个参数,第三个参数即可以是一个独立的函数入口,又可以是某个类对象的公有方法,且该方法可以有任意的参数数目和数据类型。
在这里,主UI线程把启动Render进程的任务投递给独立的“进程启动者线程”(PROCESS_LAUNCHER)来完成,具体的代码是调用Context::LaunchInternal()方法:
voidLaunchInternal(
constFilePath&exposed_dir,
CommandLine*cmd_line)
{
...
scoped_ptr<CommandLine>cmd_line_deleter(cmd_line);
base::ProcessHandlehandle=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“菜单中的“AttachtoProcess”来附加到Render进程(chrome.exe)。
附加后,我们在RenderView::Init中设置断点:
voidRenderView::Init(gfx::NativeViewIdparent_hwnd,
int32opener_id,
constRendererPreferences&renderer_prefs,
SharedRenderViewCounter*counter,
int32routing_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通道最终被分派到voidChildThread::OnMessageReceived(…),通过消息映射,该消息ViewMsg_New由相应的函数来处理,并最终调用到RenderView::Init完成内核初始化。在RenderView::Init中,webview()->initializeMainFrame(this)此句完成了webkit内核的初始化。以后的过程就是主进程和Renderer进程通过IPC通道进行消息交互的协作过程。
ViewMsg_New消息是浏览器主进程在打开新页面的时候发送给Renderer进程的控制消息,其目的是打开一个对应的RenderView对象完成内核初始化,在主进程中与之对应的通信对象是RenderViewHost。后续我会详细解释。

总结

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