见解有限,如有描述不当之处,还请大家指出,如有错误,会及时修正。(Doge保命)
这是本系列的第一篇文章,主要讲浏览器的架构知识。
目的:对浏览器架构有初步的认知。
文章难度由浅入深,大致可以分为四个模式:
首先,我们先来看下 全球浏览器的市场占有率 如下。
可以很明显的看到,Chrome从它横空出世,到现在60%左右的市场份额,是什么让它可以如此流行。
The mantra for Chrome was the four S’s: simplicity, speed, security, and stability.
从 How we designed Chrome 10 years ago 一文中提到的Chrome的核心原则是四个S: 简单,速度,安全和稳定 。
打造最快的浏览器
为用户提供最安全的环境
提供弹性和稳定的 Web 应用程序平台
创造复杂的技术,融入简单的用户体验
就这几个方向来看,我们或许可以获取答案。
引自: 浏览器野史 UserAgent列传 , 历史在重演:从KHTML到WebKit,再到Blink
这一部分,我们简单的列一下浏览器和渲染引擎的发展历史
如果对这一部分感兴趣,可以去 浏览器野史 UserAgent列传 了解一下。
这里列一下渲染引擎的家谱
注意:Chromium是浏览器引擎,不是渲染引擎哦~
在渲染引擎的发展中,发生了一些有趣的事情,具体可以查看 历史在重演:从KHTML到WebKit,再到Blink
这一部分历史其实不用记住,需要了解的点就是**KHTML——>Webkit——>Blink,**由于多方的理念差异,导致的渲染引擎的不断进步。
引自: What’s the Diff: Programs, Processes, and Threads 维基百科进程 维基百科线程
进程(process),是指计算机中已运行的程序。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。
线程(thread)是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。
用户下达运行程序的命令后,就会产生进程。进程需要一些资源才能完成工作,且为依序逐一进行,也就是 每个CPU核心任何时间内仅能运行一项进程,不同的进程之间是相互隔离的 。
而进程中的每个线程共享该内存和资源。在单线程进程中,进程包含一个线程。进程和线程是相同的。
在多线程进程中,该进程包含多个线程,以允许同时有多位用户运行同一程序,却不会相冲突。每个线程将拥有自己的栈,但是进程中的所有线程将共享堆。也就是说 同一进程中的不同线程,是可以并行的。
想要详细了解程序,进程和线程的不同点,可以阅读 What’s the Diff: Programs, Processes, and Threads
单进程内容引自: 浏览器工作原理与实践 多进程和服务化内容引自:
单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里,这些模块包含了网络、插件、JavaScript 运行环境、渲染引擎和页面等。单进程浏览器的架构如下图所示:
如此多的功能模块运行在一个进程里,是导致单进程浏览器 不稳定 、 不流畅 和 不安全 的一个主要因素。
问题 1:不稳定
插件**来实现诸如 Web 视频、Web 游戏等各种强大的功能,但是插件是最容易出问题的模块,并且还运行在浏览器进程之中,所以一个插件的意外崩溃会引起整个浏览器的崩溃。
除了插件之外, 渲染引擎模块 也是不稳定的,通常一些复杂的 JavaScript 代码就有可能引起渲染引擎模块的崩溃。和插件一样,渲染引擎的崩溃也会导致整个浏览器的崩溃。
问题 2:不流畅所有页面的渲染模块、JavaScript 执行环境以及插件都是运行在同一个线程中的,这就意味着同一时刻只能有一个模块可以执行。
除了上述 脚本 或者 插件 会让单进程浏览器变卡顿外, 页面的内存泄漏 也是单进程变慢的一个重要原因。通常浏览器的内核都是非常复杂的,运行一个复杂点的页面再关闭页面,会存在内存不能完全回收的情况,这样导致的问题是使用时间越长,内存占用越高,浏览器会变得越慢。
问题 3:不安全
这里依然可以从插件和页面脚本两个方面来解释该原因。
插件可以使用 C/C++ 等代码编写,通过插件可以获取到操作系统的任意资源,当你在页面运行一个插件时也就意味着这个插件能完全操作你的电脑。如果是个恶意插件,那么它就可以释放病毒、窃取你的账号密码,引发安全性问题。
至于页面脚本,它可以通过浏览器的漏洞来获取系统权限,这些脚本获取系统权限之后也可以对你的电脑做一些恶意的事情,同样也会引发安全问题。
现代的多线程架构更加稳定,因为它们 将应用程序置于相互隔离的单独进程中 。一个应用程序中的崩溃通常不会损害其他应用程序或浏览器的完整性,并且每个用户对其他用户数据的访问受到限制。
对浏览器选项卡使用单独的进程,以保护整个应用程序免受渲染引擎中的错误和干扰。Chrome还限制了每个渲染引擎进程对其他进程以及系统其余部分的访问。在某些方面,这个为Web浏览器带来了内存保护和访问控制的好处。
Chrome的服务化示意图,将不同的服务移至多个进程和一个浏览器进程。
由于多进程架构会造成**占用更多的资源以及更加复杂的体系架构,**所以Chrome 正在进行体系架构改变,将浏览器程序的每个部分,作为一项服务运行,从而可以轻松拆分为不同的进程或汇总为同一个进程。(仔细观察上图不同进程合并到浏览器进程的过程)
具体的想法如下:
这个进程模型的方案会在多架构架构详解中的 进程模型 部分进行更加详细的介绍。
此部分参考了以下内容:
还有更多的流程,如: 扩展进程 (Extension Process)和 实用进程 (Utility Process)。如果你想查看 Chrome 中正在运行的进程数,请点击右上角的选项, 菜单图标→选择更多工具→任务管理器 。
这将打开一个窗口,其中包含当前正在运行的进程列表以及它们使用的 CPU/内存信息。
通过简介,我们已经了解了浏览器里面有什么进程,以及各个浏览器进程的作用。
到这里,对于浏览器的了解已经足够进行浏览器渲染的知识储备了。
部分内容引自: Threading and Tasks in Chrome
Chromium多进程体系结构中有两种核心的进程: 浏览器进程和渲染进程。一般来说,只能有一个浏览器进程和多个渲染进程。我将在下面给他们更详细的讨论。其他的进程以及沙箱等内容,会给出大概的介绍,以及参考资料。
首先我们要简单的了解下每个Chrome进程都有
进程和线程的详细内容可以深入看: Threading and Tasks in Chrome
引自: Explore the Magic Behind Google Chrome
本节概念比较多,在看概念之后,再去对照上图来理解,会有更深的感受浏览器进程也简称为浏览器 (这与我们通常所说的浏览器不同,指的是您计算机上允许与互联网的进行信息交互的程序)。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
浏览器进程维护两个线程: main / UI线程 和 I / O线程 。 main / UI线程 主要负责在屏幕上呈现网页。 I / O线程 负责浏览器进程和渲染进程之间的 IPC 通信,并且在此线程中也处理所有网络通信。每个线程运行几个不同的对象。在下面的部分中,我将按照他们所属进程来介绍所有主要对象的的详细信息
RenderProcessHost
浏览器进程可能有多个 RenderProcessHost。每个都以一对一的关系连接到渲染进程。RenderProcessHost 的主要职责是将调度特定于视图的消息发送到 RenderViewHost,并接受并处理其余的非特定于视图的消息。此对象还需要唯一的标识 RenderView 以及 RenderView Id,因为渲染进程可能有多个 RenderView,并且 Id 仅在渲染进程中唯一,而在浏览器进程中是不唯一。
RenderViewHost:
正如我上面提到的,RenderViewHost 从 RenderProcessHost 接收特定于视图的消息。RenderProcessHost 和 RenderViewHost 之间的关系是一对多的,因为渲染进程可能会生成多个 RenderView (比如说.同一选项卡中的网页和弹出窗口) 和 RenderProcessHost 需要将该信息传递给相应的 RenderViewHost。此对象负责导航命令、接收输入事件和绘制网页。
此对象表示包含网页的选项卡。它负责在矩形视图中显示网页。
Browser:
此对象表示浏览器窗口,可能包含多个 web 内容。
Channel:
此对象定义了跨管道通信的方法。它的主要职责是通过 IPC 与渲染进程进行通信。
ResourceDispatcherHost:
此对象负责向 Internet 发送网络请求。它充当互联网的大门。如果渲染进程要发送请求,则需要先将此请求发送到 RenderProcessHost。然后 RenderProcessHost 将此请求转发给 ResourceDispatcherHost 以完成发送请求。
详细内容可以看: process-models 引自:浏览器多进程架构
首先,我们先介绍一下 site和****site-instance 的概念。
Chromium 提供了四种进程模式,他们影响了浏览器分配页面给渲染进程的行为,比如采用某个模式况会给 tab 分配新进程,而采用另外一个模式则不会,下面是四种模式的介绍, Chrome 默认采用第一个模式。
因为这种模型兼顾了性能与易用性,是一个比较中庸通用的模式
内容引自: Explore the Magic Behind Google Chrome
本节概念比较多,在看概念之后,再去对照上图来理解,会有更深的感受渲染进程也简称为渲染器。这个进程维护两个线程: 主线程和渲染线程,并负责构建一个网页。
渲染进程维护两个线程: 主线程和渲染线程。主线程只有一个对象 RenderProcess,其唯一职责是促进 RenderView 所在的渲染线程与浏览器进程的 I/O 线程之间的跨线程通信。另一方面,渲染线程负责生成网页。在下面的部分中,我将说明渲染进程中的主要对象。
RenderProcess:
正如我上面提到的,RenderProcess 主要用于促进跨线程通信。
资源调度程序: 有时网页需要向服务器发出获取内容的请求。渲染进程无法访问 internet。它必须依靠 ResourceDispatcher 通过 IPC 向浏览器转发请求。
Webkit: 这是Chromium中使用的渲染引擎。它用于构建 DOM 和布局网页。Webkit 由两个主要组件组成: 包含核心布局功能的 WebCore 和 JavaScript 解释器 V8 所在的 JavaScriptCore。
内容引自: Explore the Magic Behind Google Chrome 更多的关于进程通信的信息,请前往: Inter-process Communication (IPC)
本节概念比较多,在看概念之后,再去对照上图来理解,会有更深的感受
到目前为止,我已经讨论了Chromium 架构中的一些主要组件。在本节中,我将简要介绍系统中的主要连接器或通信途径。
浏览器进程和多个渲染进程之间的通信是通过 IPC 进行的,该通信基于为每个渲染进程创建的异步命名管道。管道充当在浏览器中的通道和渲染器中的 RenderProcess 之间传递消息的通信桥梁。因为通信通常是双向的,所以提供的和需要的接口都可以是 IPC 侦听器接口。这种通信是通过隐式调用来实现的,因为消息被发送到系统,系统将在另一个组件上调用适当的函数。
来自和到渲染进程的消息没有在浏览器进程的主/用户界面线程上直接通信。相反,它将工作委托给位于 I/O 线程上的 ChannelProxy。这是因为通道不是线程安全的。ChannelProxy 在发送和接收消息时为主/UI 线程和 I/O 线程之间的跨线程通信提供安全。这个 ChannelProxy 还有一个消息过滤器,它拦截任何资源请求 (例如网络请求),并将它们直接转发给 ResourceDispatcherHost。使用此 ChannelProxy,所有性能关键消息都完全在 I/O 线程上处理,因此不会对浏览器进程的主/UI 线程产生任何负面影响。
本地程序调用可能是Chromium 多进程体系结构中最常用的连接器。许多组件通过 LPC 进行通信。例如,浏览器存储对许多 web 内容的引用,这些内容存储对许多 RenderViewHost 的引用,当在浏览器中调用一个函数时,它通过在其上调用函数来向所有引用的组件发送消息。提供的和必需的接口通常是特定调用函数上的参数和参数。
引自: multi-process-architecture
_
每个渲染进程都有一个全局 RenderProcess 对象,该对象管理与父浏览器进程的通信并维护全局状态。浏览器为每个渲染进程维护相应的 RenderProcessHost ,该渲染进程程管理渲染器的浏览器状态和通信。浏览器和渲染器使用 Chromium 的 IPC 系统进行通信。
每个渲染进程都有一个或多个由 RenderProcess 管理的 RenderView 对象,这些对象与内容选项卡相对应。相应的 RenderProcessHost 维护与渲染器中的每个视图相对应的 RenderViewHost。每个视图都有一个视图 ID,用于区分同一渲染器中的多个视图。这些 ID 在一个渲染器中是唯一的,但在浏览器中不是唯一的,因此识别视图需要一个 RenderProcessHost 和一个视图 ID。从浏览器到特定内容选项卡的通信是通过这些 RenderViewHost 对象来完成的,这些对象知道如何通过其 RenderProcessHost 将消息发送到 RenderProcess 并发送到 RenderView。
内容引自: displaying-a-web-page-in-chrome
信息流照上图来理解,会有更深的感受设置光标是一个渲染器发往浏览器的典型消息的例子。
在渲染器端,以下是发生的事情:
然后浏览器获得了控制权:
发送一个鼠标点击是一个经典的浏览器到渲染器的例子。
当浏览器进程的main/ UI线程收到鼠标单击事件时,它将发送到RenderViewHost,后者又告诉RenderProcessHost将消息发送到ChannelProxy。然后,ChannelProxy将消息代理到浏览器进程的I / O线程,并通过IPC管道将其发送到呈现进程。
在渲染过程中,该消息由RenderView接收,然后RenderView将该消息传递给WebKit。最后,WebKit JavaScriptCore将触发适当的回调函数来处理此鼠标单击事件。
引自: Sandbox
安全是Chromium最重要的目标之一。安全的关键在于理解下面这点:在我们完整地理解了系统在所有可能的输入组合下表现出的行为之后,我们才能够真的保证系统安全。对于像Chromium这样庞大而多样化的代码库,推理它的各个部分可能的行为的组合几乎是不可能的。沙箱的目标是提供这样一种保证:不论输入什么,它最终能做什么或不能做什么。
沙箱利用操作系统提供的安全性,允许不能对计算机做出持久性改变或者访问持续变化的信息的代码的执行。沙箱提供的架构和具体保证依赖于操作系统。
引自: Plugin Architecture
插件是浏览器不稳定的主要来源。插件也会在渲染器没有实际运行时,让进程沙箱化。因为进程是第三方编写的,我们无法控制他们对操作系统的访问。解决方案是,让插件在各自独立的进程中运行。
这个图片展示了整个系统,有浏览器和两个渲染进程,它们都与一个共享的进程外Flash进程交流。总共有三个插件实例。注意这个图表有一部分是过期的,WebPluginStub已经合并到WebPluginDelegateProxy中了。
引用: Network Stack
网络堆栈是主要用于资源获取的主要是单线程的跨平台库。
其主要接口是URLRequest和URLRequestContext。
URLRequest如其名称所示,代表对 URL 的请求。
URLRequestContext包含完成URL请求所需的所有关联上下文,例如 cookie ,主机解析器,代理解析器, 缓存 等。
许多 URLRequest 对象可能共享同一 URLRequestContext。
这部分内容会在接下来的文章有关网络部分去详细描述。引自:
这部分内容会在接下来的文章有关渲染部分去详细描述。推荐看一下 像素的生命 ,通俗易懂。
引自:
网站隔离是Chrome中的一项安全功能,可针对某些类型的安全错误提供额外的保护。这使不可信的网站更难以访问或窃取您在其他网站上的帐户中的信息。
由于强制执行“同源策略”的代码,网站通常无法在浏览器内访问彼此的数据。
站点隔离提供了第二道防线,可以降低此类攻击成功的可能性。它可确保始终将来自不同网站的页面置于不同的流程中,每个流程都在沙箱中运行,以限制流程的执行范围。它还可以阻止该过程从其他站点接收某些类型的敏感数据。因此,一个恶意网站会发现从其他网站窃取数据要困难得多,即使它可以在自己的进程中打破一些规则。
内容引自: Explore the Magic Behind Google Chrome
客户端-服务器架构: 如果我们缩小并从更高层次的角度来看整个多进程架构,它主要由一个浏览器进程和许多渲染进程组成。浏览器进程可以通过 IPC 与渲染进程通信,但是渲染进程之间没有通信。浏览器进程并不真正关心渲染进程的细节,只要它符合某些要求。这些是客户端-服务器体系结构中的核心特征。服务器是浏览器进程; 客户端是渲染进程; 连接器是 IPC 而不是 HTTP。
分层架构: 从如何生成网页的角度来看,多进程架构也可以适合分层架构。该体系结构大致可分为 7 个应用程序层: WebKit 、 RenderView 、 RenderProcess 、 RenderProcessHost 、 RenderViewHost 、 WebContents 和浏览器 (自下而上的顺序)。每一层都有自己的责任。每一层不需要知道,也不依赖于任何更高级别的层。
使用多进程体系结构,每个渲染进程都是沙箱化的,因此限制了对系统资源的访问。如果渲染进程想要发出网络请求,它必须首先与浏览器进程通信。这种沙箱技术带来了更好的安全性,因为即使渲染进程受到损害,它也限制了攻击者可能对操作系统造成的损害。沙箱每个渲染进程也会导致更好的稳定性,因为一个渲染进程(选项卡) 中的崩溃不会对其他渲染进程 (选项卡) 产生任何影响。因为多进程体系结构从高层次来看是客户端-服务器的,它也产生了客户端-服务器体系结构的质量,如可扩展性和可维护性。
多进程体系结构最显著的好处是能够轻松地修改整个软件。这部分是由客户端-服务器体系结构导致的,其中渲染进程代表客户端,浏览器进程代表服务器。更重要的是,事件驱动的系统在很大程度上有助于可修改性。浏览器进程和渲染进程通过 IPC 进行通信。具体来说,该通信中涉及的组件配备了 IPC 通道侦听器接口。这使发送方组件与侦听器组件分离,因为它们不需要知道彼此的存在。发件人组件可以将消息广播到系统。消息总线将把它交给任何对此消息感兴趣的组件。有了这个事件驱动的系统,我们可以很容易地添加或删除渲进程。
内容引用: core-principles
通过上面一系列的介绍,现在我们再次回到核心原则,以下是Chrome的核心原则的说明性介绍,大家应该有了更深的认识了吧。
Chrome的目标是做最快的浏览器,并且通过多种方式来实现。
为用户提供最安全的环境变得越来越重要。
本着 最小特权的原则 运作。在可能的情况下,对代码进行沙箱化,并尽可能严格地限制其与主机系统交互的特权。不受信任的(Web)内容永远不会在特权代码中处理,而是传递给沙箱代码以供使用。
纵深防御。为用户提供多层次的保护。 沙箱 旨在确保恶意软件无法安装到您的系统上并无法从您的系统读取数据,还有其他组件(例如安全浏览)可以帮助您首先防止用户感染恶意软件。
我们努力提供一种更易理解的上下文,在其中提供相关信息(如果有)以及解释,以鼓励用户采取 安全的默认操作。
认为默认情况下用户应该是安全的。 不应该期望用户采取安全措施 ,而应该在确保用户安全的前提下采取行动。会 自动将用户更新为最新版本 ,为与安全相关的功能设置合理的默认值,并且了解用户专注于一项任务而不是安全,并会采取相应的行动。
由于如今浏览器不仅仅是内容查看器,因此Chrome在其中做了很多工作。重要的是它们的行为应更像平台。
将操作系统设计中的常规思想应用于Web浏览器。影响一个应用程序的错误不应使所有应用程序崩溃。考虑到这一点,将各个选项卡分为各自的流程。也对插件执行此操作。
尝试编写尽可能多的自动化测试,以确保产品仍能按预期运行。
使用崩溃统计信息来确定如何确定工程工作的优先级,并避免将特别崩溃的版本运送到更稳定的渠道。
Google致力于提供令人难以置信的复杂技术,但它 具有简单的用户体验 。将竭尽所能来优化用户交互。执行此操作的最主要方法是消除浏览器本身的干扰,并让页面内容说明一切。“内容而不是Chrome”是我们的口头禅。
从安装到选件,倾向于避免产品各个方面的复杂配置步骤。确保软件保持最新状态,因此您无需担心运行最新,最安全的Chrome版本。
尽量不要用他们不准备回答的问题来打断用户,或者使用他们不需要的笨拙的模式对话框来打扰他们。
希望浏览器看起来像是您意志的自然延伸。它应该感觉流畅和令人愉快。这是关于使您获得所需的信息,而不是驱动软件。
由于Chrominu的项目很大,如果要通读的话可能会陷入某些细节中出不来。 这里建议在有需要的情况下去阅读 。这里的难度不是因为这个文档难理解,主要是因为项目很大,资料很多,人很茫然。
作为这一系列的第一篇,从浏览器市场占有率开始,进而介绍了浏览器历史,浏览器架构已经浏览器中不同组件的含义以及关系。
看完之后如果对浏览器架构有了初步的认识,知道浏览器有几种进程,以及它们分别有什么作用,就达到了本文的目的了。如果想要有更加深入的理解,本文也提供了进阶的方向。
下一章就正式进入从URL到渲染完成的过程了。各位大佬准备好上车了吗?
如果觉得还不错的话,可以点赞,加收藏,来关注这一系列文章的输出。欢迎大家监督(催更)。
再次感谢
这里可以不用看,是我的一些感受,没有什么营养。
在准备这一篇的之前,我的认知范围是在"普通模式"的范围,是一个 小圈 。
但是如果想要更好的表达出来这个知识点,就要去查资料,去扩散。
查资料的过程开始是明确的,在不断的扩散过程中,注意力被慢慢的分散了,就很容易进去一个知识点去钻牛角尖。整个人开始茫然,需要了解的东西好多,好像没有尽头。甚至想把源码看完,再去梳理。简直是想屁吃。
后来,慢慢的想通了,不断的搜索过程中,我们得到了一个个零散的知识点,这些东西就像在一个 大圈里的豆子 一样,没有规律的散落。就算现在会了,不过也会忘得很快。
然后就开始有意识的缩圈,让它到一个自己可以完全掌控的情况。这时是一个 中圈 ,在这个圈子了,我的逻辑可以自洽,这里的知识点我可以描述清楚。然后再去不断的精炼,然后精炼之后的东西,就是我真正得到的东西。
最后的中圈才是自己真正沉淀下来的东西。
并不是要给自己设限,而是先把握住自己可以把握的,再继续前行。
也许写的不大好,不过开始了,就走下去。还是那句话
不怕真理无穷,进一寸有一寸的欢喜