比特币源代码--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)将重新打开调试日志打印文件。
(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命令,直接终止程序的方式解决内存分配失败导致的错误,并且进行日志打印。