.NET / Rotor 源码研究2 – PAL Initialization / Termination

1.     Introduction
PAL (Platform Adaptation Layer) 是Rotor得以在多种平台之间移植的关键所在。不同操作系统的API区别非常大,很难简单用#if/#else/#endif之类的语句进行条件编译来解决不同操作系统API之间互不兼容的问题,Rotor的解决方法是创建一个兼容层PAL,建立在PAL之上的程序无需关心面对的是何种操作系统,只需调用PAL提供的服务即可,在底层,PAL调用被转换成相应的操作系统相关的API调用,便可以解决此类问题。如果需要支持其他操作系统,只需为此操作系统写一个PAL实现,无需重写所有代码。Rotor的PAL设计和Win32 API十分接近,是Win32 API的一个子集,因此Win32 PAL的实现相对于*NIX版的PAL要简单很多,而对于*NIX来说,由于操作系统设计和Win32 API非常不同,需要做大量工作。PAL在架构上并无特别之处,只是简单的C函数,根据动态连接库实现的不同而不同,并没有使用通常C++所常用的Factory方式。 由于篇幅和个人精力有限,以后文章的的讨论只限于 Win32 版本PAL 实现。
PAL的主要组成部分如下:
DLL
Description
rotor_pal.dll
和平台相关的PAL的实现
rotor_palrt.dll
PAL Runtime,和平台无关的公共代码都在这里。比如加密/解密,资源,Assert,String等等
rotor_palrt_s.dll
静态GUID的定义
 
2.     PAL_Initialization & PAL_Termination
PAL的Initialization/Termination是通过PAL_Initialize/PAL_Terminate来完成的。PAL_Initialize/PAL_Terminate只是PAL_Startup & PAL_Shutdown的一层Thin Wrapper,其主要作用在于维护一个PalReferenceCount,避免对PAL_Startup/PAL_Shutdown的重复调用。为了并发安全性,PalReferenceCount的++/--是通过InterlockedIncrement/InterlockedDecrement进行的。PAL_Initialize代码如下:
PALIMPORT
int
PALAPI
PAL_Initialize(
            int argc,
            char *argv[])
{   
    int RetVal = 0;
    LONG RefCount;
 
    RefCount = InterlockedIncrement(&PalReferenceCount);
 
 
   if (RefCount == 1)
   {
 
        RetVal = PAL_Startup(argc, argv);
 
 
    }
    LOGAPI("PAL_Initialize(argc=0x%x, argv=%p)/n", argc, argv);
 
 
    LOGAPI("PAL_Initialize returns int 0x%x/n", RetVal);
    return RetVal;
}
 
PAL_Startup负责初始化PAL:
1.       首先PAL_Startup获得rotor_pal.dll的FullPathName:
Int
PALAPI
PAL_Startup(
            int argc,
            char *argv[])
{
    int RetVal = -1;
    HMODULE hMod;
    WCHAR ModulePath[_MAX_PATH];
 
    hMod = GetModuleHandleW(L"rotor_pal.dll");
    if (!hMod) {
        return RetVal;
    }
 
    if (!GetModuleFileNameW(hMod,
                            ModulePath,
                            sizeof(ModulePath)/sizeof(WCHAR))) {
        return RetVal;
    }
2.       设置ErrorMode,从而在无法打开文件的和CriticalError的情况下不显示错误对话框,直接返回错误。
3.       调用RegisterWithEventLog在注册表中注册Rotor,这样RegisterEventSource才可以注册Rotor这个Source,ReportEvent才能工作。具体请参看MSDN。
4.       调用InitializeObjecNameMangler初始化NameMangler。NameMangler的作用是根据rotor_pal.dll的所在位置用SHA1算法计算出一个Hash值,再转换为一个合法路径名。在很多需要名字的地方,如CreateEvent, CreateMutex…等均会将传入的名字进行Mangle处理,从而使得这些名字在不同的rotor_pal.dll实例中互不冲突。详情下面会有一节专门讲述。
    SetErrorMode(SEM_NOOPENFILEERRORBOX|SEM_FAILCRITICALERRORS);
 
    RegisterWithEventLog(ModulePath);
 
    if (!InitializeObjectNameMangler(ModulePath)) {
        return RetVal;
    }
5.       接下来,仅当在调试版本中,根据PAL_API_TRACING的值设置LogFileHandle打开对PAL API的跟踪输出。PAL_API_TRACING 可以是stdout, stderr, debugger或者指定文件名,PAL通过PalLogApi, PalLogApiCore, PalAssertFired函数来调用WriteFile(LogFileHandle)或OutputDebugString(当PAL_API_TRACING的值为debugger时)来输出TRACE的内容。
#if DBG
        //
        // Do one-time initialization of the API tracing mechanism
        //
    {
        DWORD dw;
        WCHAR buffer[_MAX_PATH];
 
        dw = GetEnvironmentVariableW(L"PAL_API_TRACING",
                                     buffer,
                                     sizeof(buffer)/sizeof(WCHAR));
        if (dw != 0 && dw < sizeof(buffer)/sizeof(WCHAR)) {
            // Environment variable exists and fit in the buffer
            if (wcscmp(buffer, L"stdout") == 0) {
                LogFileHandle = GetStdHandle(STD_OUTPUT_HANDLE);
            } else if (wcscmp(buffer, L"stderr") == 0) {
                LogFileHandle = GetStdHandle(STD_ERROR_HANDLE);
            } else if (wcscmp(buffer, L"debugger") == 0) {
                LogFileHandle = 0;
            } else {
                LogFileHandle = CreateFileW(buffer,
                                            GENERIC_WRITE,
                                            FILE_SHARE_READ,
                                            NULL,
                                            CREATE_ALWAYS,
                                            FILE_ATTRIBUTE_NORMAL,
                                            NULL);
            }
        }
    }
#endif //DBG
 
6.       之后,安装PAL自己的Exception Filter,这样PAL便可以接管对Unhandled Exceptions的处理,下篇文章会详细解释
7.       获得当前线程一个TLS(Thread Local Storage)的Slot,被SEH用到,同样,下篇文章会详细讲到
8.       检查PAL_TRY_LOCAL_SIZE,在下篇文章会解释
9.       设置浮点运算的控制字(Control Word)
    // Note: MSVCRT has already registered their exception filter at this point
    // SEH_CurrentTopLevelFilter won't be NULL
 
    SEH_CurrentTopLevelFilter = (PAL_LPTOP_LEVEL_EXCEPTION_FILTER)
        SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)PAL_ExceptionFilter);
 
    if ((SEH_Tls = TlsAlloc()) == TLS_OUT_OF_INDEXES) {
        goto LExit;
    }
 
    PALASSERT(PAL_TRY_LOCAL_SIZE >= sizeof(jmp_buf));
 
 
    // exceptions masked, round to nearest, 53 bit precision.
    _controlfp(_MCW_EM | _RC_NEAR | _PC_53 | _IC_PROJECTIVE | _DN_SAVE,
               _MCW_EM | _MCW_RC | _MCW_PC | _MCW_IC | _MCW_DN);
 
    RetVal = 0;
 
LExit:
    return RetVal;
}
 
 
PAL_Shutdown则是Shutdown PAL:
1.       释放NameManager所占用的内存和资源
2.       释放SEH Tls所占用的TLS 的Slot,恢复之前的Exception Filter
3.       在调试版本中,关闭LogFileHandle
void
PALAPI
PAL_Shutdown(
              void)
{
    PAL_LPTOP_LEVEL_EXCEPTION_FILTER PreviousFilter;
 
    if (hCryptProv != NULL) {
        CryptReleaseContext(hCryptProv, 0);
        hCryptProv = NULL;
    }
 
    free(NameManglerA);
    NameManglerA = NULL;
    free(NameManglerW);
    NameManglerW = NULL;
 
 
    if (SEH_Tls != TLS_OUT_OF_INDEXES) {
        TlsFree(SEH_Tls);
    }
 
    // Reset the global exception filter
    PreviousFilter = (PAL_LPTOP_LEVEL_EXCEPTION_FILTER)
        SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)SEH_CurrentTopLevelFilter);
 
#if DBG
    // Do this after the LOGAPI, so the output gets logged
    if (LogFileHandle != INVALID_HANDLE_VALUE) {
        //
        // Refcount is zero and logging is enabled - close the
        // file handle now in preparation for unload.
        //
        if (LogFileHandle) {
            CloseHandle(LogFileHandle);
        }
        LogFileHandle = INVALID_HANDLE_VALUE;
    }
#endif
}
 
 
3.     Application Startup
下面以clix (Managed Application Launcher)为例,说明用到了PAL的程序的启动过程:
由于clix引用了rotor_pal.dll和rotor_palrt.dll,在clix的main执行之前,rotor_pal.dll和rotor_palrt.dll的DllMain会相继执行:
首先是rotor_pal.dll的DllMain被执行,当Dll在进程中被Load的时候 (DLL_PROCESS_ATTACH) 记录Dll的HINSTANCE,并调用DisableThreadLibraryCalls避免DllMain在线程创建 (DLL_THREAD_ATTACH) 和销毁 (DLL_THREAD_DETACH) 的时候被调用以提高速度:
// This is the main DLL entrypoint
BOOL
WINAPI
DllMain(
        HANDLE hDllHandle,
        DWORD dwReason,
        LPVOID lpReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH) {
        hInstPal = (HINSTANCE)hDllHandle;
        DisableThreadLibraryCalls(hDllHandle);
    }
    return TRUE;
}
之后,rotor_palrt.dll的DllMain被执行:
1.       DLL_PROCESS_ATTACH的时候
a.       调用ROTOR_PAL_CTOR_TEST_RUN验证静态构造函数已经在DllMain之前执行,估计是部分编译器并没有遵循标准,有此宏可以更加容易定位此类错误。可参考rotor_palrt.h中ROTOR_PAL_CTOR_BODY和ROTOR_PAL_CTOR_TEST_RUN的定义
b.      调用PAL_Initialize,这是对PAL_Initialize的第一次调用,会调用PAL_Startup。注意argc和argv均传了NULL,这是因为在Win32的PAL实现中,PAL_Initialize不需要argc和argv。由于Unix/Linux实现下需要用到argc/argv来初始化一些信息,这些信息只能由外部传入得到,因此PAL_Initialize函数还是需要argc/argv这两个参数。
c.       为APP_DATA指针分配一个TLS (Thread Local Storage)的Slot g_itlsAppData, 这个Slot对于任何一个Thread都是一样的。APP_DATA的作用是保存CNumInfo的指针(和Thread相关的数字表示的设置,和culture对应起来)和IErrorInfo接口指针(记录错误信息)。
d.      调用InitNumInfo(TRUE),参数为TRUE表明是做初始化,FALSE表明则是释放,两种情况下InitNumInfo做的事情不同,TRUE则初始化CrticialSection,在CNumInfo内部用到。FALSE则释放CriticalSection和CNumInfo中的一些缓存信息,具体可以参考palrt/src/convert.cpp
extern "C"
BOOL WINAPI DllMain( HINSTANCE hInst, DWORD dwReason, LPVOID pvReserved )
{
 
    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:
            ROTOR_PAL_CTOR_TEST_RUN(ROTOR_PALRT);
            // initialize PAL
            if (PAL_Initialize(NULL, NULL) != 0)
                return FALSE;
 
            // initialize APP_DATA
            if ((g_itlsAppData = TlsAlloc()) == ITLS_EMPTY) {
                return FALSE;
            }
            InitNumInfo(TRUE);
            break;
2.       DLL_PROCESS_DETACH
a.       调用ReleaseAppData释放g_itlsAppData对应的TLS的Slot中保存的APP_DATA指针
b.      调用InitNumInfo释放CNumInfo中的Cache
c.       调用PAL_Terminate
3.       DLL_THREAD_DETACH的时候,调用ReleaseAppData释放和此线程相关的APP_DATA。
 
        case DLL_PROCESS_DETACH:
            ReleaseAppData();
            InitNumInfo(FALSE);
            if (g_itlsAppData != ITLS_EMPTY) {
                TlsFree(g_itlsAppData);
            }
 
            PAL_Terminate();
            break;
 
        case DLL_THREAD_ATTACH:
            break;
 
        case DLL_THREAD_DETACH:
            ReleaseAppData();
            break;
    }
    return TRUE;
}
在rotor_pal.dll和rotor_palrt.dll的DllMain被执行完毕之后,palrt/palstartup.h中的main被调用。注意clix.cpp中的main并不是真正的main,main已经被定义成了PAL_startup_main,而真正的main则是在palstartup.h之中定义的:
int __cdecl main(int argc, char **argv) {
    struct _mainargs mainargs;
 
#ifdef _MSC_VER
    if (PAL_Initialize(0, NULL)) {
        return 1;
    }
#else
    if (PAL_Initialize(argc, argv)) {
        return 1;
    }
#endif
 
    // PAL_Terminate is a stdcall function, but it takes no parameters
    // so the difference doesn't matter.
    atexit((void (__cdecl *)(void)) PAL_Terminate);
 
    mainargs.argc = argc;
    mainargs.argv = argv;
 
 
    exit((int)PAL_EntryPoint((PTHREAD_START_ROUTINE)run_main, &mainargs));
    return 0;   // Quiet a compiler warning
}
main作了如下几件事情:
1.初始化PAL
2.调用atexit注册退出函数PAL_Terminate,这样PAL_Terminate便会在程序正常结束的时候被调用。注意虽然PAL_Terminate和atexit所要求的函数调用协定不同,PAL_Terminate是__stdcall而atexit参数要求__cdecl,由于PAL_Terminate是无参数的,__stdcall/__pascal在无参数的时候是没有区别的,因此可以直接强制转换成__cdecl
3.调用PAL_EntryPoint入口函数,此函数调用run_main,并最后调用PAL_startup_main函数。如上所述,PAL_startup_main才是clix中的main
4.退出时候,自动调用PAL_Terminate
OK,这次就分析到这里。下篇文章会重点分析PAL中的Exception机制。  

 

作者:      ATField
E-Mail:   atfield_zhang@hotmail.com
Blog:     
http://blog.csdn.net/atfield

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值