Chromium内核原理之blink内核工作解密

《Chromium内核原理之blink内核工作解密》
《Chromium内核原理之多进程架构》
《Chromium内核原理之进程间通信(IPC)》
《Chromium内核原理之网络栈》
《Chromium内核原理之网络栈HTTP Cache》
《Chromium内核原理之Preconnect》
《Chromium内核原理之Prerender》
《Chromium内核原理之cronet独立化》

概要
1.Blink做了什么
2.进程/线程架构

2.1 进程
2.2 线程
2.3 Blink初始化

3.目录结构

3.1 内容公共API和Blink公共API
3.2 目录结构和依赖项
3.3 WTF

4.内存管理
5.任务调度
6.Page, Frame, Document, DOMWindow等

6.1 概念
6.2 Out-of-Process iframes (OOPIF)
6.3 分离的 Frame/ Document

7.Web IDL绑定
8.V8和Blink

8.1 Isolate, Context, World
8.2 V8 API
8.3 V8 wrappers

9.渲染管道

概要:

在Blink上工作并不容易。对于新的Blink开发人员来说并不容易,因为为了实现非常快速的渲染引擎,已经引入了许多特定于Blink的概念和编码约定。即使是经验丰富的Blink开发人员也不容易,因为Blink非常庞大,对性能,内存和安全性极为敏感。

1.Blink做了什么

Blink是Web平台的渲染引擎。粗略地说,Blink实现了在浏览器选项卡中呈现内容的所有内容:

  • 实现Web平台的规范(例如,HTML标准),包括DOM,CSS和Web IDL
  • 嵌入V8并运行JavaScript
  • 从底层网络堆栈请求资源
  • 构建DOM树
  • 计算样式和布局
  • 嵌入Chrome Compositor并绘制图形

Blink通过内容公共API嵌入许多客户,如Chromium,Android WebView和Opera。


Chromium内核原理之blink内核工作解密_第1张图片
blink_content_arch.jpg

从代码库的角度来看,“Blink”通常表示// third_party / blink /。从项目角度来看,“Blink”通常表示实现Web平台功能的项目。实现Web平台功能的代码跨度为// third_party / blink /,// content / renderer /,// content / browser /和其他位置。

2.进程/线程架构

2.1 进程

Chromium具有多进程架构。 Chromium有一个浏览器进程和N个沙盒渲染器进程。 Blink在渲染器进程中运行。

创建了多少个渲染器进程?出于安全原因,在跨站点文档之间隔离内存地址区域很重要(这称为站点隔离)。从概念上讲,每个渲染器进程应该专用于最多一个站点。然而,实际上,当用户打开太多选项卡或设备没有足够的RAM时,将每个渲染器进程限制为单个站点有时太重了。然后,渲染器进程可以由从不同站点加载的多个iframe或选项卡共享。这意味着一个选项卡中的iframe可能由不同的渲染器进程托管,并且不同选项卡中的iframe可能由同一渲染器进程托管。渲染器进程,iframe和制表符之间没有1:1映射。

假定渲染器进程在沙箱中运行,则Blink需要请求浏览器进程分派系统调用(例如,文件访问,播放音频)和访问用户简档数据(例如,cookie,密码)。这个浏览器渲染器进程通信由Mojo实现。 (注意:过去我们使用的是Chromium IPC,但仍有一些地方正在使用它。但是,它已被弃用,并在引擎盖下使用Mojo。)在Chromium方面,Servicification正在进行并将浏览器进程抽象为一组“服务。从Blink的角度来看,Blink可以使用Mojo与服务和浏览器进程进行交互。


Chromium内核原理之blink内核工作解密_第2张图片
blink_process.jpg
2.2 线程

在渲染器进程中创建了多少个线程?

Blink有一个主线程,N个工作线程和几个内部线程。

几乎所有重要的事情都发生在主线程上。所有JavaScript(工作者除外),DOM,CSS,样式和布局计算都在主线程上运行。假设大多数是单线程架构,Blink经过高度优化以最大化主线程的性能。

Blink可能会创建多个工作线程来运行Web Workers,ServiceWorker和Worklet。

Blink和V8可能会创建几个内部线程来处理webaudio,数据库,GC等。

对于跨线程通信,您必须使用PostTask API使用消息传递。不鼓励共享内存编程,除非出于性能原因需要使用它的几个地方。这就是为什么你在Blink代码库中看不到很多MutexLock的原因。


Chromium内核原理之blink内核工作解密_第3张图片
thread_arc.jpg
2.3 Blink初始化

Blink由BlinkInitializer :: Initialize()初始化。必须在执行任何Blink代码之前调用此方法。

另一方面,Blink永远不会完成;即渲染器过程被强制退出而不被清理。一个原因是表现。另一个原因是,通常很难以优雅的顺序清理渲染器过程中的所有内容(并且不值得付出努力)。

3.目录结构

3.1 内容公共API和Blink公共API

内容公共API是API层,它使嵌入器能够嵌入呈现引擎。必须仔细维护内容公共API,因为它们暴露给嵌入器。

Blink公共API是API层,它公开了从// third_party / blink /到Chromium的功能。此API层只是从WebKit继承的历史工件。在WebKit时代,Chromium和Safari共享了WebKit的实现,因此需要API层来将功能从WebKit公开到Chromium和Safari。现在Chromium是// third_party / blink /的唯一嵌入器,API层没有意义。我们通过将网络平台代码从Chromium移动到Blink(该项目称为Onion Soup)来积极减少Blink公共API的数量。


Chromium内核原理之blink内核工作解密_第4张图片
public_api.jpg
3.2 目录结构和依赖项

/ third_party / blink /具有以下目录。有关这些目录的更详细定义,请参阅此文档:https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/README.md

  • platform/
    Blink的低级功能集合,由单片内核/。例如,几何和图形工具。
  • core/ 和 modules/
    实现了规范中定义的所有Web平台功能。核心/实现与DOM紧密结合的功能。模块/实现更多自包含功能。例如webaudio,indexeddb。
  • bindings/core/ 和 bindings/modules/
    概念上绑定/ core /是core /的一部分,而bindings / modules /是modules /的一部分。大量使用V8 API的文件放在绑定/ {core,modules}中。
  • controller/
    一组使用核心/和模块/的高级库。例如,devtools前端。

依赖关系按以下顺序:
Chromium => controller/ => modules/ and bindings/modules/ => core/ and bindings/core/ => platform/ => low-level primitives such as //base, //v8 and //cc

Blink仔细维护暴露于// third_party / blink /的低级原语列表。

3.3 WTF

WTF是一个“Blink特定的基础”库,位于platform / wtf /。我们试图尽可能地在Chromium和Blink之间统一编码原语,因此WTF应该很小。这个库是必需的,因为有许多类型,容器和宏真正需要针对Blink的工作负载和Oilpan(Blink GC)进行优化。如果在WTF中定义了类型,则Blink必须使用WTF类型而不是// base或std库中定义的类型。最流行的是矢量,哈希集,哈希映射和字符串。 Blink应该使用WTF :: Vector,WTF :: HashSet,WTF :: HashMap,WTF :: String和WTF :: AtomicString而不是std :: vector,std :: * set,std :: * map和std :: string 。

4.内存管理

就Blink而言,您需要关心三个内存分配器:

  • PartitionAlloc
  • Oilpan
  • malloc/free 或者 new/delete

您可以使用USING_FAST_MALLOC()在PartitionAlloc的堆上分配对象:

class SomeObject {
  USING_FAST_MALLOC(SomeObject);
  static std::unique_ptr Create() {
    return std::make_unique();  // Allocated on PartitionAlloc's heap.
  }
};

PartitionAlloc分配的对象的生命周期应由scoped_refptr <>或std :: unique_ptr <>管理。强烈建议不要手动管理生命周期。 Blink禁止手动删除。

您可以使用GarbageCollected在Oilpan的堆上分配一个对象:

class SomeObject : public GarbageCollected {
  static SomeObject* Create() {
    return new SomeObject;  // Allocated on Oilpan's heap.
  }
};

Oilpan分配的对象的生命周期由垃圾收集自动管理。你必须使用特殊的指针(例如,Member <>,Persistent <>)来保存Oilpan堆上的对象。请参阅此API参考以熟悉有关Oilpan的编程限制。最重要的限制是不允许在Oilpan的对象的析构函数中触摸任何其他Oilpan的对象(因为无法保证销毁顺序)。

如果既不使用USING_FAST_MALLOC()也不使用GarbageCollected,则在系统malloc的堆上分配对象。在Blink中强烈建议不要这样做。所有Blink对象应由PartitionAlloc或Oilpan分配,如下所示:

  • 默认使用 Oilpan
  • 仅当1)对象的生命周期非常清楚且std :: unique_ptr <>或scoped_refptr <>足够时才使用PartitionAlloc,2)在Oilpan上分配对象引入了很多复杂性或者3)在Oilpan上分配对象引入了垃圾收集运行时有很多不必要的压力。

无论您使用的是PartitionAlloc还是Oilpan,您都必须非常小心,不要创建悬空指针(注意:强烈建议不要使用原始指针)或内存泄漏。

5.任务调度

为了提高渲染引擎的响应能力,Blink中的任务应尽可能异步执行。不鼓励同步IPC / Mojo和可能需要几毫秒的任何其他操作。

渲染器进程中的所有任务都应该使用适当的任务类型发布到Blink Scheduler,如下所示:

// Post a task to frame's scheduler with a task type of kNetworking
frame->GetTaskRunner(TaskType::kNetworking)->PostTask(..., WTF::Bind(&Function));

Blink Scheduler维护多个任务队列,并巧妙地确定任务的优先级,以最大限度地提高用户感知的性能。指定正确的任务类型以使Blink Scheduler正确而巧妙地安排任务非常重要。

6.Page, Frame, Document, DOMWindow等

6.1 概念

Page,Frame,Document,ExecutionContext和DOMWindow是以下概念:

  • Page:页面对应于选项卡的概念(如果未启用下面解释的OOPIF)。每个渲染器进程可能包含多个选项卡。
  • Frame:帧对应于帧的概念(主帧或iframe)。每个页面可能包含一个或多个以树形层次结构排列的框架。
  • DOMWindow对应于JavaScript中的窗口对象。每个Frame都有一个DOMWindow。
  • Document对应于JavaScript中的window.document对象。每个框架都有一个文档。
  • ExecutionContext是一个抽象Document(用于主线程)和WorkerGlobalScope(用于工作线程)的概念。

渲染器过程:Page = 1:N。
Page : Frame = 1 : M.

Frame : DOMWindow : Document (or ExecutionContext) = 1:1:1在任何时间点,但映射可能会随时间而变化。例如,请考虑以下代码:

iframe.contentWindow.location.href = "https://example.com";

在这种情况下,将为https://example.com创建新的DOMWindow和新文档。但是,帧可以重复使用。

6.2 Out-of-Process iframes (OOPIF)

站点隔离使事情更安全,但更复杂。 :)站点隔离的想法是为每个站点创建一个渲染器进程。 (网站是网页的可注册域名+ 1标签及其网址方案。例如,https://mail.example.com和https://chat.example.com位于同一网站,但https:// noodles.com和https://pumpkins.com不是。)如果一个页面包含一个跨站点iframe,那么该页面可能由两个渲染器进程托管。请考虑以下页面:





主框架和