likes
comments
collection
share

Dart 启动流程解析:探秘梦之起源

作者站长头像
站长
· 阅读数 4

全文阅读预计 45 分钟,建议先「收藏」稍后有空再静心阅读

缘起

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。

Dart 启动流程解析:探秘梦之起源

从上面的图片中的调用栈分析可以得出一个简单的的结论:main 函数并不是由 Runtime 直接调用,而是通过消息机制来触发(根据调用栈中 ReceivePorthandleMessage 关键词来推断,另外看到 ReceivePort 你应该知道它与 isolate 的关系 ),先记住这个结论,后面会得到证明。先看 _delayEntrypointInvocation 函数与 _handleMessage 的内部实现:

// 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 函数又是哪里被调用的呢?全局搜索一下,发现只有两处有效调用,就在当前函数的上面。

// 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 的调用流程线索就断了。

// sdk/lib/_internal/vm/lib/isolate_patch.dart#L271
@pragma("vm:entry-point", "call")
Function _getStartMainIsolateFunction() {
  return _startMainIsolate;
}

由此可以推断 Runtime 是用上面的代码获取 Dart 侧 _startMainIsolate 函数地址,然后再调用,调用时将 main 函数地址当成第一个参数传了进来。回顾一下前面提到的的大致流程,示意图如下:

Dart 启动流程解析:探秘梦之起源

上面的流程还遗留了两个问题:

  1. main 函数是怎么传到 _startMainIsolate 的第一个参数的 ?
  2. RawReceivePort 与 Runtime 建立联系了吗?send 之后为什么会调用到 _handleMessage

先来看第二个问题,毕竟 RawReceivePort 的实现咱还没有看,回到 _delayEntryPointInvocation 函数内 RawReceivePort 创建处:

// 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 实例(抽象类中用工厂构造函数返回实际实例,对应工厂设计模)。

// 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 相关的函数里去,他们是:

// 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 源码里搜索相关的代码,看能不能找到一定调用或者实现。

Dart 启动流程解析:探秘梦之起源

可以看到确实有相关实现,并且只有一处,这可省了不少事 ,继续看源码。发现源码是一个宏定义,展开宏后确认这里就是 RawReceivePort_factory 的实现函数,函数内部直接调用了 isolate 的 CreateReceivePort 成员函数。

// 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);
}

上面的宏展开后调用到了当前线程关联的 isolateCreateReceivePort,它才是 Dart 侧 _RawReceivePort 的实际造物主。接着往下走:

// 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 是个啥类型?

/**
 * A port is used to send or receive inter-isolate messages
 */
typedef int64_t Dart_Port;

哦... 原来就是个 int64 类型,淦;还记得 Dart 侧 _RawReceivePort 的静态函数 _handleMesssage 的第一个函数吗?「凑巧」也是 int 类型哦(这个世界没有那么多巧合,大多都是精心的设计)。 继续看 RawReceivePort_get_id :

// 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 的来源,总结一下这个过程:

  1. Dart 侧 _RawReceivePort 的私有构造函数会调用 Runtime 侧的 RawReceivePort_factory C++ 全局函数;

  2. RawReceivePort_factory 被定义在一个宏中,展开后其实际调用的是当前线程绑定的 isolate 实例的 CreateReceivePort 成员函数;

  3. CreateReceivePort 函数内一开始就使用了 PortMap::CreatePort 来创建了一个 id (类型为 Dart_Port,真实类型为 int64),并把这个与当前的 IoslateHandlerMessage(通过 handler_message()返回)包装成了一个 Entry 对象保存到全局 Set 中;

  4. 紧接着又调用了 ReceivePort::New 函数,参数就是为上面创建的 idNew 函数里直接创建了 ReceivePortSendPort 两个对象并把 SendPort 对象关联到了 ReceivePort 对象,SendPort 对象又同时保存了步骤 3 传过来的 id 参数,最后返回 ReceivePort 的 C++ 对象;

  5. 当 Dart 侧调用 _get_id() 时会调用到 Runtime 侧的 RawReceivePort_get_id C++ 函数,该函数同样也被定义在宏中,展开后调用的 ReceivePort 对象的 Id() 成员函数;

  6. ReceivePortId() 成员函数返回的是关联 SendPort 所保存的 id,对应步骤 3、4;

呐,Dart 侧的代码拎出再看看,RawReceivePort 的创建过程是不是就清晰很多了?

// 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();
}

还不清晰?再来两张图来!

Dart 启动流程解析:探秘梦之起源
Dart 启动流程解析:探秘梦之起源

如果还不清晰可以再回过头去再看一遍。

_SendPortsend 过程

接下来看重点,RawReceivePort.sendPort.send() 到底干了什么?但我们需要先返回到 Dart 侧看看 Dart 侧的 _SendPort.send

// 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 指定)。这个就是进行代码搜索的关键词,接着看:

// 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 方法来,到目前为止还没有直接看这个过程,还得接着往下看。

// 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() 函数返回的类型即可。

// 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)

Dart 启动流程解析:探秘梦之起源

通过搜索发现 IsolateMessageHandler 没有重写 PostMessage,也就是当前 IsolateMessageHandler::PostMessage 会调用到父类 MessageHandler 内的默认实现。

// 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 函数的调用线索。看到这里再回顾一下整个流程:

  1. Dart 侧 _RawReceivePortsendPort get 函数,按 @pragma 标记应该对应 RawReceivePort_getSendPort C++ 函数,但通过模糊搜索发现是直接调用到的 Runtime 侧的 ReceivePort 类型的 send_port() 成员函数并返回 Dart 侧真实实例 。

  2. Dart 侧调用 RawReceivePort.send 方法时调用的又是 _sendInternal 方法,该方法是一个与 Runtime 交互的方法,会直接调用到 SendPort_sendInternal C++ 函数,这个函数也是一个宏定义,展开宏后调用的是当前线程绑定的 isolate 的 message_handler 成员属性(MessageHandler 的子类型)的 PortMap::PostMessage 函数。

  3. 由于 MessageHandler 有多个子类,通过找到赋值语句发现,message_handler_ 指定的类型为 IsolateMessageHandler。并且该子类没有重写 PostMessage 方法,因此 Dart 侧的 SendPort._sendInternal 调用到了 Runtime 父类 MessageHandlerPostMessage 方法。

  4. MessageHandler::PostMessage 函数传入的参数是一个 Message 类型,包含了 Dart 侧调用 send(_) 时传过来的参数,并指定了默认优先级(Message::kNormalPriority)。

  5. MessageHandler::PostMessage 内将 Message 保存到了一个 queue_ 队列里,如果当前没有启动线程池则启动线程池开始消费 _queue 内的消息(通过调用 pool_->Run())。

流程确实有点长,不过还不算太复杂,静下心来还是能理清楚的。到这里只是完成 ReceivePort 创建与 send 消息相关的部分内容。send 最后调用到了 PostMessage,并启动线程池来消费消息队列。_handleMessage 的静态函数的调用身影还没有看到。还需要进一步深入到线程池启动相关的部分。

深入

上一小节我们探索到了 HandleMessage::PostMessage 方法内,该方法启动了线程池(通过 pool_->Run() 函数) ,现在来正式探索线程池相关的内容。

// 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 段代码引入了两个新类型:MessageHandlerTaskWorker

// 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_;
};

MessageHandlerTaskTask 的子类型,MessageHandlerTask 只有一个属性一个方法,属性就是当前的 MessageHandler,还记得吗?MessageHandler 是一个父类,在这里实际是它的子类型 IsolateMessageHandler,如果不记得回过头去看上个小节的 「SendPort 的 send 过程」。Run() 函数也会调用到 IsolateMessageHandlerTaskCallback 方法里去。所以这里的 MessageHandlerTask 只是对 IsolateMessageHandler 的简单包装。

// 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 里的 ScheduleTaskLockedStartThread 调用过程。

// 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 的调用已经切换到新的线程里了。

// 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 啦,这个小节开头就介绍了,这里再把它翻出来。

// message_handler.cc#L29
virtual void Run() {
  ASSERT(handler_ != nullptr);
  handler_->TaskCallback();
}

handler_ 仍然是 IsolateMessageHandler,看看它有没有重写 TaskCallback() ,发现并没有,那还是回到了它的父类 MessageHandlerTaskCallback()

// 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 过程->第五步」中保存。

// 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 启动流程解析:探秘梦之起源

在「入口」一节说到:

Dart 的 main 通过消息机制触发

我想,看到这里应该能理解这句话的意义了吧!在消息发送的过程中顺便完成了线程的切换,由平台主线程切换到了线程池中的某个子线程。

了然

通过前面的分析知道了 _RawReceivePort 的基本原理,知道了 Dart 的 main 函数是如何从 _RawReceivePort.handler 中触发。但是「入口」一节中开头提到的 _startMainIsolate/_startIsolate 函数是何时被调用的还没有看到,毕竟 _startMainIsolate/_startIsolate 才是 Dart 世界真正的起源。接下来,继续探索这个过程。

还是用老办法,用 _startMainIsolate 关键词搜索 Runtime 源码,发现有且仅有一处匹配。

Dart 启动流程解析:探秘梦之起源

这行代码出现在 RunMainIsolate 中,进一步查找 RunMainIsolate 的引用位置发现 RunMainIsolate 只在 void main(int argc, char** argv) 函数(Runtime 的 main 函数)中引用。

// 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 的创建与消息发送机制,如果你理解下面这张图就真正理解了这个过程。

Dart 启动流程解析:探秘梦之起源

受限于篇幅在这篇文章中省略了 IsolateIsolateGroupHeap 等类的实现分析,但这并不影响理解启动流程的分析,在后续的文章中将一一拆解并分析,敬请期待。