CLR 在执行您的代码之前执行的 68 件事 (*)






4.98/5 (18投票s)
以下是 CLR 在执行您的任何代码之前执行的 68 件事的列表
由于 CLR 是一个托管环境,运行时中有许多组件需要在执行您的任何代码之前进行初始化。这篇博文将探讨 EE(执行引擎)的启动例程,并详细检查初始化过程。
(*) 68 只是一个大致的数字,具体取决于您使用的运行时版本、启用的功能以及其他一些因素。
Hello World
想象一下,您有一个最简单的 C# 程序,在 CLR 在控制台打印“Hello World”之前必须发生什么?
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
进入 EE(执行引擎)的代码路径
当 .NET 可执行文件运行时,控制通过以下代码路径进入 EE:
- _CorExeMain()(外部入口点)
- _CorExeMainInternal()
- EnsureEEStarted()
- 调用 EEStartup()
- EEStartup()
- EEStartupHelper()
(如果您对在此之前发生的事情感兴趣,即 CLR Host 如何启动运行时,请参阅我之前的博文 dotnet CLI 工具如何运行您的代码)。
这样我们就进入了 EEStartupHelper()
,它在高层上执行以下操作(来自 ceemain.cpp 中的注释)
EEStartup 负责运行时的所有一次性初始化。
它所做的一些亮点包括:
- 创建默认和共享的 AppDomain。
- 加载 mscorlib.dll 并加载基本类型(
System.Object
)。
EE(执行引擎)启动例程的主要阶段
但是,让我们详细看看它做了什么。下面的列表包含从 EEStartupHelper() (~500 行代码) 进行的所有单独函数调用。为了使它们更容易理解,我们将它们分成不同的阶段。
- 阶段 1 - 设置所有组件运行之前需要就绪的基础设施
- 阶段 2 - 初始化核心、低级组件
- 阶段 3 - 启动低级组件,即错误处理、性能分析 API、调试
- 阶段 4 - 启动主要组件,即垃圾回收器 (GC)、AppDomain、安全性
- 阶段 5 - 最终设置,然后通知其他组件 EE 已启动
注意,下面列表中的某些项目仅在特定的 功能 在 构建时 定义时包含,这些项目通过包含 ifdef
语句来指示。另请注意,链接指向的是被调用函数的代码,而不是 EEStartupHelper()
中的代码行。
阶段 1 - 设置所有组件运行之前需要就绪的基础设施
- 连接控制台处理 - SetConsoleCtrlHandler(..) (
ifndef FEATURE_PAL
) - 初始化内部
SString
类(所有内容都使用string
!)- SString::Startup() - 确保配置已设置,以便可以访问控制运行时选项的设置 - EEConfig::Set-up() 和 InitializeHostConfigFile() (
#if !defined(CROSSGEN_COMPILE)
) - 初始化 NUMA 和 CPU 组信息 - NumaNodeInfo::InitNumaNodeInfo() 和 CPUGroupInfo::EnsureInitialized() (
#ifndef CROSSGEN_COMPILE
) - 根据启动标志初始化全局配置设置 - InitializeStartupFlags()
- 设置线程管理器,它为运行时提供了对操作系统线程功能的访问(
StartThread()
、Join()
、SetThreadPriority()
等)- InitThreadManager() - 初始化 事件跟踪 (ETW) 并触发 CLR 启动事件 - InitializeEventTracing() 和 ETWFireEvent(EEStartupStart_V1) (
#ifdef FEATURE_EVENT_TRACE
) - 设置 GS Cookie(缓冲区安全检查) 以帮助防止缓冲区溢出 - InitGSCookie()
- 创建用于保存堆栈跟踪的帧的数据结构 - Frame::Init()
- 确保初始化Apphack 环境变量 - GetGlobalCompatibilityFlags() (
#ifndef FEATURE_CORECLR
) - 创建运行时使用的诊断和性能日志 - InitializeLogging() (
#ifdef LOGGING
) 和 PerfLog::PerfLogInitialize() (#ifdef ENABLE_PERF_LOG
)
阶段 2 - 初始化核心、低级组件
- 写入日志
===================EEStartup Starting===================
- 确保运行时库函数(与 ntdll.dll 交互)已启用 - EnsureRtlFunctions() (
#ifndef FEATURE_PAL
) - 设置用于运行时内部同步的全局事件存储(互斥锁、信号量)- 事件(互斥锁、信号量) - InitEventStore()
- 创建程序集绑定日志记录机制,也称为 Fusion - InitializeFusion() (
#ifdef FEATURE_FUSION
) - 然后初始化实际的程序集绑定器基础结构 - CCoreCLRBinderHelper::Init(),它反过来调用 AssemblyBinder::Startup() (
#ifdef FEATURE_FUSION
未定义) - 设置用于控制监视器、Crst 和 SimpleRWLocks 的启发式算法 - 监视器、Crst 和 SimpleRWLocks - InitializeSpinConstants()
- 初始化进程间通信 (IPC) - InitializeIPCManager() (
#ifdef FEATURE_IPCMAN
) - 设置并启用性能计数器 - PerfCounters::Init() (
#ifdef ENABLE_PERF_COUNTERS
) - 设置CLR 解释器 - Interpreter::Initialize() (
#ifdef FEATURE_INTERPRETER
),原来 CLR 有一种模式,您的代码不是被编译而是被解释的! - 初始化 CLR 用于调用方法和触发 JIT 的存根 - StubManager::InitializeStubManagers(),还有 Stub::Init() 和 StubLinkerCPU::Init()
- 设置用于加载程序集到内存的核心句柄映射 - PEImage::Startup()
- 启动访问检查选项,用于在方法调用时授予/拒绝安全性要求 - AccessCheckOptions::Startup()
- 启动mscorlib 绑定器(用于从 mscorlib.dll 加载已知类型)- MscorlibBinder::Startup()
- 初始化远程处理,它允许进程外通信 - CRemotingServices::Initialize() (
#ifdef FEATURE_REMOTING
) - 设置 GC 用于弱引用、强引用和无固定引用的数据结构 - Ref_Initialize()
- 设置用于跨 AppDomain 代理方法调用的上下文 - Context::Initialize()
- 连接允许 EE同步关闭的事件 -
g_pEEShutDownEvent->CreateManualEvent(FALSE)
- 初始化用于读写锁实现的进程范围数据结构 - CRWLock::ProcessInit() (
#ifdef FEATURE_RWLOCK
) - 初始化调试器管理器 - CCLRDebugManager::ProcessInit() (
#ifdef FEATURE_INCLUDE_ALL_INTERFACES
) - 初始化CLR 安全属性管理器 - CCLRSecurityAttributeManager::ProcessInit() (
#ifdef FEATURE_IPCMAN
) - 设置虚拟调用存根管理器 - VirtualCallStubManager::InitStatic()
- 初始化 GC 在控制内存压力时使用的锁 - GCInterface::m_MemoryPressureLock.Init(CrstGCMemoryPressure)
- 初始化程序集使用日志记录器 - InitAssemblyUsageLogManager() (
#ifndef FEATURE_CORECLR
)
阶段 3 - 启动低级组件,即错误处理、性能分析 API、调试
- 设置 CLR 使用的AppDomain - SystemDomain::Attach()(还通过调用 SystemDomain::CreateDefaultDomain() 和 SharedDomain::Attach() 创建了
DefaultDomain
和SharedDomain
) - 启动ECall 接口,这是 CLR 内部使用的私有本机调用接口 - ECall::Init()
- 设置用于
delegate
的存根缓存 - COMDelegate::Init() - 设置EE 本身使用的所有全局/静态变量 - ExecutionManager::Init()
- 初始化Watson,用于 Windows 错误报告 - InitializeWatson(fFlags) (
#ifndef FEATURE_PAL
) - 初始化调试服务,这必须在创建任何 EE 线程对象以及加载任何类或模块之前完成 - InitializeDebugger() (
#ifdef DEBUGGING_SUPPORTED
) - 激活 CLR 提供的托管调试助手 - ManagedDebuggingAssistants::EEStartupActivation() (
ifdef MDA_SUPPORTED
) - 初始化性能分析 API - ProfilingAPIUtility::InitializeProfiling() (
#ifdef PROFILING_SUPPORTED
) - 初始化异常处理机制 - InitializeExceptionHandling()
- 安装 CLR 全局异常过滤器 - InstallUnhandledExceptionFilter()
- 确保创建初始运行时线程 - SetupThread() 反过来调用 SetupThread(..)
- 初始化PreStub 管理器(PreStub 触发 JIT)- InitPreStubManager() 和相应的帮助程序 StubHelpers::Init()
- 初始化COM 互操作层 - InitializeComInterop() (
#ifdef FEATURE_COMINTEROP
) - 初始化NDirect 方法调用(非托管 P/Invoke 目标的惰性绑定)- NDirect::Init()
- 设置JIT 辅助函数,以便在执行管理器运行之前就位 - InitJITHelpers1() 和 InitJITHelpers2()
- 初始化并设置SyncBlock 缓存 - SyncBlockCache::Attach() 和 SyncBlockCache::Start()
- 创建用于行走/展开堆栈的缓存 - StackwalkCache::Init()
阶段 4 - 启动主要组件,即垃圾回收器 (GC)、AppDomain、安全性
- 启动安全系统,处理 代码访问安全性 (CAS) - Security::Start(),它反过来调用 SecurityPolicy::Start()
- 连接一个事件以允许同步 AppDomain 卸载 - AppDomain::CreateADUnloadStartEvent()
- 初始化用于设置堆栈保护程序的堆栈探测器 - InitStackProbes() (
#ifdef FEATURE_STACK_PROBE
) - 初始化GC 并创建它使用的堆 - InitializeGarbageCollector()
- 初始化用于保存固定对象位置的表** - InitializePinHandleTable()
- 通知调试器有关 DefaultDomain 的信息,以便它可以与之交互 - SystemDomain::System()->PublishAppDomainAndInformDebugger(..) (
#ifdef DEBUGGING_SUPPORTED
) - 初始化现有的OOB 程序集列表(不知道是什么?)- ExistingOobAssemblyList::Init() (
#ifndef FEATURE_CORECLR
) - 实际初始化System Domain(包含 mscorlib),以便它可以开始执行 - SystemDomain::System()->Init()
阶段 5 最终设置,然后通知其他组件 EE 已启动
- 通知性能分析器已启动 - SystemDomain::NotifyProfilerStartup() (
#ifdef PROFILING_SUPPORTED
) - 预先创建一个线程来处理 AppDomain 卸载 - AppDomain::CreateADUnloadWorker() (
#ifndef CROSSGEN_COMPILE
) - 设置一个标志以确认EE 初始化成功 -
g_fEEInit = false
- 将系统程序集(mscorlib)加载到 Default Domain - SystemDomain::System()->DefaultDomain()->LoadSystemAssemblies()
- 在Default Domain 中设置所有共享静态变量(以及
String.Empty
) - SystemDomain::System()->DefaultDomain()->SetupSharedStatics(),它们都包含在内部类 SharedStatics.cs 中 - 设置堆栈采样器功能,该功能可识别您代码中的热点方法 - StackSampler::Init() (
#ifdef FEATURE_STACK_SAMPLING
) - 执行任何一次性 SafeHandle 初始化 - SafeHandle::Init() (
#ifndef CROSSGEN_COMPILE
) - 设置标志以指示CLR 已成功启动 -
g_fEEStarted = TRUE
、g_EEStartupStatus = S_OK
和hr = S_OK
- 写入日志
===================EEStartup Completed===================
完成这一切后,CLR 就已准备好执行您的代码了!
执行您的代码
您的代码将被执行(在首次 JIT 编译后)通过以下代码流:
更多信息
如果您创建一个 调试版本,然后启用 正确的环境变量,CLR 会提供大量的日志信息。以下链接指向运行一个简单的 hello world 程序(如本文顶部所示)时生成的各种日志,它们能让您相当清楚地了解 CLR 在后台所做的各种事情。
- 所有类已加载
- 所有方法已 JIT 编译
- 完整日志(警告:约 68K 行!!)
- 仅在 EEStartupHelper() 期间生成的日志(仅约 48K 行!!)
- AppDomain 日志
- 类加载器日志
- 仅针对
ConsoleApplication
的类加载器日志 - 代码共享日志
- 核心调试日志
- 异常处理日志
- JIT 日志
- 加载器日志
博文 CLR 在执行您的代码之前执行的 68 件事 (*) 最初出现在我的博客 Performance is a Feature! 上。