比特币源码研读之二十
今天是2017年12月31日,即2017年的最后一天了,本应好好总结,对今年做个总结的,但想着自己身为源码研读班班长,许久没有带头发表研读记录了,因此,我今天先把研读记录写出来,同时今天这篇是第二十篇研读记录,也就是说我个人的研读记录已经到达2“”字头的数量了,趁着2017年最后一天,将这篇文章写完,来年更加努力,实现源码的全覆盖!
我们今天将深入AppInitMain函数,该函数将带领我们进入比特币核心程序的初始化过程,下面我们一起来对其进行详细分析。

本文主要涉及的源码文件包括:
src/bitcond.cpp、src/init.h、src/init.cpp、src/util.h、src/util.cpp、src/script/scriptcache.h、src/script/scriptcache.cpp、src/cuckoocache.h
AppInitMain函数中包含的内容较多,涉及了应用程序初始化、钱包数据库完整性、网络初始化、区块链加载、钱包加载、数据目录以及区块加载等内容,其具体执行流程如图所示。

在开始应用程序初始化之前,程序首先执行运行网络的选择代码:
const CChainParams& chainparams = Params();
关于运行网络的分析我在第六篇文章中已详细说明,此处不再赘述,一般来说,如果我们在启动比特币核心程序时,没有设置相应网络参数,则默认运行主链,否则将根据输入的参数启动相应网络。
在完成链参数设置后,程序将进入应用程序初始化模块,具体初始化内容如下。
一、应用程序初始化
(1).lock文件。首先LockDataDirectory实现对数据目录的锁定,锁定的原因是保证数据目录在同一台机器中仅被一个比特币核心核心程序所使用,否则如果多个比特币核心程序同时使用同一数据目录,将会造成该程序数据内容产生不一致的情况。
在LockDataDirectory中,首先获取数据目录值,然后打开数据目录下的.lock文件,判断其是否存在。该文件我们可以在比特币数据目录中找到,其形式如图所示:

其文件名就是.lock,而且其内容为空。该文件的作用我们可以通过后面的static boost::interprocess::file_lock来确定,其实现如下:
try {
static boost::interprocess::file_locklock(pathLockFile.string().c_str());
if (!lock.try_lock()) {
return InitError(strprintf(_("Cannot obtain a lock on datadirectory %s. %s is probably already running."), strDataDir,_(PACKAGE_NAME)));
}
if (probeOnly) {
lock.unlock();
}
}catch(const boost::interprocess::interprocess_exception& e) {
return InitError(strprintf(_("Cannot obtain a lock on datadirectory %s. %s is probably already running.") + " %s.",strDataDir, _(PACKAGE_NAME), e.what()));
}
通过上述代码,我们可以理解到.lock文件将通过lock.try_lock()被锁定,但是如果已被其他先期启动的比特币程序锁定了的话,本次锁定将失效,同时将提示错误信息,返回false,整个程序将退出。
因为该处为程序的正式初始化,所以LockDataDirectory函数中传入的probeOnly为false,意思是当前已不是用于试验、检测锁定文件了,如果仅是检测,将会释放锁定文件,而当前是正式的锁定,所以,一旦锁定将不会解锁,除非程序退出。
(2)pid文件。完成了数据目录的锁定之后,如果是在非windows中的比特币核心程序,程序将通过CreatePidFile函数创建进程编号记录文件,进程编号记录文件名在src/util.h和src/util.cpp中进行了声明与定义,其定义如下:
const char * const BITCOIN_PID_FILENAME = "bitcoind.pid";
我们通过QYB的程序可以直观看到,Ubuntu下的数据目录中存在qybcoind.pid(同bitcoind.pid),且其文件中记录了比特币核心的进程号。而在QYB的Windows版的数据目录中并不存在该文件。

在非windows中记录比特币核心程序的pid,其目的也是为了与前面的锁定文件一样,防止出现多个比特币核心程序,文件中始终记录第一个启动的程序进程。
pid的详细作用大家可以参考:http://blog.csdn.net/yinqingwang/article/details/52841744
(3)调试日志输出。完成了进程ID存储文件的处理后,我们再来看下后面的调试日志文件的处理代码。此处出现了shrinkdebugfile这个参数,该参数的含义,我们可在init.cpp的HelpMessage函数中看到其解释信息:
strUsage +=HelpMessageOpt("-shrinkdebugfile", _("Shrink debug.log file onclient startup (default: 1 when no -debug)"));
其含义为当客户端启动时,对debug.log文件进行压缩处理,默认在不进行调试时会进行压缩操作,这个也好理解,因为我们不进行调试,所以该文件中的内容没必要保留那么多。同时,if
(GetBoolArg("-shrinkdebugfile", !fDebug))语句默认来说是为true的,因为fDebug默认是false,我们在启动时很少会使用shrinkdebugfile参数,所以将会执行ShrinkDebugFile函数,该函数中包含了具体的压缩处理过程。
我们来看具体的压缩处理过程,具体见ShrinkDebugFile函数,该函数位于src/util.cpp中,通过该函数的代码我们可以知道debug.log文件的大小限定在RECENT_DEBUG_HISTORY_SIZE,即10 * 1000000=10MB。如果debug.log文件大小超过限定大小的10%时,则对文件进行裁剪处理,使其限制在RECENT_DEBUG_HISTORY_SIZE范围内。
对debug.log文件进行压缩处理后,程序将正式打开debug.log文件,实现程序运行过程的记录,以便调试。我们首先来看定义于src/util.cpp中的fPrintToDebugLog变量默认为true,然后程序将执行OpenDebugLog函数,该函数定义于src/util.cpp中,在该函数中完成了debug.log文件的打开,打开方式是增加内容“a”模式,即启动后程序将在上一次日志信息的基础上继续添加本次运行日志,在打开日志文件后,程序将实现vMsgsBeforeOpenLog包含内容的打印输出。vMsgsBeforeOpenLog为日志文件未打开之前,预先存储的一些打印输出信息。该信息的存储位于src/util.cpp中的LogPrintStr函数中:

从图中代码可以看出,fileout为空时,vMsgsBeforeOpenLog将预先存储将打印至日志文件的内容,待日志文件打开后,进行写入,写入代码位于OpenDebugLog函数中:

至此完成了日志文件的打开操作,并完成了预先存储日志信息的输出。
随后是日志时间戳信息在日志文件中的输出,但我们看fLogTimestamps其默认值为DEFAULT_LOGTIMESTAMPS,而DEFAULT_LOGTIMESTAMPS在src/util.h中定义为true,意味着日志的每一行都会带有时间戳信息,输入内容加时间戳的函数位于src/util.cpp中的LogTimestampStr函数中,大家有兴趣可以详细看看该函数实现。正因为每一行都带有时间戳,因此,此处不单独输出时间信息。
再下来就是实现基本配置信息的输出了,这些内容包括数据默认目录、当前实际数据目录、比特币配置文件目录以及最大连接数等信息,我们可以看看实际的日志文件debug.log来对比下代码实现,这样就可以更清晰的明确代码实现内容:

从上图我们可以清晰地看到日志文件中包含了预输出内容,还有后面的日志启动输出内容与我们现在的代码内容是一致的。
(4)签名缓存。我们再来看下签名缓存的初始化代码,其代码在src/script/sigcache.h的InitSignatureCache()函数中实现,其包含的代码很简单:
// To be calledonce in AppInit2/TestingSetup to initialize the signatureCache
voidInitSignatureCache()
{
// nMaxCacheSize is unsigned. If-maxsigcachesize is set to zero,
// setup_bytes creates the minimum possiblecache (2 elements).
size_t nMaxCacheSize =std::min(std::max((int64_t)0, GetArg("-maxsigcachesize",DEFAULT_MAX_SIG_CACHE_SIZE)), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 <<20);
size_t nElems = signatureCache.setup_bytes(nMaxCacheSize);
LogPrintf("Using %zu MiB out of %zurequested for signature cache, able to store %zu elements\n",
(nElems*sizeof(uint256))>>20, nMaxCacheSize>>20, nElems);
}
通过其代码我们可以知道签名的默认缓存大小默认为DEFAULT_MAX_SIG_CACHE_SIZE,如果设置了-maxsigcachesize,并且大于DEFAULT_MAX_SIG_CACHE_SIZE,签名大小将是-maxsigcachesize或者MAX_MAX_SIG_CACHE_SIZE之间的最小者。
其中,DEFAULT_MAX_SIG_CACHE_SIZE、MAX_MAX_SIG_CACHE_SIZE的定义在src/script/sigcache.h中:
// DoS prevention:limit cache size to 32MB (over 1000000 entries on 64-bit
// systems). Dueto how we count cache size, actual memory usage is slightly
// more (~32.25MB)
static const unsigned intDEFAULT_MAX_SIG_CACHE_SIZE = 32;
// Maximum sigcache size allowed
static const int64_t MAX_MAX_SIG_CACHE_SIZE= 16384;
在获得了最大签名缓存大小后,程序将计算在当前大小的缓存下能存储多少的签名数,即nElems的值是多少。nElems的计算要直接从src/cuckoocache.h中来看:

很简单的一个计算公式,就是根据传入的字节数除以每个元素的字节数,即可得到相应的元素数量。最后就是签名缓存信息内容的日志输出了。
二、小结
本研读记录主要分析了AppInitMain中初始化部分,属于刚刚开始,后面包括的内容较多,也很重要,我将会继续带领大家一起详细解读、剖析后续内容,敬请期待。
比特币源码研读班班长 菜菜子

比特币编程课程
