本文将继续接着上一篇文章继续开展源码研读之旅,本文将完成初始化基本环境构建源码部分(AppInitBasicSetup)的讲解。
本文主要涉及的源码文件包括:
src/bitcond.cpp、src/util.h、src/util.cpp、src/init.h、src/init.cpp
一、警告消息处理
警告消息处理函数实现代码很简单,只有5行代码
#ifdef _MSC_VER
// Turnoff Microsoft heap dump noise
_CrtSetReportMode(_CRT_WARN,_CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN,CreateFileA("NUL", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0));
#endif
通过#ifdef _MSC_VER,我们可以知道上面这段代码是针对微软的VS开发环境而设置的,在其他编译环境下是这段代码是不会执行的。上述这段代码中调用了2个开发环境相关的函数_CrtSetReportMode和_CrtSetReportFile。
(1)_CrtSetReportMode函数定义如下:
int _CrtSetReportMode(
intreportType,
intreportMode
);
其包含的两个参数分别为报告类型和报告输出模式:
报告类型包含:
l_CRT_WARN:警告、消息和不需要立即关注的信息。
l_CRT_ERROR:错误、不可恢复的问题和需要立即关注的问题。
l_CRT_ASSERT:断言失败(断言表达式的计算结果为FALSE)。。
报告模式包含:
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
由上述分析可以知道当前代码中设置的报告类型为警告,报告输出方式为文件输出。
(2)_CrtSetReportFile函数定义如下:
_HFILE _CrtSetReportFile(
int reportType,
_HFILE reportFile
);
参数reportType与_CrtSetReportMode中的参数一致,都是_CRT_WARN, _CRT_ERROR, and _CRT_ASSERT3种类型,这里就不需要赘述了。
参数reportFile为文件句柄,其对应的文件作为reportType相应消息的输出对象。
在当前代码中使用CreateFileA("NUL", GENERIC_WRITE, 0,NULL, OPEN_EXISTING, 0, 0)实现了输出文件的创建。但是其第一个参数值(文件名)为“NULL”,则说明这个文件为空,警告消息虽然说是输出至文件中,但当前为空文件,那么警告消息的输出可理解为被关闭了。这点可从代码注释找到依据:// Turn off Microsoft heap dump noise,即关闭微软内存堆快照的“噪音”,这里的噪音应该就是指此处的警告消息了。
二、abort函数调用处理
很多软件通过设置自己的异常捕获函数,捕获未处理的异常,生成报告或者日志(例如生成mini-dump文件),达到Release版本下追踪Bug的目的。但是,到了VS2005(即VC8),Microsoft对CRT(C运行时库)的一些与安全相关的代码做了些改动,典型的,例如增加了对缓冲溢出的检查。新CRT版本在出现错误时强制把异常抛给默认的调试器(如果没有配置的话,默认是Dr.Watson),而不再通知应用程序设置的异常捕获函数,这种行为主要在以下3种情况出现。
(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://blog.csdn.net/yuzhiyuxia/article/details/16889155
三、数据执行保护(DEP)功能处理
数据执行保护(DEP)的目的是为了防止病毒或其他安全威胁造成损害,Windows XP SP2、WinXP SP3, WinVista >= SP1, Win Server
2008使用了数据执行保护(DEP)功能,而GCCs winbase.h将该功能限制在_WIN32_WINNT >= 0x0601 (Windows 7)才能使用,所以在代码中强制定义了宏定义
#ifndef PROCESS_DEP_ENABLE
#define PROCESS_DEP_ENABLE 0x00000001
#endif
并且通过函数指针获取Kernel32.dll中的SetProcessDEPPolicy函数对象,实现DEP功能的开启。
typedef BOOL (WINAPI*PSETPROCDEPPOL)(DWORD);
PSETPROCDEPPOL setProcDEPPol =(PSETPROCDEPPOL)GetProcAddress(GetModuleHandleA("Kernel32.dll"),"SetProcessDEPPolicy");
if (setProcDEPPol != NULL)
setProcDEPPol(PROCESS_DEP_ENABLE);
四、初始化网络连接
SetupNetworking函数在src/util.cpp中实现,其主要实现Winsock服务的初始化,初始化的工作通过WSAStartup函数完成。这一步的执行是必须在初始化中完成的,具体原因如下:
为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。
总得来说,Socket连接涉及的API调用,都必须在WSAStartup函数执行之后才有效,否则将无法执行网络连接操作。
五、信号处理设置
通过宏定义判断#ifndef WIN32,我们可以知道信号处理设置是针对非Windows系统的。信号处理设置代码可分为4部分来分析:
(1)文件创建权限
if (!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
struct sigaction sa; //信号处理对象
sa.sa_handler = HandleSIGTERM; //进程终止信号处理句柄
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, NULL); //终止信号处理
sigaction(SIGINT, &sa, NULL); //中断信号处理
从代码可以看到,程序首先定义了信号处理对象sa,然后对其句柄、标志和掩码赋值,最后将该信号对象传递给终止与中断信号处理函数。
这里要说明的是句柄函数HandleSIGTERM,该函数在src/init.cpp中实现,其实现代码为:
void HandleSIGTERM(int)
{
fRequestShutdown = true;
}
很简单的一个函数,将全局变量fRequestShutdown设置为true,所有正在运行的线程将根据一定的规则停止运行。
(3)挂起信号处理
进程挂起信号处理代码如下所示:
// Reopen debug.log on SIGHUP
struct sigaction sa_hup;
sa_hup.sa_handler = HandleSIGHUP; //挂起信号处理句柄函数
sigemptyset(&sa_hup.sa_mask);
sa_hup.sa_flags = 0;
sigaction(SIGHUP, &sa_hup, NULL); //挂起信号处理
从代码可以看出挂起信号处理过程与终止信号处理过程是一样的。这里要说明的是句柄函数HandleSIGHUP,该函数在src/init.cpp中实现,其实现代码为:
void HandleSIGHUP(int)
{
fReopenDebugLog = true;
}
很简单的一个函数,将全局变量fReopenDebugLog设置为true,src/util.cpp中的LogPrintStr将重新打开调试日志打印文件。
(4)管道错误处理
管道错误处理只有一行代码:
signal(SIGPIPE, SIG_IGN);
signal为信号函数,第一个参数表示需要处理的信号值(SIGPIPE,管道错误),第二个参数为处理函数或者是一个表示,此处SIG_IGN表示忽略SIGPIPE那个注册的信号。
此处需设置忽略SIGPIPE管道错误,否则客户端异常关闭时会将守护进程连带着也给关闭,影响守护进程的正常运行。
六、内存分配失败处理
内存分配失败处理函数主要由set_new_handler函数完成,该函数说明如下:
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命令,直接终止程序的方式解决内存分配失败导致的错误,并且进行日志打印。
作者:区块链研习社比特币源码研读班 菜菜子
以下是广告:
我们区块链研习社已创建“区块链研习社币圈交流”小密圈”,在小密圈中,我们将带领大家一起学习区块链的原理与投资,还将提供区块链基本原理解答、交易所注册与交易操作、ICO交易与操作、投资分析、风险分析等内容。
目前入圈价格初始定价50元,50人调整一次价格,每次调整幅度为50元!