全文阅读预计 45 分钟,建议先「收藏」稍后有空再静心阅读
缘起
一直想写一个有既有意义又对自己技术提升有所帮助的专栏,思考很久都没有找到合适又感觉兴趣的题材。写面试八股文吧,在掘金尤如汗牛充栋也不差我一个。写框架教程吧也只不过是搬运工(没有看不起搬运的意思,主要是写不出什么新意)。直到有一天看到这一篇关于 Dart 服务端的应用开发文章(地址在这), 里面记录了用 Dart 进行服务端开发的一些体验与性能测评,其中有一个内存方面的结论让我记忆深刻。
GC is not fine for long-running server-side daemons
大意是说:「Dart 的 GC 机制不适合需要长时间运行的服务端应用」这句话让我思考了一个问题:现代高级语言居然在长时间运行会有内存问题?正是上面这个问题给了我一点启发,我想如果能弄清楚 Dart GC 机制并解释上面这个现象一定是一个有趣且有意义的事。在此之后便开始了 Dart VM 的源码阅读。
刚开始本意只是想读一读 GC 方面的内容,但随着代码的深入阅读发现我不得不了解整个 Dart Runtime 相关的内容,于是我想为什么不干脆写一个专栏来记录这个过程呢。一方面能记录这个过程不至于看过就过了什么也没有记住,另一方面总结知识与大家分享也是一件让人兴奋的事情。所以这个专栏不是课本,没有权威性,没有指导性,你可以当它是我个的人读书笔记,也可以当它是个人理解下的源码注释说明,总之不管这个专栏是什么它都不一定是对的,你应带着批判的眼光来,指出它的问题来让我们共同进步。
废话说了一大堆,接下来开始本专栏的第一篇内容:Dart main 函数启动分析。
与其它语言相同 main 函数是 Dart 语言的运行入口,运行到 Dart main 函数也就意味着 Dart Runtime 完成了运行环境准备工作,弄清楚 Runtime 是怎么从 C++ 运行到 main 函数也就理解了 Dart Runtime 的启动机制。
准备
- 在这个地址下载 Runtime 源码
- VSCode 安装好「C/C++ for Visual Studio Code」 插件
- 安装并配置好 Dart 开发环境
- 了解 Dart Isolate 的使用
入口
为了研究 main 函数的运行流程,先从 Dart 侧开始找突破点,看看 Dart 侧都做了哪些准备工作。为加快源码查找速度,这里我在 main 函数内打断点看调用栈的方式。
注:为减少干扰因素所有示例都使用 Dart Commond Line App 而不是 Flutter App。
从上面的图片中的调用栈分析可以得出一个简单的的结论:main 函数并不是由 Runtime 直接调用,而是通过消息机制来触发 (根据调用栈中 ReceivePort
、handleMessage
关键词来推断,另外看到 ReceivePort
你应该知道它与 isolate 的关系 ),先记住这个结论,后面会得到证明。先看 _delayEntrypointInvocation
函数与 _handleMessage
的内部实现:
dart
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L286
void _delayEntrypointInvocation(Function entryPoint, List<String>? args,
Object? message, bool allowZeroOneOrTwoArgs) {
// 创建 RawReceivePort 实例
final port = RawReceivePort();
port.handler = (_) {
port.close();
if (allowZeroOneOrTwoArgs) {
if (entryPoint is _BinaryFunction) {
(entryPoint as Function)(args, message);
} else if (entryPoint is _UnaryFunction) {
(entryPoint as Function)(args);
} else {
entryPoint(); // 触发 main 函数调用,也是当前函数第一个参数
}
} else {
entryPoint(message);
}
};
// 发送消息
port.sendPort.send(null);
}
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L176
@pragma("vm:entry-point", "call")
static _handleMessage(int id, var message) {
// 用 id 为 key 取出 _portMap 对象保存的 _handler 回调
final Function? handler = _portMap[id]?._handler;
if (handler == null) {
return null;
}
handler(message); // 触发上面注册的 handle 调用
_runPendingImmediateCallback();
return handler;
}
忽略其它不重要的细节,在 _delayEntrypointInvocation
函数中创建了一个 RawReceivePort
设置了一个 handler
回调,然后立即给 sendPort
发送了一条「空」消息。根据调用栈可以清楚看到 main
函数是在 handler
回调中触发,这里可以猜测给 sendPort
发送消息(调用send函数)之后触发了 handler
回调。handler
中的 entryPoint
函数来自于当前函数的第一个参数,那 _delayEntrypointInvocation
函数又是哪里被调用的呢?全局搜索一下,发现只有两处有效调用,就在当前函数的上面。
dart
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L262
@pragma("vm:entry-point", "call")
void _startMainIsolate(Function entryPoint, List<String>? args) {
_delayEntrypointInvocation(entryPoint, args, null, true);
}
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L283
@pragma("vm:entry-point", "call")
void _startIsolate(
Function entryPoint, List<String>? args, Object? message, bool isSpawnUri) {
_delayEntrypointInvocation(entryPoint, args, message, isSpawnUri);
}
可以看到 _startMainIsolate/_startIsolate
函数直接调用了 _delayEntrypointInvocation
,并且两个函数是都是顶层函数并被 @pragma("vm:entry-point", "call")
被标记,说明这个函数有可能是从 Runtime 直接调过来的。间接验证一下,搜一下 Dart 侧还有没有其它地方调用 _startMainIsolate/_startIsolate
,发现没有直接调用这两个函数的 Dart 代码,唯一的调用是下面这段返回函数签名的代码并且这面的代码在 Dart 侧已搜不到不任何调用,到这里 Dart 的调用流程线索就断了。
dart
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L271
@pragma("vm:entry-point", "call")
Function _getStartMainIsolateFunction() {
return _startMainIsolate;
}
由此可以推断 Runtime 是用上面的代码获取 Dart 侧 _startMainIsolate
函数地址,然后再调用,调用时将 main
函数地址当成第一个参数传了进来。回顾一下前面提到的的大致流程,示意图如下:
上面的流程还遗留了两个问题:
main
函数是怎么传到_startMainIsolate
的第一个参数的 ?RawReceivePort
与 Runtime 建立联系了吗?send 之后为什么会调用到_handleMessage
?
先来看第二个问题,毕竟 RawReceivePort
的实现咱还没有看,回到 _delayEntryPointInvocation
函数内 RawReceivePort
创建处:
dart
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L286
void _delayEntrypointInvocation(Function entryPoint, List<String>? args,
Object? message, bool allowZeroOneOrTwoArgs) {
final port = RawReceivePort();
port.handler = (_) {
// ......
};
port.sendPort.send(null);
}
上面的代码中 port
实例是 RawReceivePort
构造函数创建,实际返回的是 _RawReceivePort
实例(抽象类中用工厂构造函数返回实际实例,对应工厂设计模)。
dart
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L55
@patch
class RawReceivePort {
@patch
factory RawReceivePort([Function? handler, String debugName = '']) {
// 实际返回的是 _RawReceivePort 类型
_RawReceivePort result = new _RawReceivePort(debugName);
result.handler = handler;
return result;
}
}
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L129
@pragma("vm:entry-point")
final class _RawReceivePort implements RawReceivePort {
// _RawReceivePort 工厂构造函数
factory _RawReceivePort(String debugName) {
final port = _RawReceivePort._(debugName);
// 将创建的 _RawReceivePort 实例注册到全局 map 内
_portMap[port._get_id()] = port;
return port;
}
@pragma("vm:external-name", "RawReceivePort_factory")
external factory _RawReceivePort._(String debugName); // 与 Runtime 关联
@pragma("vm:external-name", "RawReceivePort_getSendPort")
external SendPort get sendPort; // 与 Runtime 关联
@pragma("vm:external-name", "RawReceivePort_get_id")
external int _get_id(); // 与 Runtime 关联
void set handler(Function? value) {
_handler = value;
}
// 静态 Map 实例,用来保存 _RawReceivePort 实例
static final _portMap = <int, _RawReceivePort>{};
}
上面的代码中 _RawReceivePort
的构造函数内将当前实例注册到了静态 _portMap
内,key 是从 _get_id()
获取, 这个值最终又是从 Runtime 那里获取。
这里便与 Dart 侧的 _RawReceivePort._handleMessage
静态函数对应起来了,可以回过来再看上面 static _handleMessage(int id, var message)
内部实现源码(往上翻一屏)。通过参数 id
( 此 id
与 _get_id()
返回值对应)为 key 取出对应的 _RawReceivePort
实例,然后再调用实例内保存的 _handler
回调,最终调用到 _delayEntrypointInvocation
内的 entryPoint
函数也就是 main
函数内。
最终 RawReceivePort
与 Runtime 的落脚点上面四个标记了 @pragma 方法上,通过这 4 个方法实现了 Dart 侧 sendPort.send
函数到 _handleMessage
回调之间的调用关系。回顾上面 main
函数堆栈分析流程,所有的源头最终都走到了 Runtime 相关的函数里去,他们是:
dart
// 1
Function _getStartMainIsolateFunction()
// 2
factory *RawReceivePort.*(String debugName);
// 3
_get_id();
// 4
SendPort get sendPort;
只需要搞清楚这上面 4 个函数与 Runtime 之间的关系便可知 main
函数的启动流程,同时这 4 个函数也将是探索 Runtime 的线索。
初探
上面分析完 Dart 侧的流程,已经知道 Dart 侧已经给不了更多关于启动流程的信息,我们需要正式进入 C++ 的世界。那从哪里开始突破呢?可以看到 _RawReceivePort
的构造函数有编译标记 @pragma
,指定了编译后 C++ 的调用函数名。你可能会问 why ?下面是 ChatGPT 给我的回答:
@pragma("vm:external-name", "RawReceivePort_factory")
的注释通常用于 Dart 语言中与 Dart FFI(Foreign Function Interface)和 Dart VM 之间的交互。在这个具体的例子中,它指示 Dart 编译器在生成机器码时,将 Dart 函数RawReceivePort.factory
的外部名称设置为 "RawReceivePort_factory"。
RawReceivePort.factory
是 Dart 语言中用于创建RawReceivePort
实例的工厂方法。通过将外部名称指定为 "RawReceivePort_factory",可以在 Dart FFI 或其他跨语言调用的上下文中使用这个名称来引用和调用 Dart 中的RawReceivePort.factory
方法。这种用法通常出现在与本地代码(如 C 语言)进行集成的情况下。在这些情况下,外部名称的正确设置是确保 Dart 代码能够与底层本地库进行正确交互的关键。
需要注意的是,这些
@pragma
注释是 Dart VM 特有的,并且可能与 Dart 编译器和运行时的实现相关。在 Dart FFI 中,通过正确设置外部名称,可以实现 Dart 代码与其他语言的无缝集成。
创建 _RawReceivePort
及 _get_id()
过程
这里我们可以以 RawReceivePort_factory
为关键词在 Runtime 源码里搜索相关的代码,看能不能找到一定调用或者实现。
可以看到确实有相关实现,并且只有一处,这可省了不少事 ,继续看源码。发现源码是一个宏定义,展开宏后确认这里就是 RawReceivePort_factory
的实现函数,函数内部直接调用了 isolate 的 CreateReceivePort
成员函数。
c++
// lib/isolate.cc#L60
// RawReceivePort_factory 的 宏定义
DEFINE_NATIVE_ENTRY(RawReceivePort_factory, 0, 2) {
ASSERT(
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(1));
return isolate->CreateReceivePort(debug_name);
}
// RawReceivePort_factory 宏展开后
// DN_HelperRawReceivePort_factory 函数的前向声明
static ObjectPtr DN_HelperRawReceivePort_factory(Isolate* isolate, Thread* thread, Zone* zone, NativeArguments* arguments);
ObjectPtr BootstrapNatives::DN_RawReceivePort_factory(Thread* thread, Zone* zone, NativeArguments* arguments) {
// 参数个数校验
do { } while (0);
do { } while (false && (arguments->NativeArgCount() == 2));
do { } while (false && (arguments->NativeTypeArgCount() >= 0));
// DN_HelperRawReceivePort_factory 调用
return DN_HelperRawReceivePort_factory(thread->isolate(), thread, zone, arguments);
}
// DN_HelperRawReceivePort_factory 的实现
static ObjectPtr DN_HelperRawReceivePort_factory(Isolate* isolate, Thread* thread, Zone* zone, NativeArguments* arguments) {
ASSERT(
TypeArguments::CheckedHandle(zone, arguments->NativeArgAt(0)).IsNull());
GET_NON_NULL_NATIVE_ARGUMENT(String, debug_name, arguments->NativeArgAt(1));
// 最后实际调用
return isolate->CreateReceivePort(debug_name);
}
上面的宏展开后调用到了当前线程关联的 isolate
的 CreateReceivePort
,它才是 Dart 侧 _RawReceivePort
的实际造物主。接着往下走:
c++
// vm/isolate.cc#L3650
ReceivePortPtr Isolate::CreateReceivePort(const String& debug_name) {
// 创建一个 port_id
Dart_Port port_id = PortMap::CreatePort(message_handler());
++open_ports_;
++open_ports_keepalive_;
// 实例化一个 port
return ReceivePort::New(port_id, debug_name);
}
// vm/port.cc#L55
Dart_Port PortMap::CreatePort(MessageHandler* handler) {
// 创建 Dart_Port
const Dart_Port port = AllocatePort();
MessageHandler::PortSetEntry isolate_entry;
isolate_entry.port = port;
handler->ports_.Insert(isolate_entry);
// Entry 用来包装 port 与 handler
Entry entry;
entry.port = port;
// 注意这里的 handler 不是 Dart 侧的回调 handler 哦
entry.handler = handler;
// 将成生成的 port_id 保存至全局的 set 中
ports_->Insert(entry);
return entry.port;
}
// vm/object.cc#L25948
// 省略了一些非重点关注的代码
ReceivePortPtr ReceivePort::New(Dart_Port id,
const String& debug_name,
Heap::Space space) {
ASSERT(id != ILLEGAL_PORT);
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
// 创建 SendPort,保存 CreateReceivePort 生成的 id
const SendPort& send_port =
SendPort::Handle(zone, SendPort::New(id, thread->isolate()->origin_id()));
// 创建 ReceivePort
const auto& result =
ReceivePort::Handle(zone, Object::Allocate<ReceivePort>(space));
// 将 ReceivePort 与 SendPort 关联
result.untag()->set_send_port(send_port.ptr());
return result.ptr();
}
上面的代码创建了一个 ReceivePort
并关联上了一个 SendPort
,并把一个 Dart_Port
交给 SendPort
保存。我们的 RawReceivePort_get_id
函数正是返回的这个,但是 这个 Dart_Port
是个啥类型?
C++
/**
* A port is used to send or receive inter-isolate messages
*/
typedef int64_t Dart_Port;
哦... 原来就是个 int64
类型,淦;还记得 Dart 侧 _RawReceivePort
的静态函数 _handleMesssage
的第一个函数吗?「凑巧」也是 int 类型哦(这个世界没有那么多巧合,大多都是精心的设计)。 继续看 RawReceivePort_get_id
:
c++
// lib/isolate.cc#L67
// 又是一堆宏定义
DEFINE_NATIVE_ENTRY(RawReceivePort_get_id, 0, 1) {
// 核心函数宏
GET_NON_NULL_NATIVE_ARGUMENT(ReceivePort, port, arguments->NativeArgAt(0));
return Integer::New(port.Id());
}
// 上面的核心函数展开后
DEFINE_NATIVE_ENTRY(RawReceivePort_get_id, 0, 1) {
const Instance& __port_instance__ = Instance::CheckedHandle(zone, arguments->NativeArgAt(0));
if (!__port_instance__.IsReceivePort()) { DartNativeThrowArgumentException(__port_instance__); }
// 下面两行是重点,ReceivePort 实例是当前函数的参数
const ReceivePort& port = ReceivePort::Cast(__port_instance__);
// 调用下面 ReceivePort 内的 Id() 函数
return Integer::New(port.Id());
}
// vm/object.h#L12479
// port id 获取逻辑
class ReceivePort : public Instance {
public:
SendPortPtr send_port() const { return untag()->send_port(); }
// 通过 send_port 获取保存的 id
Dart_Port Id() const { return send_port()->untag()->id_; }
}
好,现在我们已经知道了 Dart 侧 _RawReceivePort
、_SendPort
之间的关系,并且知道传给 Dart 侧的 _portMap
的 key 的来源,总结一下这个过程:
-
Dart 侧
_RawReceivePort
的私有构造函数会调用 Runtime 侧的RawReceivePort_factory
C++ 全局函数; -
RawReceivePort_factory
被定义在一个宏中,展开后其实际调用的是当前线程绑定的isolate
实例的CreateReceivePort
成员函数; -
CreateReceivePort
函数内一开始就使用了PortMap::CreatePort
来创建了一个 id (类型为Dart_Port
,真实类型为int64
),并把这个与当前的IoslateHandlerMessage
(通过 handler_message()返回)包装成了一个Entry
对象保存到全局 Set 中; -
紧接着又调用了
ReceivePort::New
函数,参数就是为上面创建的id
。New
函数里直接创建了ReceivePort
、SendPort
两个对象并把SendPort
对象关联到了ReceivePort
对象,SendPort
对象又同时保存了步骤 3 传过来的id
参数,最后返回ReceivePort
的 C++ 对象; -
当 Dart 侧调用
_get_id()
时会调用到 Runtime 侧的RawReceivePort_get_id
C++ 函数,该函数同样也被定义在宏中,展开后调用的ReceivePort
对象的Id()
成员函数; -
ReceivePort
的Id()
成员函数返回的是关联SendPort
所保存的id
,对应步骤 3、4;
呐,Dart 侧的代码拎出再看看,RawReceivePort
的创建过程是不是就清晰很多了?
dart
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L55
@patch
class RawReceivePort {
@patch
factory RawReceivePort([Function? handler, String debugName = '']) {
_RawReceivePort result = new _RawReceivePort(debugName);
result.handler = handler;
return result;
}
}
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L129
@pragma("vm:entry-point")
final class _RawReceivePort implements RawReceivePort {
factory _RawReceivePort(String debugName) {
// 对应步骤 1,2,3,4
final port = _RawReceivePort._(debugName);
// _get_id() 对应步骤 5,6
_portMap[port._get_id()] = port;
return port;
}
@pragma("vm:external-name", "RawReceivePort_factory")
external factory _RawReceivePort._(String debugName);
@pragma("vm:external-name", "RawReceivePort_get_id")
external int _get_id();
}
还不清晰?再来两张图来!
如果还不清晰可以再回过头去再看一遍。
_SendPort
的 send
过程
接下来看重点,RawReceivePort.sendPort.send()
到底干了什么?但我们需要先返回到 Dart 侧看看 Dart 侧的 _SendPort.send
。
dart
// sdk/lib/_internal/vm/lib/isolate_patch.dart#L222
final class _SendPort implements SendPort {
@pragma("vm:entry-point", "call")
void send(var message) {
_sendInternal(message);
}
@pragma("vm:external-name", "SendPort_sendInternal_")
external void _sendInternal(var message);
}
Dart 侧 send
又绕回了 Runtime 里面,只不过函数名又变成了 SendPort_sendInternal_
(通过 @pragma 指定)。这个就是进行代码搜索的关键词,接着看:
c++
// lib/isolate.cc#L113
DEFINE_NATIVE_ENTRY(SendPort_sendInternal_, 0, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(SendPort, port, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Instance, obj, arguments->NativeArgAt(1));
const Dart_Port destination_port_id = port.Id();
const bool same_group = InSameGroup(isolate, port);
// 重点,传入的优先级为:Message::kNormalPriority
PortMap::PostMessage(WriteMessage(same_group, obj, destination_port_id,
Message::kNormalPriority));
return Object::null();
}
忽略其它细节,当 Dart 侧的 send
函数调用的时候,一路调用到了 PortMap::PostMessage
,直译过来就是「发送消息」。是不是与「入口」小节的结论:「main 函数并不是由 runtime 直接调用,而是通过消息机制来触发」呼上了~ 后面还会有更证据证明这个结论。
PS: WriteMessage 内创建了一个 Message 对象,实现没啥重点不影响逻辑分析,这里先省略。
PortMap::PostMessage
函数最终会通过消息机制调到 Dart 侧的静态 _handleMessage
方法来,到目前为止还没有直接看这个过程,还得接着往下看。
c++
// vm/port.cc#L152
bool PortMap::PostMessage(std::unique_ptr<Message> message,
bool before_events) {
MutexLocker ml(mutex_);
if (ports_ == nullptr) {
return false;
}
// 通过 int64 类型的 port id 在 ports_ 中查找 对应 Entry
auto it = ports_->TryLookup(message->dest_port());
if (it == ports_->end()) {
message->DropFinalizers();
return false;
}
MessageHandler* handler = (*it).handler;
ASSERT(handler != nullptr);
// 调用 handler 继续发送消息,翻一下前面 Entry 保存的 handler 对象
handler->PostMessage(std::move(message), before_events);
return true;
}
Dart 侧的 _RawReceivePort.sendPort.send()
一路走来到了 handler->PostMessage
函数。handler
来自于 Isolate
(vm/isolate.cc) 类型内的 message_handler()
函数。handler
是一个 MessageHandler
类型。MessageHandler
在 Runtime 中是一个基类,派生出了多个子类型。当前只需要关注 Isolate
类型内的 message_handler()
函数返回的类型即可。
dart
// vm/isolate.cc#L2338
MessageHandler* Isolate::message_handler() const {
// 在下面 Isolate::InitIsolate 赋值
return message_handler_;
}
// vm/isolate.cc#L1757
Isolate* Isolate::InitIsolate(const char* name_prefix,
IsolateGroup* isolate_group,
const Dart_IsolateFlags& api_flags,
bool is_vm_isolate) {
Isolate* result = new Isolate(isolate_group, api_flags);
// ... 省略若干代码
// Setup the isolate message handler.
result->message_handler_ = new IsolateMessageHandler(result);
// ... 省略若干代码
}
由上可知当前 Isolate
内的 handler
类型为 IsolateMessageHandler
,看看 IsolateMessageHandler
内有没有重写 PostMessage
成员函数。(搜索关键词:IsolateMessageHandler::PostMessage)
通过搜索发现 IsolateMessageHandler
没有重写 PostMessage
,也就是当前 IsolateMessageHandler::PostMessage
会调用到父类 MessageHandler
内的默认实现。
c++
// vm/message_handler.cc#L130
void MessageHandler::PostMessage(std::unique_ptr<Message> message,
bool before_events) {
Message::Priority saved_priority;
{
MonitorLocker ml(&monitor_);
// WriteMessage 传入的消息优先级为 Message::kNormalPriority
saved_priority = message->priority();
if (message->IsOOB()) {
oob_queue_->Enqueue(std::move(message), before_events);
} else {
// 走这个分支,存入消息
queue_->Enqueue(std::move(message), before_events);
}
if (paused_for_messages_) {
ml.Notify();
}
if (pool_ != nullptr && !task_running_) {
task_running_ = true;
// 如果当前 MessageHandler 没有启动则启动线程池,开始消费 queue_ 内的消息
const bool launched_successfully = pool_->Run<MessageHandlerTask>(this);
}
}
MessageNotify(saved_priority);
}
目前为止追即便已经追到了线程池也还没有发现 Dart 侧 _RawReceivePort
内的静态 _handleMessage
函数的调用线索。看到这里再回顾一下整个流程:
-
Dart 侧
_RawReceivePort
的sendPort
get 函数,按 @pragma 标记应该对应RawReceivePort_getSendPort
C++ 函数,但通过模糊搜索发现是直接调用到的 Runtime 侧的ReceivePort
类型的send_port()
成员函数并返回 Dart 侧真实实例 。 -
Dart 侧调用
RawReceivePort.send
方法时调用的又是_sendInternal
方法,该方法是一个与 Runtime 交互的方法,会直接调用到SendPort_sendInternal
C++ 函数,这个函数也是一个宏定义,展开宏后调用的是当前线程绑定的 isolate 的message_handler
成员属性(MessageHandler 的子类型)的PortMap::PostMessage
函数。 -
由于
MessageHandler
有多个子类,通过找到赋值语句发现,message_handler_
指定的类型为IsolateMessageHandler
。并且该子类没有重写PostMessage
方法,因此 Dart 侧的SendPort._sendInternal
调用到了 Runtime 父类MessageHandler
的PostMessage
方法。 -
MessageHandler::PostMessage
函数传入的参数是一个Message
类型,包含了 Dart 侧调用send(_)
时传过来的参数,并指定了默认优先级(Message::kNormalPriority
)。 -
MessageHandler::PostMessage
内将Message
保存到了一个queue_
队列里,如果当前没有启动线程池则启动线程池开始消费_queue
内的消息(通过调用pool_->Run()
)。
流程确实有点长,不过还不算太复杂,静下心来还是能理清楚的。到这里只是完成 ReceivePort
创建与 send
消息相关的部分内容。send
最后调用到了 PostMessage
,并启动线程池来消费消息队列。_handleMessage
的静态函数的调用身影还没有看到。还需要进一步深入到线程池启动相关的部分。
深入
上一小节我们探索到了 HandleMessage::PostMessage
方法内,该方法启动了线程池(通过 pool_->Run()
函数) ,现在来正式探索线程池相关的内容。
C++
// vm/message_handler.cc#L167
if (pool_ != nullptr && !task_running_) {
task_running_ = true;
// 如果当前 MessageHandler 没有启动则启动线程池,开始消费 queue_ 内的消息
// 注意这里的 MessageHandlerTask 泛型,this 指向当前 MessageHandler
// Run 实现继续看下面
const bool launched_successfully = pool_->Run<MessageHandlerTask>(this);
}
// vm/thread_pool.h#L44
template <typename T, typename... Args>
bool Run(Args&&... args) {
// 这里定义了一个模板类型,new 出来的类型为上面指定的 MessageHandlerTask
// RunImp 继续看下面
return RunImpl(std::unique_ptr<Task>(new T(std::forward<Args>(args)...)));
}
// vm/thread_pool.cc#L84
bool ThreadPool::RunImpl(std::unique_ptr<Task> task) {
Worker* new_worker = nullptr;
{
MonitorLocker ml(&pool_monitor_);
if (shutting_down_) {
return false;
}
// 传入了 task,创建并返回了一个 Worker
new_worker = ScheduleTaskLocked(&ml, std::move(task));
}
// 创建 Worker 成功后启动线程
if (new_worker != nullptr) {
new_worker->StartThread();
}
return true;
}
上面的 3 段代码引入了两个新类型:MessageHandlerTask
、Worker
。
c++
// message_handler.cc#L23
class MessageHandlerTask : public ThreadPool::Task {
public:
explicit MessageHandlerTask(MessageHandler* handler) : handler_(handler) {
ASSERT(handler != nullptr);
}
virtual void Run() {
ASSERT(handler_ != nullptr);
handler_->TaskCallback();
}
private:
MessageHandler* handler_;
};
MessageHandlerTask
是 Task
的子类型,MessageHandlerTask
只有一个属性一个方法,属性就是当前的 MessageHandler
,还记得吗?MessageHandler
是一个父类,在这里实际是它的子类型 IsolateMessageHandler
,如果不记得回过头去看上个小节的 「SendPort 的 send 过程」。Run()
函数也会调用到 IsolateMessageHandler
的 TaskCallback
方法里去。所以这里的 MessageHandlerTask
只是对 IsolateMessageHandler
的简单包装。
c++
// vm/thread_pool.h#L70
class Worker : public IntrusiveDListEntry<Worker> {
public:
explicit Worker(ThreadPool* pool);
void StartThread();
private:
friend class ThreadPool;
// 留意这里的 static
static void Main(uword args);
ThreadPool* pool_;
ThreadJoinId join_id_;
OSThread* os_thread_ = nullptr;
bool is_blocked_ = false;
DISALLOW_COPY_AND_ASSIGN(Worker);
};
Work
类型就复杂一点,从类型的定义来看它持有当前线程、线程池、关联当前线程 id,所以 Worker
应该代表线程。
再回过头来看 ThreadPool::RunImpl
里的 ScheduleTaskLocked
、StartThread
调用过程。
c++
// vm/thread_pool.cc#L263
ThreadPool::Worker* ThreadPool::ScheduleTaskLocked(MonitorLocker* ml,
std::unique_ptr<Task> task) {
// 把 task 添加到当前线程池的任务列表中并将待运行任务数 +1
tasks_.Append(task.release());
pending_tasks_++;
// 省略干扰代码
// 创建 Worker,传入的 this 是当前线程池
auto new_worker = new Worker(this);
// 添加到列表,空闲列表数 +1
idle_workers_.Append(new_worker);
count_idle_++;
return new_worker;
}
// vm/thread_pool.cc#L296
void ThreadPool::Worker::StartThread() {
// 把当前 Worker 的静态 Main 函数丢到平台线程里去运行
// OSThread::Start 不同平台有不现实现,都是创建线程运行指定函数那套,这里不继续展开了
// this 代表当前 Worker, 而 Worker 持有当前线程池
// Worker::Main 的实现往下看
int result = OSThread::Start("DartWorker", &Worker::Main,
reinterpret_cast<uword>(this));
// 忽略其它代码
}
// vm/thread_pool.cc#L331
void ThreadPool::Worker::Main(uword args) {
// args 参数就是上面传进来的 this
Worker* worker = reinterpret_cast<Worker*>(args);
// 取出 Worker 持有的线程池
ThreadPool* pool = worker->pool_;
// 调用线程池的 WorkerLoop 方法,参数是当前是传入的 Worker 对象
pool->WorkerLoop(worker);
}
StartThread
里面会利用平台创建新线程运行静态 ThreadPool::Worker::Main(_)
函数。但这里需要记住,在上面的 ThreadPool::Worker::Main(_)
函数里线程已完成切换 ,WorkerLoop
的调用已经切换到新的线程里了。
c++
// vm/thread_pool.cc#L146
// 同样省略了非核心的部分代码
void ThreadPool::WorkerLoop(Worker* worker) {
while (true) {
MonitorLocker ml(&pool_monitor_);
// 不断检查当前线程里的 tasks_ 列表是否为空
if (!tasks_.IsEmpty()) {
IdleToRunningLocked(worker);
while (!tasks_.IsEmpty()) {
std::unique_ptr<Task> task(tasks_.RemoveFirst());
pending_tasks_--;
MonitorLeaveScope mls(&ml);
// 运行 task 的 Run 函数
task->Run();
ASSERT(Isolate::Current() == nullptr);
task.reset();
}
RunningToIdleLocked(worker);
}
}
}
还记得 tasks_
列表里的任务是在哪里加入的吗?不记得的话看看 ThreadPool::ScheduleTaskLocked
函数的实现。在新的线程里,会不断检查 tasks_
列表是否为空,不为空则执行它的 Run()
函数,这里 task
是什么类型?不会又不记得了吧?另外 Run()
函数的实现调到哪里去了?task
就是 MessageHandlerTask
啦,这个小节开头就介绍了,这里再把它翻出来。
c++
// message_handler.cc#L29
virtual void Run() {
ASSERT(handler_ != nullptr);
handler_->TaskCallback();
}
handler_
仍然是 IsolateMessageHandler
,看看它有没有重写 TaskCallback()
,发现并没有,那还是回到了它的父类 MessageHandler
的 TaskCallback()
。
c++
// message_handler.cc#L391
// 省略很多代码
void MessageHandler::TaskCallback() {
MessageStatus status = kOK;
// 调用到了当前类的 HandleMessages
if (status != kShutdown) {
// message_handler.cc#L458
status = HandleMessages(&ml, (status == kOK), true);
}
}
// message_handler.cc#L194
MessageHandler::MessageStatus MessageHandler::HandleMessages(
MonitorLocker* ml,
bool allow_normal_messages,
bool allow_multiple_normal_messages) {
MessageStatus max_status = kOK;
Message::Priority min_priority =
((allow_normal_messages && !paused()) ? Message::kNormalPriority
: Message::kOOBPriority);
// 取出保存的 Message, 在启动流程中只一个
// 还记得在哪里保持的这个 message 吗?
std::unique_ptr<Message> message = DequeueMessage(min_priority);
// 一个 while 循环不停的取 message 进行处理
while (message != nullptr) {
// 取出优先级
Message::Priority saved_priority = message->priority();
// 取出端口编号
Dart_Port saved_dest_port = message->dest_port();
MessageStatus status = kOK;
{
DisableIdleTimerScope disable_idle_timer(idle_time_handler);
// 又往下套了一层 HandleMessage,这个与当前函数签名不一致哦(参数不一致)
// 注释里说由子类实现,也就是 IsolateMessageHandler 中实现
// 消息最终在这个方法里消费掉
status = HandleMessage(std::move(message));
}
min_priority = (((max_status == kOK) && allow_normal_messages && !paused())
? Message::kNormalPriority
: Message::kOOBPriority);
// 如果还有 message 继续消费,进入下一个轮回
message = DequeueMessage(min_priority);
}
}
看到这里有没有发现,这里的逻辑与「初探」一节的逻辑关联性越来越强。当前 message
从队列中取出,这个 message
是在「初探->SendPort 的 send 过程->第五步」中保存。
c++
// vm/isolate.cc#L1292
MessageHandler::MessageStatus IsolateMessageHandler::HandleMessage(
std::unique_ptr<Message> message) {
MessageStatus status = kOK;
if (message->IsOOB()) {
// 省略
} else if (message->IsFinalizerInvocationRequest()) {
// 省略
} else if (message->dest_port() == Message::kIllegalPort) {
// 省略
} else {
// 实际调用
const Object& msg_handler = Object::Handle(
zone, DartLibraryCalls::HandleMessage(message->dest_port(), msg));
// 省略
}
return status;
}
ObjectPtr DartLibraryCalls::HandleMessage(Dart_Port port_id,
const Instance& message) {
auto* const thread = Thread::Current();
auto* const zone = thread->zone();
auto* const isolate = thread->isolate();
auto* const object_store = thread->isolate_group()->object_store();
// 获取 Dart 侧 _handleMessage 函数地址
const auto& function =
Function::Handle(zone, object_store->handle_message_function());
ASSERT(!function.IsNull());
Array& args =
Array::Handle(zone, isolate->isolate_object_store()->dart_args_2());
ASSERT(!args.IsNull());
args.SetAt(0, Integer::Handle(zone, Integer::New(port_id)));
args.SetAt(1, message);
DebuggerSetResumeIfStepping(isolate);
// 最后调用 Dart 侧 _handleMessage 函数
const Object& handler =
Object::Handle(zone, DartEntry::InvokeFunction(function, args));
return handler.ptr();
}
到这里 _RawReceivePort
整个的创建、消息发送与接收完成了闭环。
在「入口」一节说到:
Dart 的 main 通过消息机制触发
我想,看到这里应该能理解这句话的意义了吧!在消息发送的过程中顺便完成了线程的切换,由平台主线程切换到了线程池中的某个子线程。
了然
通过前面的分析知道了 _RawReceivePort
的基本原理,知道了 Dart 的 main 函数是如何从 _RawReceivePort.handler
中触发。但是「入口」一节中开头提到的 _startMainIsolate/_startIsolate
函数是何时被调用的还没有看到,毕竟 _startMainIsolate/_startIsolate
才是 Dart 世界真正的起源。接下来,继续探索这个过程。
还是用老办法,用 _startMainIsolate
关键词搜索 Runtime 源码,发现有且仅有一处匹配。
这行代码出现在 RunMainIsolate
中,进一步查找 RunMainIsolate
的引用位置发现 RunMainIsolate
只在 void main(int argc, char** argv)
函数(Runtime 的 main 函数)中引用。
c++
// bin/main_impl.cc#L1160
void main(int argc, char** argv) {
// 省略其它代码
if (should_run_user_program) {
if (!Dart_IsPrecompiledRuntime() && Snapshot::IsAOTSnapshot(script_name)) {
// 省略
} else {
if (Options::gen_snapshot_kind() == kKernel) {
// 省略
} else {
// 调用 RunMainIsolate
RunMainIsolate(script_name, package_config_override,
force_no_sound_null_safety, &dart_options);
}
}
}
}
// bin/main_impl.cc#L992
void RunMainIsolate(const char* script_name,
const char* package_config_override,
bool force_no_sound_null_safety,
CommandLineOptions* dart_options) {
// 省略其它代码
// 获取 Dart 侧 main 函数入口地址
Dart_Handle main_closure =
Dart_GetField(root_lib, Dart_NewStringFromCString("main"));
CHECK_RESULT(main_closure);
if (!Dart_IsClosure(main_closure)) {
ErrorExit(kErrorExitCode, "Unable to find 'main' in root library '%s'\n",
script_name);
}
// 生成传给 Dart 侧的参数(包含 main 函数地址)
const intptr_t kNumIsolateArgs = 2;
Dart_Handle isolate_args[kNumIsolateArgs];
isolate_args[0] = main_closure; // entryPoint
isolate_args[1] = dart_options->CreateRuntimeOptions(); // args
// 调用到 Dart 侧的 _startMainIsolate
Dart_Handle isolate_lib =
Dart_LookupLibrary(Dart_NewStringFromCString("dart:isolate"));
result =
Dart_Invoke(isolate_lib, Dart_NewStringFromCString("_startMainIsolate"),
kNumIsolateArgs, isolate_args);
// 省略其它代码
}
这不就对上了吗?void main(int argc, char** argv)
函数是 C++ 程序的入口,在入口里触发 Dart 侧 main
函数的调用过程。
启动 Runtime 进程后进入 main
函数,先初始化了运行环境,再获取 Dart 侧 main
函数地址与启动参数。将 Dart 的 main
函数与进程参数包装成了 Dart_Handle
,传递给 Dart 侧的 _startMainIsolate
函数指针并完成了调用,在这之后就正式进入了 Dart 的世界。
进入 Dart 后,回顾一下 「入口」一节的流程。首先创建了 RawReceivePort
实例,并给该实例设置了 handler
回调。handler
回调中捕获了 _startMainIsolate
的第一个参数也就是 main
函数地址。当向 RawReceivePort
的 _SendPort
对象 send
消息后会触发 handler
回调,handler
回调再触发 main
函数的调用。
整个启动流程的重点还是在于 RawReceivePort
的创建与消息发送机制,如果你理解下面这张图就真正理解了这个过程。
受限于篇幅在这篇文章中省略了 Isolate
、IsolateGroup
、Heap
等类的实现分析,但这并不影响理解启动流程的分析,在后续的文章中将一一拆解并分析,敬请期待。