一、背景:一个跨平台浏览器为什么要重新造“轮子”?
在浏览器开发生态里,Chromium 是当之无愧的“核心内核”。无论是 Google Chrome、Edge、360、Brave,还是大量国产定制浏览器,它们的技术底座几乎都源自 Chromium。
从表面上看,我们很容易把精力放在渲染引擎(Blink)、进程模型、网络模块、Media Pipeline 等“看起来很硬核”的模块上,而忽略掉内核最底层的基础设施 —— base 库。
事实上,如果没有 base 这一层打底,Chromium 很多模块(包括线程系统、IPC 框架甚至 task 调度模型)根本无法运行。
更重要的是,标准 C++ STL 或 Boost 并不能满足 Chromium 巨型跨平台架构在一致性、性能和可维护性上的要求,这也是它“重新造轮子”的根本原因。
本文将从源码层面出发,带你完整理解 base 库是什么、解决了什么问题、主要模块组成、核心设计理念、实际开发场景,以及它的独特价值,希望可以成为你学习 Chromium 内核体系的一个“起始点”。
二、base 库是什么?
2.1 定位
base 是 Chromium 的最底层、最通用的公共库,它 不依赖任何其他模块 却被其他所有模块依赖(net/content/chrome/gin/v8...)。
它提供的是:
-
通用跨平台工具类(字符串、时间、文件、进程)
-
线程、任务、事件循环
-
内存管理封装(智能指针、引用计数、弱指针)
-
基础容器、安全格式化、日志框架
-
方便浏览器使用的观察者模式 / callback / Bind
换句话说,它相当于一个大型 C++ 工具库,针对浏览器场景做过深度裁剪与优化。
2.2 为什么不是直接用 STL?
| 标准库 / Boost | Chromium 需求 |
|---|---|
| 行为在不同 OS 上实现不同 | 需要跨平台行为绝对一致 |
| 缺乏线程模型 / 消息循环 | 浏览器大量依赖异步任务 |
| debug 模式性能很差(特别是多线程容器) | 大型项目开发期间大量使用 debug 构建 |
| 部分 API 不支持 UTF-8 字符处理 | 浏览器要处理全球语言 |
| 很难对每个细节打补丁 | Chromium 要求所有基础能力可控 |
✅ 所以 base 是一种“巨大工程规模 + 跨平台一致性 + 高性能可控”的折中解决方案
❌ 它不是为了“重复造轮子”而造轮子
三、整体结构全览
从功能层面,base 可以大致划成下面几个子域:
| 功能域 | 典型文件夹 / 类 |
|---|---|
| 智能指针与内存 | memory/(RefCounted、WeakPtr、scoped_refptr) |
| 线程 & 任务调度 | threading/(Thread、ThreadPool、TaskRunner) |
| 消息循环 | message_loop/(MessageLoop、RunLoop) |
| 字符串处理 | strings/(UTF8→UTF16、string_util) |
| 容器 | containers/(flat_map、circular_deque) |
| 时间日期 | time/(Time、TimeDelta、Timer) |
| 文件系统 | files/(File、FilePath、FileUtil) |
| 日志 | logging/(LOG/ CHECK/ DCHECK) |
| JSON | json/(base::Value、JSONReader/Writer) |
| 观察者 | observer_list.h / ObserverListThreadSafe |
| 系统/进程信息 | process/、sys_info/ |
| 数据统计 | metrics/(Histogram、UMA) |
| IPC/SOCKET | sync_socket/ |
下面我们从几个关键子模块深入展开。
四、核心子模块分析
4.1 memory:智能指针、引用计数、WeakPtr
Chromium 拒绝使用裸指针,因此 base 提供了三种常用内存管理手段:
| 类 | 典型用途 |
|---|---|
| RefCounted | 引用计数对象,适用于跨线程共享资源 |
| WeakPtr | 避免异步回调中访问已销毁对象 |
| scoped_refptr | 智能指针封装,对应 RefCounted 对象 |
例如常见写法:
class MyObject : public base::RefCounted<MyObject> { public: void DoSomething(); private: friend class base::RefCounted<MyObject>; ~MyObject() = default; }; // 使用 scoped_refptr<MyObject> obj = new MyObject();
⚠️ 注意:WeakPtr 必须配合
base::WeakPtrFactory使用,且只能在创建它的线程销毁。
4.2 threading + message_loop:线程模型与事件分发
Chromium 并不是简单的 “pthread + sleep”,而是实现了一整套可扩展的异步执行模型,其中最核心的是:
| 模块 | 功能 |
|---|---|
base::Thread | 创建一个有自己 MessageLoop 的线程 |
TaskRunner | 提供任务投递接口(PostTask / PostDelayedTask) |
MessageLoop | 事件循环 + 消息调度 |
ThreadPool | 全局线程池(后台执行任务) |
典型使用:
base::Thread my_thread("MyThread"); my_thread.Start(); scoped_refptr<base::TaskRunner> task_runner = my_thread.task_runner(); task_runner->PostTask(FROM_HERE, base::BindOnce(&Foo));
这套机制贯穿于整个浏览器生命周期(I/O 线程 / UI 线程 / DB 线程 / GPU 线程 都是由 base 启动和管理的)。
4.3 containers:自研轻量容器
Chromium 没有完全摒弃 STL,但对以下场景重新实现容器,以提升性能:
| base 容器 | 优势与场景 |
|---|---|
| flat_map | 基于排序数组实现,查询快,内存连续,适合小规模 KV |
| circular_deque | 环形缓冲区,频繁入队出队性能好 |
| stack_container | 小对象直接放在栈上,避免 heap 分配 |
例如 flat_map 效果如下:
base::flat_map<std::string, int> m = {{"a",1}, {"b",2}};
性能上在 50 个元素以内比 std::map 快很多。
4.4 files & path & sys_info
-
FilePath专门处理不同平台的目录分隔符 -
ReadFileToString()/WriteFile()提供统一接口 -
SysInfo::AmountOfPhysicalMemory()返回系统内存
→ 行为在 Windows / Linux / macOS 上完全一致
4.5 logging 与 CHECK/DCHECK
在 Chromium 项目中你会非常频繁地见到这些宏:
LOG(INFO) << "Something happened"; CHECK(condition); DCHECK(ptr != nullptr);
区别:
| 宏 | 含义 |
|---|---|
| LOG | 始终启用的日志 |
| CHECK | 失败直接崩溃(发布版本也有效) |
| DCHECK | 仅在 Debug 构建中有效 |
五、设计哲学总结
| 关键词 | 表现 |
|---|---|
| 跨平台一致性 | 所有模块对不同 OS 做统一抽象(FilePath / SysInfo / Thread) |
| 性能可控 | 自研容器 / 回收机制 / 减少 STL 波动带来的抖动 |
| 适配浏览器模型 | 专门的 MessageLoop + TaskRunner 支撑多进程异步调度 |
| 长期可维护性 | 统一宏 / 接口风格 / 智能指针规范 |
这也是为什么“base” 被视作浏览器内核的“地基”。越往上的模块越轻,但它必须足够稳固,否则整个架构就会崩。
六、在实际开发中的典型使用场景
| 场景 | base 中对应功能 |
|---|---|
| 跨线程投递任务 | PostTask(FROM_HERE,...) |
| 延迟或重复任务 | base::Timer / base::RepeatingTimer |
| 安全字符串拼接 | base::StringPrintf |
| JSON 配置加载 | base::JSONReader::Read() |
| 系统级信息(CPU/内存) | base::SysInfo |
| 多线程日志调试 | LOG(…) << …; |
| 避免异步回调悬空 | base::WeakPtr + WeakPtrFactory |
七、如何进一步阅读 base 源码(推荐阅读路径)
| 步骤 | 模块 | 目标 |
|---|---|---|
| ① | base/memory | 理解 RefCounted、WeakPtr 实现和引用模型 |
| ② | base/threading | 阅读 Thread/ThreadPool/TaskRunner 工作流程 |
| ③ | base/message_loop | 异步事件循环扩展(MsgPump 、MessageLoop::Run) |
| ④ | base/containers | 感受自研容器设计思路 |
| ⑤ | base/logging | 日志系统实现,理解 CHECK/DCHECK crash 流程 |
| ⑥ | base/task | 宏观理解整个 TaskScheduler 如何在浏览器内分发任务 |
📌 建议结合一个实际页面 WebUI 逻辑,例如 chrome://version,反着追踪它用到的 base API,会更快建立“感知能力”。
八、总结与个人体会
“为什么 Chromium 要封装一套自己的基础库?”
—— 因为它不是普通项目,它需要在 几十年生命周期 + 数十个平台 + 上千万行代码 + 数千开发者并行协作 的体系中保持一致性和可维护性。
base 提供的不是简单的工具集合,而是一组被严格校验过的“架构支点”。
| 核心认识 |
|---|
| ✅ 它支撑了浏览器的线程模型、任务调度、安全调用和设备无关性 |
| ✅ 它保证了跨平台行为完全一致 |
| ✅ 它将复杂体系拆成了可维护、可测试的模块单位 |
| ✅ 它提升了性能,并避免了 STL 在大型工程场景的不可控因素 |
| ✅ 它是进入 Chromium 内核体系的最佳入口 |
如果你想真正理解浏览器内核架构,从 base 开始绝对是一个正确的打开方式。

530

被折叠的 条评论
为什么被折叠?



