理解浏览器工作原理之前,需要先对进程与线程有一个大致的了解。首先,对进程与线程的概念和实例场景进行一个简单的概括和介绍。
进程与线程
- 进程(Process):
- 狭义: 计算机中正在运行的一个程序实例。例如一个运行的浏览器。
- 广义: 一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
- 简单来说,进程就是我们使用计算机时,打开的每一个运用程序,可以理解为一个公司,特点就是它们都是高度独立的,就好比你正在运行的浏览器突然崩溃了,但是不会影响到你正在运行的QQ。
- 线程(Thread):
- 狭义: 一个进程中负责某一项功能的代码执行器。例如浏览器中负责network的线程负责解析你输入的URL在得到服务器响应后返回的相关信息。
- 广义: 进程中一个单一顺序的控制流与实际运作单位,是独立调度和分派的基本单位,一个进程中可以并发多个线程,这些线程共享同一进程中的全部系统资源,如虚拟地址空间,文件描述符等。
- 简单来说,线程就是我们使用的运用程序中负责帮我们实现具体功能的一个小模块,可以理解为一个公司的员工,特点是它们之间可以互相通信,相对而言是独立的,一个线程崩溃了,可能会影响其他线程,也可能不影响其他线程,但是所属的进程崩溃了,所有线程都会崩溃,就好比公司都没了,这个公司的员工自然也就不存在了。
在一些运用程序中,为了满足某些功能的需要,细分要执行的任务,启动的进程往往会创建另外的新的进程来处理其他任务,但是这些新创建出来的进程拥有全新的独立的内存空间,不能与原来的进程进行内存共享,如果这些进程之间需要通信,可以通过IPC机制来实现。
很多应用程序都会采取这种多进程的方式来工作,因为进程和进程之间是互相独立的它们互不影响,也就是说,当其中一个进程挂掉了之后,不会影响到其他进程的执行,只需要重启挂掉的进程就可以恢复运行。
Chrome的多进程架构
在Chrome中,主要的进程有4个:
- 浏览器进程(Browser Process): 负责浏览器的页面的前进、后退、地址栏、书签栏和处理浏览器的一些不可见的底层操作,例如网络请求和文件访问。
- 渲染进程(Render Process): 负责一个页面内的显示相关工作,也称渲染引擎。
- 插件进程(Plugin Process): 负责控制网页使用到的一些插件。
- GPU进程(GPU Process): 负责处理整个应用程序的GPU任务。
4个进程之间的关系,工作之间互相配合的过程是什么呢?下面用打开一个网页来实际演示一下。
首选,当我们要浏览一个网页时,我们需要在浏览器的地址栏输入URL,当我们输入完成敲下回车之后, Browser Process 会向这个URL发送网络请求,请求成功后获取到服务器返回的HTML内容,然后将HTML交给 Render Process 处理, Render Process 对内容进行解析,解析时当遇到需要请求网络的资源(例如图片、样式文件、js文件等)时,又返回来交给 Browser Process 进行加载处理,与此同时,当有插件代码需要执行时,会开启 Plugin Process 来处理。解析完成后, Render Process 通过计算得到图像帧,并将这些图像帧交给 GPU Process , GPU Process 再将其转化为图像显示在屏幕上。
多进程架构的优势
高容错性。 当下的WEB应用中,HTML、CSS、JavaScript日益复杂,当渲染引擎在处理这些代码时经常会出现Bug,而有些Bug直接会导致渲染引擎直接崩溃,多进程架构使得每一个渲染引擎都运行在各自的进程中,正常情况下,相互之间不会互相影响,这就保证了当某个页面挂了之后,其他页面仍然是正常的,只需要重开挂掉的页面就好了。
高安全性与高沙盒性。 渲染引擎经常会遇到不可信,甚至是恶意的代码,这些代码会利用渲染引擎在你的电脑上植入木马、病毒,针对这一问题,浏览器对不同的进程限制了不同的权限,并为其提供沙盒运行环境,使其更加安全可靠。
高响应速度。 在单进程的架构中,各个任务互相竞争抢夺CPU资源,使得浏览器响应速度变慢,而多进程架构正好规避了这一缺点。
多进程架构优化
上面提到 Render Process 的作用是负责一个Tab内的显示相关的工作,这就意味着,一个Tab,就会有一个 Render Process ,但是这些进程之间的内存无法进行共享,而不同进程的内存常常需要包含相同的内容。
浏览器的进程模式
为了节省内存,Chrome提供了四种进程模式(Process Models),不同的进程模式会对 Tab 进程做不同的处理。
- Process-per-site-instance(default) 同一个 site-instance 使用一个进程
- Process-per-site 同一个 site 使用一个进程
- Process-per-tab 每个 tab 使用一个进程
- Single process 所有 tab 共用一个进程
先把 site 和 site-instance 的定义做一个介绍
- site 指相同的 registered domain name(例如:a.baidu.com 和 b.baidu.com 就可以理解为同一个 site)
- site-instance 指的是一组 connected pages from the same site,这里 connected 的定义是 can obtain references to each other in script code。通俗来说,满足下面两中情况并且打开的新页面和旧页面属于上面定义的同一个 site,就属于同一个 site-instance
再对四种进程模式做一个解释
- 首先是 Single process ,顾名思义,单进程模式,所有 tab 都会使用同一个进程。
- 接下来是Process-per-tab ,每打开一个tab,会新建一个进程。而对于 Process-per-site ,当你打开 a.baidu.com 页面,再打开 b.baidu.com 的页面,这两个页面的tab使用的是共一个进程,因为这两个页面的site相同,而如此一来,如果其中一个tab崩溃了,而另一个tab也会崩溃。 Process-per-site-instance
- 是最重要的,因为这个是 Chrome 默认使用的模式,也就是几乎所有的用户都在用的模式。当你打开一个 tab 访问 a.baidu.com ,然后再打开一个 tab 访问 b.baidu.com,这两个 tab 会使用两个进程。而如果你在 http://a.baidu.com 中,通过JS代码打开了 http://b.baidu.com 页面,这两个 tab 会使用同一个进程。
那么为什么浏览器使用Process-per-site-instance作为默认的进程模式呢?
首先, Process-per-site-instance 兼容了性能与易用性,是一个比较中庸通用的模式。相较于 Process-per-tab ,能够少开很多进程,就意味着更少的内存占用相较于 Process-per-site ,能够更好的隔离相同域名下毫无关联的 tab,更加安全。
网页加载过程
之前我们我们提到,tab 以外的大部分工作由浏览器进程 Browser Process 负责,针对工作的不同, Browser Process 划分出不同的工作线程:
- UI thread: 控制浏览器上的按钮及输入框
- network thread: 处理网络请求,从网上获取数据
- storage thread: 控制文件等的访问
第一步:处理输入
当我们在浏览器的地址栏输入内容按下回车时, UI thread 会判断输入的内容是搜索关键词(search query)还是URL,如果是搜索关键词,跳转至默认搜索引擎对应都搜索URL,如果输入的内容是URL,则开始请求URL。
第二步:开始导航
回车按下后, UI thread 将关键词搜索对应的URL或输入的URL交给网络线程 Network thread ,此时UI线程使Tab前的图标展示为加载中状态,然后网络进程进行一系列诸如DNS寻址,建立TLS连接等操作进行资源请求,如果收到服务器的301重定向响应,它就会告知UI线程进行重定向然后它会再次发起一个新的网络请求。
第三步:读取响应
network thread 接收到服务器的响应后,开始解析HTTP响应报文,然后根据响应头中的Content-Type字段来确定响应主体的媒体类型(MIME Type),如果媒体类型是一个HTML文件,则将响应数据交给渲染进程(renderer process)来进行下一步的工作,如果是 zip 文件或者其它文件,会把相关数据传输给下载管理器。
与此同时,浏览器会进行 Safe Browsing 安全检查,如果域名或者请求内容匹配到已知的恶意站点,network thread 会展示一个警告页。除此之外,网络线程还会做 CORB(Cross Origin Read Blocking)检查来确定那些敏感的跨站数据不会被发送至渲染进程。
第四步:查找渲染进程
各种检查完毕以后, network thread 确信浏览器可以导航到请求网页, network thread 会通知 UI thread 数据已经准备好, UI thread 会查找到一个 renderer process 进行网页的渲染。
浏览器为了对查找渲染进程这一步骤进行优化,考虑到网络请求获取响应需要时间,所以在第二步开始,浏览器已经预先查找和启动了一个渲染进程,如果中间步骤一切顺利,当 network thread 接收到数据时,渲染进程已经准备好了,但是如果遇到重定向,这个准备好的渲染进程也许就不可用了,这个时候会重新启动一个渲染进程。
第五步:提交导航
到了这一步,数据和渲染进程都准备好了, Browser Process 会向 Renderer Process 发送IPC消息来确认导航,此时,浏览器进程将准备好的数据发送给渲染进程,渲染进程接收到数据之后,又发送IPC消息给浏览器进程,告诉浏览器进程导航已经提交了,页面开始加载。
这个时候导航栏会更新,安全指示符更新(地址前面的小锁),访问历史列表(history tab)更新,即可以通过前进后退来切换该页面。
第六步:初始化加载完成
当导航提交完成后,渲染进程开始加载资源及渲染页面(详细内容下文介绍),当页面渲染完成后(页面及内部的iframe都触发了onload事件),会向浏览器进程发送IPC消息,告知浏览器进程,这个时候 UI thread 会停止展示tab中的加载中图标。