比特币源代码--9--bitcoind(main函数)初始化基本环境构建—— AppInitBasicSetup

当前位置:bitcoind.cpp=>main=>AppInit(132)

        if (!AppInitBasicSetup())
        {
            // InitError will have been called with detailed error, which ends up on console
            exit(EXIT_FAILURE);
        }

AppInitBasicSetup()函数是注册相应的消息以及处理方式。位于src/init.cpp(855)

一、 警告消息处理

#ifdef _MSC_VER
    // Turn off Microsoft heap dump noise
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, 0));
    // Disable confusing "helpful" text message on abort, Ctrl-C
    _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
#endif

根据第一行代码 #ifdef _MSC_VER 可以得知,这段代码只在微软的visual studio 编译环境才执行。上述这段代码中调用了2个开发环境相关的函数_CrtSetReportMode和_CrtSetReportFile。

_CrtSetReportMode

函数是设置消息的处理方式。参考:https://www.jianshu.com/p/d42558e7e8e8

int _CrtSetReportMode(
   int reportType,
   int reportMode
);

reportType: 报告类型

l_CRT_WARN:警告、消息和不需要立即关注的信息。

l_CRT_ERROR:错误、不可恢复的问题和需要立即关注的问题。

l_CRT_ASSERT:断言失败(断言表达式的计算结果为FALSE)。

reportMode: 报告模式

l_CRTDBG_MODE_DEBUG:将消息写入调试器的输出窗口。

l_CRTDBG_MODE_FILE:将消息写入用户提供的文件句柄。_CrtSetReportFile应调用以定义要用作目标流的特定文件。

l_CRTDBG_MODE_WNDW:创建一个消息框,以显示该消息以及Abort,Retry,和Ignore按钮。

l_CRTDBG_REPORT_MODE:返回reportMode指定reportType:

1 _CRTDBG_MODE_FILE、

2_CRTDBG_MODE_DEBUG、

4_CRTDBG_MODE_WNDW

消息的报告类型为警告,输出方式为文件。那么接下来就需要定义输出到哪个文件,即轮到函数_CrtSetReportFile 

_CrtSetReportFile

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/crtsetreportfile?view=vs-2017

_HFILE _CrtSetReportFile(
   int reportType,
   _HFILE reportFile
);

在当前代码中使用CreateFileA("NUL", GENERIC_WRITE, 0,NULL, OPEN_EXISTING, 0, 0)实现了输出文件的创建。但是其第一个参数值(文件名)为“NULL”,则说明这个文件为空,警告消息虽然说是输出至文件中,但当前为空文件,那么警告消息的输出可理解为被关闭了。这点可从代码注释找到依据:// Turn off Microsoft heap dump noise,即关闭微软内存堆快照的“噪音”,这里的噪音应该就是指此处的警告消息了。

二、异常处理

函数 _set_abort_behavior 是当程序发生异常时采取的处理操作。

一般来说软件通过设置自己的异常捕获函数,捕获未处理的异常,生成报告或者日志(例如生成mini-dump文件),达到Release版本下追踪Bug的目的。但是,到了VS 2005(VC 8),Microsoft对 CRT(C运行时库)的一些与安全相关的代码做了些改动,例如增加了对缓冲溢出的检查。新CRT版本在出现错误时强制把异常抛给默认的调试器(如果没有配置的话,默认是Dr.Watson),而不再通知应用程序设置的异常捕获函数,这种行为主要在以下三种情况出现。

(1)调用abort函数,并且设置了_CALL_REPORTFAULT选项(这个选项在Release版本是默认设置的)。

(2)启用了运行时安全检查选项,并且在软件运行时检查出安全性错误,例如出现缓存溢出(安全检查选项/GS默认也是打开的)。

(3)遇到_invalid_parameter错误,而应用程序又没有主动调用

_set_invalid_parameter_handler设置错误捕获函数。

所以使用VS2005(VC8,代码中的宏定义为_MSC_VER >= 1400)编译的程序,许多错误都不能在SetUnhandledExceptionFilter捕获到。这是CRT相对于前面版本的一个比较大的改变,但是很遗憾,Microsoft却没有在相应的文档明确指出。

可以通过_set_abort_behavior(0, _WRITE_ABORT_MSG |_CALL_REPORTFAULT)解决(1),也就是abort函数异常错误捕获问题。详细解释参考:http://www.cppblog.com/woaidongmao/archive/2009/10/21/99129.html?opt=admin

三、数据执行保护(DEP)处理

#ifdef WIN32
    // Enable Data Execution Prevention (DEP)
    // Minimum supported OS versions: WinXP SP3, WinVista >= SP1, Win Server 2008
    // A failure is non-critical and needs no further attention!
#ifndef PROCESS_DEP_ENABLE
    // We define this here, because GCCs winbase.h limits this to _WIN32_WINNT >= 0x0601 (Windows 7),
    // which is not correct. Can be removed, when GCCs winbase.h is fixed!
#define PROCESS_DEP_ENABLE 0x00000001
#endif
    typedef BOOL (WINAPI *PSETPROCDEPPOL)(DWORD);
    PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy");
    if (setProcDEPPol != nullptr) setProcDEPPol(PROCESS_DEP_ENABLE);
#endif

这段代码主要是针对 winow 32 系统中的DEP(Data Execution Prevention,数据执行保护),DEP是WinXP SP3,WinVista >= SP1, Win Server 2008中为防止缓冲区溢出攻击而采用的一种保护措施,目的是保护特定内存中的数据,不会将其当成代码一样执行。 

通过注释我们知道,之所以在这里启用是由于GCC中的winbase.h中做了限制:只有当系统版本满足_WIN32_WINNT >= 0x0601(Win 7)时才会启用DEP,这就导致低版本就默认没有启用DEP,这时就需要手动开启。

启用的方式:调用动态链接库文件Kernel32.dll(https://baike.baidu.com/item/kernel32.dll/4577394?fr=aladdin)寻找到SetProcessDEPPolicy (https://docs.microsoft.com/zh-cn/windows/desktop/api/winbase/nf-winbase-setprocessdeppolicy)函数。

BOOL SetProcessDEPPolicy(
  DWORD dwFlags
);

四、初始化网络连接

  if (!SetupNetworking())
        return InitError("Initializing networking failed");

SetupNetworking函数实现代码于文件src/util.cpp(865),其主要为 window 系统  初始化 socket, 如果是其他系统,则直接返回 true.

bool SetupNetworking()
{
#ifdef WIN32
    // Initialize Windows Sockets
    WSADATA wsadata;
    int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
    if (ret != NO_ERROR || LOBYTE(wsadata.wVersion ) != 2 || HIBYTE(wsadata.wVersion) != 2)
        return false;
#endif
    return true;
}

其中,WSADATA 这个数据结构被用来存储被WSAStartup函数调用后返回的Windows Sockets数据。它包含Winsock.dll执行的数据。

五、信号处理设置

上面的网络设置是针对window系统,而这里的#ifndef WIN32 则是针对非window系统的

(1)文件权限设置

    if (!gArgs.GetBoolArg("-sysperms", false)) {
        umask(077);
    }

在该代码中,程序首先判断是否设置了sysperms参数,该参数的的含义为:

Create new files with system default permissions,instead of umask 077 (only effective with disabled wallet functionality)

在创建新文件时,文件权限为系统默认权限,以此来代替umask 077命令(因为umask 077只在钱包功能被禁止时才其作用)

如果设置了则返回其状态值,如果为false,则需执行umask(077)命令。

umask用于设置文件与文件夹使用权限,此处077代表---rwxrwx,表示owner没有任何权限,group和other有完全的操作权限。

(2)停止信号处理

// Clean shutdown on SIGTERM
    registerSignalHandler(SIGTERM, HandleSIGTERM);
    registerSignalHandler(SIGINT, HandleSIGTERM);

其中,SIGTERM和SIGINT都是结束、终止进程运行。区别

1. SIGINT与 ctrl+c 关联,SIGTERM则没有任何字符关联;

2. SIGINT只能结束前台进程;SIGTERM则不是。

再看参数中的函数HandleSIGTERM,该函数在src/init.cpp(284)中实现,定义如下:

/**
 * Signal handlers are very limited in what they are allowed to do.
 * The execution context the handler is invoked in is not guaranteed,
 * so we restrict handler operations to just touching variables:
 */
//信号控制器限制很多,能做的事情不多,原因在于执行会话调用控制器这种行为是没有保证的,
//所以限制了控制器只能访问变量。
static void HandleSIGTERM(int)
{
    fRequestShutdown = true;
}

将全局变量fRequestShutdown设置为true,所有正在运行的线程将根据一定的规则停止运行。

(3)挂起信号处理

  // Reopen debug.log on SIGHUP
    registerSignalHandler(SIGHUP, HandleSIGHUP);

从代码可以看出挂起信号处理过程与终止信号处理过程是一样的。这里要说明的是句柄函数HandleSIGHUP,该函数在src/init.cpp(294)中实现,其实现代码为

static void HandleSIGHUP(int)
{
    fReopenDebugLog = true;
}

将全局变量fReopenDebugLog设置为true,src/util.cpp中的LogPrintStr(328)将重新打开调试日志打印文件。

比特币源代码--9--bitcoind(main函数)初始化基本环境构建—— AppInitBasicSetup

(4)管道错误处理

// Ignore SIGPIPE, otherwise it will bring the daemon down if the client closes unexpectedly
    signal(SIGPIPE, SIG_IGN);

signal为信号函数,第一个参数表示需要处理的信号值(SIGPIPE,管道错误),第二个参数为处理函数或者是一个表示,此处SIG_IGN表示忽略SIGPIPE那个注册的信号。

此处需设置忽略SIGPIPE管道错误,否则客户端异常关闭时会将守护进程连带着也给关闭,影响守护进程的正常运行。

六、内存分配失败处理

  std::set_new_handler(new_handler_terminate);

1.   set_new_handler函数的作用是设置new_p指向的函数为new操作或new[]操作失败时调用的处理函数。

2.设置的处理函数可以尝试使更多空间变为可分配状态,这样新一次的new操作就可能成功。当且仅当该函数成功获得更多可用空间它才会返回;否则它将抛出bad_alloc异常(或者继承该异常的子类)或者终止程序(例如调用abort或exit)。

3.如果设置的处理函数返回了(例如,该函数成功获得了更多的可用空间),它可能将被反复调用,直到内存分配成功,或者它不再返回,或者被其它函数所替代。

4.在尚未用set_new_handler设置处理函数,或者设置的处理函数为空时,将调用默认的处理函数,该函数在内存分配失败时抛出bad_alloc异常。

set_new_handler需要一个内存分配失败后的处理函数,源码中通过new_handler_terminate()函数完成了该功能,通过new_handler_terminate函数中的注释我们可以知道其为了防止影响区块链被破坏,通过执行terminate命令,直接终止程序的方式解决内存分配失败导致的错误,并且进行日志打印。