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

全文阅读预计 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 直接调用,而是通过消息机制来触发 (根据调用栈中 ReceivePorthandleMessage 关键词来推断,另外看到 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 函数地址当成第一个参数传了进来。回顾一下前面提到的的大致流程,示意图如下:

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

  1. main 函数是怎么传到 _startMainIsolate 的第一个参数的 ?
  2. 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);
}

上面的宏展开后调用到了当前线程关联的 isolateCreateReceivePort,它才是 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 的来源,总结一下这个过程:

  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 的创建过程是不是就清晰很多了?

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

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


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

_SendPortsend 过程

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

  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() 函数) ,现在来正式探索线程池相关的内容。

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

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

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

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() ,发现并没有,那还是回到了它的父类 MessageHandlerTaskCallback()

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

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

相关推荐
瓜子三百克4 小时前
七、性能优化
flutter·性能优化
恋猫de小郭11 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
Codebee12 小时前
OneCode自主UI设计体系:架构解析与核心实现
前端·javascript·前端框架
jingling55516 小时前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架
点金石游戏出海17 小时前
每周资讯 | Krafton斥资750亿日元收购日本动画公司ADK;《崩坏:星穹铁道》新版本首日登顶iOS畅销榜
游戏·ios·业界资讯·apple·崩坏星穹铁道
伍哥的传说18 小时前
鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)
前端·华为·前端框架·harmonyos·鸿蒙
小蜜蜂嗡嗡18 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
旷世奇才李先生19 小时前
Swift 安装使用教程
开发语言·ios·swift
90后的晨仔19 小时前
Xcode16报错: SDK does not contain 'libarclite' at the path '/Applicati
ios
青松学前端19 小时前
vue-2.7源码解读之初始化流程和响应式实现
前端·vue.js·前端框架