本篇开始分析 AppInit(argc, argv) 应用程序初始化函数。

源码剖析

3.调用 AppInit(argc, argv) 函数初始化并启动应用程序。该函数定义在“bitcoind.cpp”文件中 main 函数的上面,入参为 main 函数的入参:参数个数,参数。

//////////////////////////////////////////////////////////////////////////////
//
// Start // 启动
//
bool AppInit(int argc, char* argv[]) // 3.0.应用程序初始化
{
    boost::thread_group threadGroup; // 空线程组对象,管理多线程,不可复制和移动
    CScheduler scheduler; // 调度器对象

    bool fRet = false; // 启动标志:用于判断应用程序启动状态,初始化为 false,表示未启动

    //
    // Parameters // 参数
    //
    // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main() // 如果使用 Qt,则在 qt/bitcoin.cpp 文件的 main 函数中解析参数/配置文件
    ParseParameters(argc, argv); // 3.1.解析命令行(控制台传入)参数
    ...
};

首先创建了一个空的线程组对象,用于管理多线程,内部还未创建线程,子线程的创建在后面进行,这里不再赘述,详见 Chapter 44. Boost.Thread - Creating and Managing Threads
接着创建了一个调度器对象,用于定时运行后台任务,具体用途也在后面进行描述。

3.1.调用 ParseParameters(argc, argv) 函数解析命令行参数,该函数声明在“util.h”文件中。

void ParseParameters(int argc, const char*const argv[]); // 解析命令行参数(启动选项)

实现在“util.cpp”文件中,入参为 main 函数入参:参数个数,指向参数的二级指针(指针数组)。

map<string, string> mapArgs; // 启动选项(命令行参数,配置文件)单值映射列表,map<选项名,选项值>
map<string, vector<string> > mapMultiArgs; // 启动选项多值映射列表,map<选项名,vector<选项值> >
...
/** Interpret string as boolean, for argument parsing */
static bool InterpretBool(const std::string& strValue) // 把字符串转换为布尔型,用于参数解析
{
    if (strValue.empty()) // 若为空串
        return true; // 返回 true,表示指定的选项未指定值时,该值默认为 true
    return (atoi(strValue) != 0); // 否则,在返回时转换为对应布尔型
}

/** Turn -noX into -X=0 */ // 转换 -noX 为 -X=0
static void InterpretNegativeSetting(std::string& strKey, std::string& strValue)
{
    if (strKey.length()>3 && strKey[0]=='-' && strKey[1]=='n' && strKey[2]=='o') // 若选项名长度大于 3,且满足所示条件
    {
        strKey = "-" + strKey.substr(3); // 重构选项名
        strValue = InterpretBool(strValue) ? "0" : "1"; // 设置选项值
    }
}

void ParseParameters(int argc, const char* const argv[]) // 3.1.0.解析命令行参数
{
    mapArgs.clear(); // 1.清空启动选项单值映射列表
    mapMultiArgs.clear(); // 清空启动选项多值映射列表

    for (int i = 1; i < argc; i++) // 2.从第一个命令行参数开始,遍历命令行参数指针数组
    {
        std::string str(argv[i]); // 2.1.获取一个命令参数:选项名=选项值
        std::string strValue; // 用于保存选项值
        size_t is_index = str.find('='); // 找到等号的位置
        if (is_index != std::string::npos) // 若存在等号
        {
            strValue = str.substr(is_index+1); // 截取选项值子串
            str = str.substr(0, is_index); // 截取选项名子串
        }
#ifdef WIN32 // 2.2.windows 平台
        boost::to_lower(str); // 选项名转换为小写
        if (boost::algorithm::starts_with(str, "/")) // 若选项名以字符 "/" 开头
            str = "-" + str.substr(1); // 替换开头为字符 "-"
#endif

        if (str[0] != '-') // 2.3.若选项名不以字符 '-' 开头
            break; // 跳出,丢弃该选项

        // Interpret --foo as -foo. // 转换 --foo 为 -foo。
        // If both --foo and -foo are set, the last takes effect. // 若同时设置了 --foo 和 -foo,则后者生效。
        if (str.length() > 1 && str[1] == '-') // 若选项名长度大于 1 且 第二个字符为 '-'
            str = str.substr(1); // 则丢弃第一个字符 '-'
        InterpretNegativeSetting(str, strValue); // 2.4.转换 -no 选项名设置

        mapArgs[str] = strValue; // 2.5.加入启动选项单值映射列表
        mapMultiArgs[str].push_back(strValue); // 加入启动选项多值映射列表
    } // 循环,直到所有命令行参数解析完毕
}

1.清空 2 个全局的启动选项映射列表对象。
2.从第 1 个命令行参数开始,遍历命令行参数指针数组。
2.1.分离一个命令行参数中的选项名和选项值,一个命令行参数的形式为:选项名=选项值。
2.2.windows 平台下把选项名开头的字符 ‘/’ 转换为 ‘-‘。
2.3.选项名必须以 - 开头,并把选项名形式 –foo 转换为 -foo。
2.4.把命令行参数 -noX 转换为 -X=0 的形式。
2.5.把选项名和选项值加入 2 个启动选项映射列表。

3.2.在处理数据目录前,先处理帮助和版本信息,实现在“bitcoind.cpp”文件的 AppInit(argc, argv) 函数中。

bool AppInit(int argc, char* argv[]) // 3.0.应用程序初始化
{
    ...
    // Process help and version before taking care about datadir // 在关注数据目录前,处理帮助和版本
    if (mapArgs.count("-?") || mapArgs.count("-h") ||  mapArgs.count("-help") || mapArgs.count("-version")) // 3.2.0.版本和帮助信息
    {
        std::string strUsage = _("Bitcoin Core Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n"; // 1.获取版本信息

        if (mapArgs.count("-version")) // 2.版本许可和帮助信息的选择
        {
            strUsage += LicenseInfo(); // 2.1.获取许可证信息
        }
        else
        {
            strUsage += "\n" + _("Usage:") + "\n" +
                  "  bitcoind [options]                     " + _("Start Bitcoin Core Daemon") + "\n";

            strUsage += "\n" + HelpMessage(HMM_BITCOIND); // 2.2.获取帮助信息
        }

        fprintf(stdout, "%s", strUsage.c_str()); // 3.把信息输出到标准输出并退出
        return false;
    }
    ...
};

若启动选项单值映射列表中含有 “-?”、”-h”、”-help” 和 “-version” 中的一项,则显示帮助或版本信息。 注意此时配置文件还未读取,只解析了命令函参数,所以以上命令只有在控制台输入(作为命令行参数)时才有效。

1.获取比特币核心服务守护进程的版本信息。
2.进行版本许可和帮助信息的选择。
2.1.若指定了 “-version”,则获取许可信息。
2.2.否则获取帮助信息。
3.把信息输出到标准输出。

1.调用 FormatFullVersion() 函数获取版本信息,该函数声明在“clientversion.h”文件中。

std::string FormatFullVersion(); // 格式化全版本信息

实现在“clientversion.cpp”文件中,没有入参。

/**
 * Client version number
 */ // 客户端版本号
#define CLIENT_VERSION_SUFFIX "" // 版本后缀,默认为 ""


/**
 * The following part of the code determines the CLIENT_BUILD variable.
 * Several mechanisms are used for this:
 * * first, if HAVE_BUILD_INFO is defined, include build.h, a file that is
 *   generated by the build environment, possibly containing the output
 *   of git-describe in a macro called BUILD_DESC
 * * secondly, if this is an exported version of the code, GIT_ARCHIVE will
 *   be defined (automatically using the export-subst git attribute), and
 *   GIT_COMMIT will contain the commit id.
 * * then, three options exist for determining CLIENT_BUILD:
 *   * if BUILD_DESC is defined, use that literally (output of git-describe)
 *   * if not, but GIT_COMMIT is defined, use v[maj].[min].[rev].[build]-g[commit]
 *   * otherwise, use v[maj].[min].[rev].[build]-unk
 * finally CLIENT_VERSION_SUFFIX is added
 */

//! First, include build.h if requested
#ifdef HAVE_BUILD_INFO
#include "build.h"
#endif

//! git will put "#define GIT_ARCHIVE 1" on the next line inside archives. $Format:%n#define GIT_ARCHIVE 1$ // git 会将 "#define GIT_ARCHIVE 1" 放在档案中的下一行。$Format:%n#define GIT_ARCHIVE 1$
#ifdef GIT_ARCHIVE
#define GIT_COMMIT_ID "$Format:%h$"
#define GIT_COMMIT_DATE "$Format:%cD$"
#endif

#define BUILD_DESC_WITH_SUFFIX(maj, min, rev, build, suffix) \
    "v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) "-" DO_STRINGIZE(suffix)

#define BUILD_DESC_FROM_COMMIT(maj, min, rev, build, commit) \
    "v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) "-g" commit

#define BUILD_DESC_FROM_UNKNOWN(maj, min, rev, build) \
    "v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) "-unk"

#ifndef BUILD_DESC
#ifdef BUILD_SUFFIX
#define BUILD_DESC BUILD_DESC_WITH_SUFFIX(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD, BUILD_SUFFIX)
#elif defined(GIT_COMMIT_ID)
#define BUILD_DESC BUILD_DESC_FROM_COMMIT(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD, GIT_COMMIT_ID)
#else
#define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD)
#endif
#endif
...
const std::string CLIENT_BUILD(BUILD_DESC CLIENT_VERSION_SUFFIX);
...
std::string FormatFullVersion()
{
    return CLIENT_BUILD; // 返回客户端构建版本
}

宏定义 BUILD_SUFFIX 定义在“obj/build.h”文件中,这个文件是构建比特币过程中生成的。
宏替换函数 DO_STRINGIZE(…) 和版本号的宏定义在“clientversion.h”文件中。

/**
 * client versioning and copyright year // 客户端版本和版权年份
 */

//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 0 // 主版本号
#define CLIENT_VERSION_MINOR 12 // 次版本号
#define CLIENT_VERSION_REVISION 1 // 修订版本号
#define CLIENT_VERSION_BUILD 0 // 构建版本号
...
/**
 * Converts the parameter X to a string after macro replacement on X has been performed.
 * Don't merge these into one macro!
 */ // 在执行 X 宏替换后把参数 X 转换为字符串。不要把这些合并为一个宏
#define STRINGIZE(X) DO_STRINGIZE(X)
#define DO_STRINGIZE(X) #X

#X 中 # 的作用就是把后面跟着的 X 转换为字符串。

2.1.调用 LicenseInfo() 函数获取许可信息。
2.2.调用 HelpMessage(HMM_BITCOIND) 函数获取帮助信息。
它们均声明在“init.h”文件中。

/** The help message mode determines what help message to show */ // 确定显示什么帮助信息的帮助信息模式
enum HelpMessageMode { // 帮助信息模式枚举
    HMM_BITCOIND, // 0
    HMM_BITCOIN_QT // 1
};

/** Help for options shared between UI and daemon (for -help) */ // 用于 UI 和守护进程间共享的帮助选项(用于 -help)
std::string HelpMessage(HelpMessageMode mode);
/** Returns licensing information (for -version) */ // 返回许可证信息(用于 -version)
std::string LicenseInfo();

实现在“init.cpp”文件中,LicenseInfo() 没有入参,HelpMessage(…) 入参为 HMM_BITCOIND。

std::string HelpMessage(HelpMessageMode mode)
{
    const bool showDebug = GetBoolArg("-help-debug", false); // 调试选项,默认关闭

    // When adding new options to the categories, please keep and ensure alphabetical ordering. // 当添加新选项到类别时,请确保按字母顺序排序。
    // Do not translate _(...) -help-debug options, Many technical terms, and only a very small audience, so is unnecessary stress to translators. // 不要翻译  _(...) -help-debug 选项,许多技术术语,只有非常小的受众,所以对译者来说是不必要的压力。
    string strUsage = HelpMessageGroup(_("Options:"));
    strUsage += HelpMessageOpt("-?", _("This help message"));
    strUsage += HelpMessageOpt("-version", _("Print version and exit"));
    ...
    if (showDebug) {
        strUsage += HelpMessageOpt("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE));
        strUsage += HelpMessageOpt("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT));
    }

    return strUsage; // 返回用法字符串
}

std::string LicenseInfo() // 许可证信息
{
    // todo: remove urls from translations on next change // todo:在下次更改时从翻译中移除 urls
    return FormatParagraph(strprintf(_("Copyright (C) 2009-%i The Bitcoin Core Developers"), COPYRIGHT_YEAR)) + "\n" +
           "\n" +
           FormatParagraph(_("This is experimental software.")) + "\n" +
           "\n" +
           FormatParagraph(_("Distributed under the MIT software license, see the accompanying file COPYING or <http://www.opensource.org/licenses/mit-license.php>.")) + "\n" +
           "\n" +
           FormatParagraph(_("This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit <https://www.openssl.org/> and cryptographic software written by Eric Young and UPnP software written by Thomas Bernard.")) +
           "\n"; // 返回格式化的文本信息
}

参考链接