The Engine startup process of Flutter describes how the Engine creates and calls the entry function main. For reasons of length, the creation of the Dart VM is not covered in this article. So this article takes a look at how the Dart VM is created.
1. Dart VM object creation
To look directly at the Shell Create function (read the Engine startup process of Flutter for details on how to call this function), the code reads as follows.
std::unique_ptr<Shell> Shell::Create(
TaskRunners task_runners,
const WindowData window_data,
Settings settings,
Shell::CreateCallback<PlatformView> on_create_platform_view,
Shell::CreateCallback<Rasterizer> on_create_rasterizer) {
PerformInitializationTasks(settings);
PersistentCache::SetCacheSkSL(settings.cache_sksl);
//Dart VM creation
auto vm = DartVMRef::Create(settings);
auto vm_data = vm->GetVMData(a);// Continue with the Shell object creation
return Shell::Create(std::move(task_runners), //
std::move(window_data), //
std::move(settings), //
vm_data->GetIsolateSnapshot(), // isolate snapshot
on_create_platform_view, //
on_create_rasterizer, //
std::move(vm) //
);
}
Copy the code
The DartVM is primarily created in Create through the DartVMRef Create function. Here’s the code.
DartVMRef DartVMRef::Create(Settings settings, fml::RefPtr
vm_snapshot, fml::RefPtr
isolate_snapshot)
{
std::scoped_lock lifecycle_lock(gVMMutex);
If there is a running Dart VM in the process, use the Dart VM directly. That is, only one Dart VM exists in the same process
if (auto vm = gVM.lock()) {
// Directly return to the existing Dart VM in the process
return DartVMRef{std::move(vm)};
}
std::scoped_lock dependents_lock(gVMDependentsMutex);
gVMData.reset(a); gVMServiceProtocol.reset(a); gVMIsolateNameServer.reset(a); gVM.reset(a);// If there is no Dart VM in the process, create and initialize one
auto isolate_name_server = std::make_shared<IsolateNameServer>();
auto vm = DartVM::Create(std::move(settings), //
std::move(vm_snapshot), //
std::move(isolate_snapshot), //
isolate_name_server //
);
if(! vm) {// Failed to create DartVM instance
return {nullptr};
}
// Set the Dart VM reference to a global reference to ensure intra-process uniqueness.
gVMData = vm->GetVMData(a); gVMServiceProtocol = vm->GetServiceProtocol(a); gVMIsolateNameServer = isolate_name_server; gVM = vm;if (settings.leak_vm) {
gVMLeak = new std::shared_ptr<DartVM>(vm);
}
return DartVMRef{std::move(vm)};
}
Copy the code
In DartVMRef, the Create function returns a reference to the DartVM, which is unique in the process. If the DartVM does not exist in the process, then a DartVM is created and initialized. Otherwise, the existing DartVM is used. Let’s take a look at the Dart VM creation process.
std::shared_ptr<DartVM> DartVM::Create( Settings settings, fml::RefPtr
vm_snapshot, fml::RefPtr
isolate_snapshot, std::shared_ptr
isolate_name_server)
{
auto vm_data = DartVMData::Create(settings, //
std::move(vm_snapshot), //
std::move(isolate_snapshot) //
);
if(! vm_data) {return {};
}
// Create the Dart VM
return std::shared_ptr<DartVM>(
new DartVM(std::move(vm_data), std::move(isolate_name_server)));
}
Copy the code
The above code simply creates a DartVM object and looks at its constructor.
DartVM::DartVM(std::shared_ptr<const DartVMData> vm_data,
std::shared_ptr<IsolateNameServer> isolate_name_server)
: settings_(vm_data->GetSettings()),
concurrent_message_loop_(fml::ConcurrentMessageLoop::Create()),
skia_concurrent_executor_(
[runner = concurrent_message_loop_->GetTaskRunner()](
fml::closure work) { runner->PostTask(work); }),
vm_data_(vm_data),
isolate_name_server_(std::move(isolate_name_server)),
service_protocol_(std::make_shared<ServiceProtocol>()) {
gVMLaunchCount++;
Since Dart VM initialization is thread-safe, the call here is thread-safe as well
SkExecutor::SetDefault(&skia_concurrent_executor_);
{
/ / create EventHandler
dart::bin::BootstrapDartIo(a);if(! settings_.temp_directory_path.empty()) {
dart::bin::SetSystemTempDirectory(settings_.temp_directory_path.c_str()); }}...// Register uI-related classes, such as Canvas, Picture, Window, etc.
DartUI::InitForGlobal(a); {Dart VM initialization parameters
Dart_InitializeParams params = {};
params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
params.vm_snapshot_data = vm_data_->GetVMSnapshot().GetDataMapping(a); params.vm_snapshot_instructions = vm_data_->GetVMSnapshot().GetInstructionsMapping(a); params.create_group =reinterpret_cast<decltype(params.create_group)>(
DartIsolate::DartIsolateGroupCreateCallback);
params.initialize_isolate =
reinterpret_cast<decltype(params.initialize_isolate)>(
DartIsolate::DartIsolateInitializeCallback);
params.shutdown_isolate =
reinterpret_cast<decltype(params.shutdown_isolate)>(
DartIsolate::DartIsolateShutdownCallback);
params.cleanup_isolate = reinterpret_cast<decltype(params.cleanup_isolate)>(
DartIsolate::DartIsolateCleanupCallback);
params.cleanup_group = reinterpret_cast<decltype(params.cleanup_group)>(
DartIsolate::DartIsolateGroupCleanupCallback);
params.thread_exit = ThreadExitCallback;
params.get_service_assets = GetVMServiceAssetsArchiveCallback;
params.entropy_source = dart::bin::GetEntropy;
//Dart VM initialization
char* init_error = Dart_Initialize(¶ms); . }... }Copy the code
The following are the main things that are done in the constructor of the DartVM object.
EventHandler
Creation.- Ui-related classes in
Engine
Layer registration. - Initialize the Dart VM.
Let’s start with the creation of EventHandler.
2. Creation of EventHandler
The EventHandler is created using the BootstrapDartIo function, with the following implementation code.
void BootstrapDartIo(a) {
// Bootstrap 'dart:io' event handler.
TimerUtils::InitOnce(a); EventHandler::Start(a); }Copy the code
In the EventHandler Start function, will create a global socket (globalTcpListeningSocketRegistry), a Monitor object and EventHandler object.
The implementation of EventHandler varies from system to system. This section uses Android as an example. EventHandler delegate_ is a EventHandlerImplementation object, his specific implementation in eventhandler_android. Cc.
void EventHandler::Start(a) {
/ / initialize a global socket (globalTcpListeningSocketRegistry)
ListeningSocketRegistry::Initialize(a); shutdown_monitor =new Monitor(a);Create an EventHandler object
event_handler = new EventHandler(a); event_handler->delegate_.Start(event_handler); . }Copy the code
In the Delegate_ constructor, an epoll handle is created and an Interrupt_fd is registered into the epoll.
Also look at Delegate_’s start function, where a new thread called DART: IO EventHandler is created. In Java, whenever you create a new thread, you pass a callback method. This is no exception. The corresponding callback is the Poll function, in which epoll is used to wait for events to execute.
After epoll was used to retrieve the completed task, HandleEvents was directly called for further processing, and corresponding callbacks were performed through the Isolate’s MessageHandler.
// Create an epoll handle
EventHandlerImplementation::EventHandlerImplementation()
: socket_map_(&SimpleHashMap::SamePointerValue, 16) {
intptr_t result;
result = NO_RETRY_EXPECTED(pipe(interrupt_fds_));
shutdown_ = false;
// The initial size passed to epoll_create. This value is ignored when Linux version >=2.6.8
static const int kEpollInitialSize = 64;
// Create an epoll handle
epoll_fd_ = NO_RETRY_EXPECTED(epoll_create(kEpollInitialSize));
// Register interrupt_fd into epoll.
struct epoll_event event;
event.events = EPOLLIN;
event.data.ptr = NULL;
int status = NO_RETRY_EXPECTED(
epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, interrupt_fds_[0], &event));
}
void EventHandlerImplementation::Start(EventHandler* handler) {
// Create a new thread named DART: IO EventHandler
int result =
Thread::Start("dart:io EventHandler", &EventHandlerImplementation::Poll,
reinterpret_cast<uword>(handler));
}
// The specific execution function of the new thread
void EventHandlerImplementation::Poll(uword args) {
ThreadSignalBlocker signal_blocker(SIGPROF);
static const intptr_t kMaxEvents = 16;
struct epoll_event events[kMaxEvents];
EventHandler* handler = reinterpret_cast<EventHandler*>(args);
EventHandlerImplementation* handler_impl = &handler->delegate_;
while(! handler_impl->shutdown_) {int64_t millis = handler_impl->GetTimeout(a);if (millis > kMaxInt32) {
millis = kMaxInt32;
}
// block wait
intptr_t result = TEMP_FAILURE_RETRY_NO_SIGNAL_BLOCKER(
epoll_wait(handler_impl->epoll_fd_, events, kMaxEvents, millis));
if (result == - 1) {... }else {
handler_impl->HandleTimeout(a);// Call the HandleEvents function in the child thread, which will process all the tasks it gets
handler_impl->HandleEvents(events, result);
}
}
handler->NotifyShutdownDone(a); }// Handle all the events you get
void EventHandlerImplementation::HandleEvents(struct epoll_event* events,
int size) {
bool interrupt_seen = false;
for (int i = 0; i < size; i++) {
if (events[i].data.ptr == NULL) {
interrupt_seen = true;
} else {
DescriptorInfo* di =
reinterpret_cast<DescriptorInfo*>(events[i].data.ptr);
const intptr_t old_mask = di->Mask(a);const intptr_t event_mask = GetPollEvents(events[i].events, di);
if ((event_mask & (1<< kErrorEvent)) ! =0) {
di->NotifyAllDartPorts(event_mask);
UpdateEpollInstance(old_mask, di);
} else if(event_mask ! =0) {
Dart_Port port = di->NextNotifyDartPort(event_mask);
UpdateEpollInstance(old_mask, di);
// Send messages to MessageHandler on the ISOLATE
DartUtils::PostInt32(port, event_mask); }}}if (interrupt_seen) {
// Handle after socket events, so we avoid closing a socket before we handle
// the current events.
HandleInterruptFd();
}
}
Copy the code
In the creation of EventHandler, it is the creation of new threads and the use of epoll. In the upper layer, EventHandler is used to execute scheduled socket and Timer tasks.
In IOS, kQueue is used to achieve asynchronous IO.
3. Dartui-related initialization
After EventHandler is successfully created, uI-related classes will be registered in C/C++ layer, so that the framework layer can call native methods smoothly. In Java, a similar registration is done if you want to call native methods.
void DartUI::InitForGlobal(a) {
if(! g_natives) { g_natives =new tonic::DartLibraryNatives(a); Canvas::RegisterNatives(g_natives);
CanvasGradient::RegisterNatives(g_natives);
CanvasImage::RegisterNatives(g_natives);
CanvasPath::RegisterNatives(g_natives);
CanvasPathMeasure::RegisterNatives(g_natives);
Codec::RegisterNatives(g_natives);
ColorFilter::RegisterNatives(g_natives);
DartRuntimeHooks::RegisterNatives(g_natives);
EngineLayer::RegisterNatives(g_natives);
FontCollection::RegisterNatives(g_natives);
FrameInfo::RegisterNatives(g_natives);
ImageFilter::RegisterNatives(g_natives);
ImageShader::RegisterNatives(g_natives);
IsolateNameServerNatives::RegisterNatives(g_natives);
Paragraph::RegisterNatives(g_natives);
ParagraphBuilder::RegisterNatives(g_natives);
Picture::RegisterNatives(g_natives);
PictureRecorder::RegisterNatives(g_natives);
Scene::RegisterNatives(g_natives);
SceneBuilder::RegisterNatives(g_natives);
SemanticsUpdate::RegisterNatives(g_natives);
SemanticsUpdateBuilder::RegisterNatives(g_natives);
Vertices::RegisterNatives(g_natives);
Window::RegisterNatives(g_natives);
#if defined(OS_FUCHSIA)
SceneHost::RegisterNatives(g_natives);
#endif
// Secondary isolates do not provide UI-related APIs.
g_natives_secondary = new tonic::DartLibraryNatives(a); DartRuntimeHooks::RegisterNatives(g_natives_secondary);
IsolateNameServerNatives::RegisterNatives(g_natives_secondary); }}Copy the code
You can see that many uI-related native methods are registered in the InitForGlobal function.
4. Initialize the Dart VM
Finally, look at the initialization of the Dart VM.
char* Dart::Init(const uint8_t* vm_isolate_snapshot,
const uint8_t* instructions_snapshot,
Dart_IsolateGroupCreateCallback create_group,
Dart_InitializeIsolateCallback initialize_isolate,
Dart_IsolateShutdownCallback shutdown,
Dart_IsolateCleanupCallback cleanup,
Dart_IsolateGroupCleanupCallback cleanup_group,
Dart_ThreadExitCallback thread_exit,
Dart_FileOpenCallback file_open,
Dart_FileReadCallback file_read,
Dart_FileWriteCallback file_write,
Dart_FileCloseCallback file_close,
Dart_EntropySource entropy_source,
Dart_GetVMServiceAssetsArchive get_service_assets,
bool start_kernel_isolate,
Dart_CodeObserver* observer) {...// Initialize the stack frame layout
FrameLayout::Init(a);set_thread_exit_callback(thread_exit);
SetFileCallbacks(file_open, file_read, file_write, file_close);
set_entropy_source_callback(entropy_source);
// System specific initialization, for different systems to do adaptation
OS::Init(a); . start_time_micros_ = OS::GetCurrentMonotonicMicros(a);// Virtual memory initialization, the default is to get the size of a page
VirtualMemory::Init(a);//OSThread thread initialization, create a Dart_Initialize OSThread and set it to TLS
OSThread::Init(a);/ / zone initialization
Zone::Init(a); .// Reset some callbacks of the ISOLATE to NULL, such as create_group_callback_ and initialize_callback_
Isolate::InitVM(a);/ / IsolateGroup initialization
IsolateGroup::Init(a);//PortMap initializes to create a map with initial capacity of 8
PortMap::Init(a);// Initialize a FreeListElement, mainly to make some assert judgments
FreeListElement::Init(a);//ForwardingCorpse is initialized to make some assert judgments
ForwardingCorpse::Init(a);/ / Api initialization
Api::Init(a);// Depending on the operating system, there are currently no implementations
NativeSymbolResolver::Init(a);//SemiSpace initialization, SemiSpace is mainly used for memory management, the new generation consists of two SemiSpace
SemiSpace::Init(a);NOT_IN_PRODUCT(Metric::Init());
StoreBuffer::Init(a); MarkingStack::Init(a); .// Create the read-only handles area.
predefined_handles_ = new ReadOnlyHandles(a);// Create the VM isolate and finish the VM initialization.
Create a thread pool
thread_pool_ = new ThreadPool(a); {// The ISOLATE to be created is vm ISOLATE
const bool is_vm_isolate = true;
// Cache value of "non-nullable" experimental flag.
set_non_nullable_flag(KernelIsolate::GetExperimentalFlag("non-nullable"));
// Setup default flags for the VM isolate.
Dart_IsolateFlags api_flags;
Isolate::FlagsInitialize(&api_flags);
// Create a pseudo-isolateGroupSource object here, because the VM-ISOLATE to be created is not a real ISOLATE object. It acts more as a container for VM global objects.
std::unique_ptr<IsolateGroupSource> source(
new IsolateGroupSource(nullptr, kVmIsolateName, vm_isolate_snapshot,
instructions_snapshot, nullptr.- 1, api_flags));
auto group = new IsolateGroup(std::move(source), /*embedder_data=*/nullptr);
IsolateGroup::RegisterIsolateGroup(group);
// Create an ISOLATE object named VM-ISOLATE
vm_isolate_ =
Isolate::InitIsolate(kVmIsolateName, group, api_flags, is_vm_isolate);
group->set_initial_spawn_successful(a); Thread* T = Thread::Current(a);StackZone zone(T);
HandleScope handle_scope(T);
// Create a NULL object, which is the first object created by the Dart VM
Object::InitNull(vm_isolate_);
// Create an ObjectStore object that stores each ISOLATE's references to objects in the Dart VM
ObjectStore::Init(vm_isolate_);
// Initializes CPU information
TargetCPUFeatures::Init(a);// Initialize the object.
Object::Init(vm_isolate_);
ArgumentsDescriptor::Init(a); ICData::Init(a); SubtypeTestCache::Init(a);if(vm_isolate_snapshot ! =NULL) {... }else{... }// Due to bootstrapping, constants need to be initialized in the thread where the VM ISOLATE is located
T->InitVMConstants(a); { Object::FinalizeVMIsolate(vm_isolate_);
}
}
Api::InitHandles(a);// Unregister the VM ISOLATE in this thread
Thread::ExitIsolate(a); Isolate::SetCreateGroupCallback(create_group);
Isolate::SetInitializeCallback_(initialize_isolate);
Isolate::SetShutdownCallback(shutdown);
Isolate::SetCleanupCallback(cleanup);
Isolate::SetGroupCleanupCallback(cleanup_group); .return NULL;
}
Copy the code
Detailed comments are made in the above code. As you can see, there are quite a few operations that need to be done during Dart VM initialization, For example, initialization of stack frame layout, virtual memory, OSThread, NULL objects and other shared objects, PortMap, thread pool, vM-ISOLATE, etc.
This article focuses on VM-ISOLATE. It is an ISOLATE, but it is very different from normal isolate, so it is not a real isolate, but a pseudo isolate. The main differences are as follows:
- general
isolate
The heap is divided into Cenozoic and old, and Cenozoicfrom
withto
The ratio is 1:1, and newly created objects are almost always allocated into
In the. whilevm-isolate
In the heap of, the space size of Cenozoic is 0, that is, there is no Cenozoic. Therefore, all objects are assigned directly to the old age. isolate
Objects in similar processes cannot refer to each other. But for thevm-isolate
The exception is that the heap contains immutable objects such as NULL, true, false, and othersisolate
You can referencevm-isolate
Objects in the heap.
For details about how to create VM-ISOLATE, see the article Understanding the MECHANISM of ISOLATE in Depth.
The Dart VM startup flowchart is shown below, which is based on the startup process of the Flutter engine
After the Dart VM is successfully initialized, it means that the Dart VM has been successfully started and used in the same process. No matter how many times the Engine is created, the Dart VM is created only once in the same process.
【 References 】
Introduction to Dart VM
In-depth understanding of Dart VIRTUAL machine startup
Flutter: Don’t Fear the Garbage Collector
ARM function call process analysis