比特币源码学习0.13(一)

源码0.13.2版的,在clion中打开的



默认文件在src文件夹下,这篇是一边看菜菜子_forest的简书上的研读笔记一边记录的。本科非计算机专业的水平,也很久不接触c++了,算是以一个小白的视角看源码了。同时在看《精通比特币》、《图解密码技术》、《区块链技术指南的书》

1.main函数

main函数在bitcoind.cpp中,第183行

int main(int argc, char* argv[])
{
    SetupEnvironment();  

    // Connect bitcoind signal handlers
    noui_connect();

    return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE);
}

SetupEnvironment();设置运行环境,noui_connect();连接比特币信号处理对象,AppInit初始化,这个函数也在这个.cpp中

查看AppInit(),包含主要步骤如下

  • ParseParameters(argc, argv);//参数解析 解析后参数处理
  • InitLogging();//初始化日志打印
  • InitParameterInteraction();//初始化参数设置
  • AppInit2(threadGroup, scheduler)//
  • WaitForShutdown(&threadGroup)//循环等待关闭消息
  • Shutdown();//关闭程序

2.SetupEnvironment()

SetupEnvironment()设置运行环境
在函数名上go to定位到util.h,在util.cpp有函数的实现,

/**
* Server/client environment: argument handling, config file parsing,
* logging, thread wrappers
*/

上述是util.h的注释部分,表示util这个源文件提供客户端/服务器端的环境:参数处理、解析配置文件、日志打印以及线程封装。
回到SetupEnvironment()函数,可分为两个部分

void SetupEnvironment()
{
    // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale
    // may be invalid, in which case the "C" locale is used as fallback.
#if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
    try {
        std::locale(""); // Raises a runtime error if current locale is invalid
    } catch (const std::runtime_error&) {
        setenv("LC_ALL", "C", 1);  //********1.本地化设置
    }
#endif
    // The path locale is lazy initialized and to avoid deinitialization errors
    // in multithreading environments, it is set explicitly by the main thread.
    // A dummy locale is used to extract the internal default locale, used by
    // boost::filesystem::path, which is then used to explicitly imbue the path.
    std::locale loc = boost::filesystem::path::imbue(std::locale::classic());
    boost::filesystem::path::imbue(loc);//2******本地化文件路径设置
}

1)本地化设置
c/c++程序中,locale(系统区域设置)将决定程序使用的当前语言编码、日期格式、数字格式以及其他与区域相关的设置。
2)本地文件设置
主要通过boost::filesystem::path::imbue实现文件系统的本地化设置;路径区域设置是惰性初始化的,避免在多线程环境出错,由主线程显示的设置。
关于boost::filesystem的概念,boost.filesystem以实现可移植的文件系统的操作为目标,通过精心设计一个中间概念来完成大多数可移植的文件系统的操作。 filesystem库的所有内容定义在boost名字空间的一个下级名字空间里,它叫boost::filesystem。在使用boost.filesystem之后,链接时需要加“-lboost_filesystem-mt”选项,因为这个需要额外的链接,并非一个纯头文件的库。


3.noui_connect

信号处理
go to definitions定位到noui.h,其函数实现在noui.cpp(no ui,没有ui界面)

void noui_connect()
{
    // Connect bitcoind signal handlers
    uiInterface.ThreadSafeMessageBox.connect(noui_ThreadSafeMessageBox);
    uiInterface.ThreadSafeQuestion.connect(noui_ThreadSafeQuestion);
    uiInterface.InitMessage.connect(noui_InitMessage);
}

noui.cppuiInterface调用了3个变量ThreadSafeMessageBox、ThreadSafeQuestion、InitMessage,每个变量通过connet方法调用noui.cpp中定义的3个静态函数
其中uiInterface是全局变量,在ui_interface.h里声明

extern CClientUIInterface uiInterface;

CClientUIInterface类在ui_interface.h里,注释为UI通信的信号,其中的3个变量ThreadSafeMessageBox、ThreadSafeQuestion、InitMessage如下

    /** Show message box. */
    boost::signals2::signal<bool (const std::string& message, const std::string& caption, unsigned int style), boost::signals2::last_value<bool> > ThreadSafeMessageBox;

    /** If possible, ask the user a question. If not, falls back to ThreadSafeMessageBox(noninteractive_message, caption, style) and returns false. */
    boost::signals2::signal<bool (const std::string& message, const std::string& noninteractive_message, const std::string& caption, unsigned int style), boost::signals2::last_value<bool> > ThreadSafeQuestion;

    /** Progress message during initialization. */
    boost::signals2::signal<void (const std::string &message)> InitMessage;

  这3个变量的类型均为boost::signals2::signal信号类型,同时在signal中通过<>方式包含了程序接收到该信号时的处理方法,也就是信号接受槽,整个机制也叫信号/槽机制。
  以ThreadSafeMessageBox为例,处理方法中包含一个函数定义和一个类型定义,这个函数定义就是信号对应的槽,类型就是这个槽函数的返回类型。也就是说,在程序实现接受槽时,其参数数量、类型、返回值需要与上述定义一致。
  再回去看noui.cpp第一行代码

 uiInterface.ThreadSafeMessageBox.connect(noui_ThreadSafeMessageBox);

其中的槽函数为noui_ThreadSafeMessageBo,其实现在noui.cpp

static bool noui_ThreadSafeMessageBox(const std::string& message, const std::string& caption, unsigned int style)
{
    bool fSecure = style & CClientUIInterface::SECURE;
    style &= ~CClientUIInterface::SECURE;

  ···
    if (!fSecure)
        LogPrintf("%s: %s\n", strCaption, message);
    fprintf(stderr, "%s: %s\n", strCaption.c_str(), message.c_str());
    return false;
}

可以通过对比发现,函数的定义符合所要求的槽函数定义,这个函数代码主要实现对不同信号的对应处理以及日志打印。


4.AppInit

应用程序初始化
其程序实现就在bitcoind.cpp中,代码包括了后续的一些步骤,是整个比特币后台进程真正开始运行的入口

bool AppInit(int argc, char* argv[])
{
    boost::thread_group threadGroup;
    CScheduler scheduler;

hread_group定义在init.h中,字面理解是对一组线程的管理;CScheduler类是线程调度器,其头文件定义在scheduler.h,实现在scheduler.cpp
从注释来看

// Simple class for background tasks that should be run
// periodically or once "after a while"
//
// Usage:
//
// CScheduler* s = new CScheduler();
// s->scheduleFromNow(doSomething, 11); // Assuming a: void doSomething() { }
// s->scheduleFromNow(boost::bind(Class::func, this, argument), 3);
// boost::thread* t = new boost::thread(boost::bind(CScheduler::serviceQueue, s));
//
// ... then at program shutdown, clean up the thread running serviceQueue:
// t->interrupt();
// t->join();
// delete t;
// delete s; // Must be done after thread is interrupted/joined.

这个类管理后台那些需要周期运行或者一段时间后运行一次的任务。源码里面的注释对每个函数也是比较清晰的。
另外了解下boost:bind函数,其可以支持函数对象、函数、函数指针、成员函数指针,并且绑定任意参数到某个指定值上或者将输入参数传入任意位置。

struct X
{
    bool f(int a);
};

X x;
shared_ptr<X> p(new X);
int i = 5;
//Using bind with pointers to members
bind(&X::f, ref(x), _1)(i);     // x.f(i)
bind(&X::f, &x, _1)(i);         // (&x)->f(i)
bind(&X::f, x, _1)(i);          // (internal copy of x).f(i)
bind(&X::f, p, _1)(i);          // (internal copy of p)->f(i)

5.ParseParmeters

参数解析,AppInit()的下一步,定位ParseParameter()的定义在util.h,实现在util.cpp

 //
    // Parameters
    //
    // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()
    ParseParameters(argc, argv);

函数的实现代码

void ParseParameters(int argc, const char* const argv[])
{
    mapArgs.clear();
    mapMultiArgs.clear();

    for (int i = 1; i < argc; i++)
    {
        std::string str(argv[i]);
        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
        boost::to_lower(str);
        if (boost::algorithm::starts_with(str, "/"))
            str = "-" + str.substr(1);
#endif

        if (str[0] != '-')
            break;

        // Interpret --foo as -foo.
        // If both --foo and -foo are set, the last takes effect.
        if (str.length() > 1 && str[1] == '-')
            str = str.substr(1);
        InterpretNegativeSetting(str, strValue);
        mapArgs[str] = strValue;
        mapMultiArgs[str].push_back(strValue);
    }

mapArgsmapMutiArgs声明在该util.cpp的前面(105、106)行,是键值对的形式,使用前先clear()变量的值。

map<string, string> mapArgs;
map<string, vector<string> > mapMultiArgs;

之后使用for循环实现对参数的逐个进行解析,获取参数及值,保存在键值对中。

//bitcoind --help
 -rpcuser=<user>
       Username for JSON-RPC connections 
  -version
       Print version and exit

参数的部分输入格式如上,以-开头的参数才能被记录并获取对应的值。std::string::npos代表不存在的位置。


6.参数处理

返回AppInit()继续看 ParseParameters(argc, argv)后面的代码

 // Process help and version before taking care about datadir
    if (mapArgs.count("-?") || mapArgs.count("-h") ||  mapArgs.count("-help") || mapArgs.count("-version"))
    {
        std::string strUsage = strprintf(_("%s Daemon"), _(PACKAGE_NAME)) + " " + _("version") + " " + FormatFullVersion() + "\n";

        if (mapArgs.count("-version"))
        {
            strUsage += FormatParagraph(LicenseInfo());
        }
        else
        {
            strUsage += "\n" + _("Usage:") + "\n" +
                  "  bitcoind [options]                     " + strprintf(_("Start %s Daemon"), _(PACKAGE_NAME)) + "\n";

            strUsage += "\n" + HelpMessage(HMM_BITCOIND);
        }

        fprintf(stdout, "%s", strUsage.c_str());
        return true;
    }

这段代码的的注释表示:在处理数据目操作前,先完成版本和帮助命令的处理。
如果bitcoind后台进程参数中包含“-?”、”-h”、”-help”、”-version”就执行if语句里的代码。
map(mapArgs)使用count,返回的是被查找元素的个数。如果有,返回1;否则,返回0。注意,map中不存在相同元素,所以返回值只能是1或0。
可以来看一下输出显示

parallels@parallels-vm:~$ bitcoind --help
Bitcoin Core Daemon version v0.13.2

Usage:
  bitcoind [options]                     Start Bitcoin Core Daemon

Options:

  -?
       Print this help message and exit···

parallels@parallels-vm:~$ bitcoind -version
Bitcoin Core Daemon version v0.13.2
Copyright (C) 2009-2016 The Bitcoin Core developers

Please contribute if you find Bitcoin Core useful. Visit
···

6.1 数据目录

接上面的

 if (!boost::filesystem::is_directory(GetDataDir(false)))
        {
            fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str());
            return false;
        }

if中首先判断GetDataDir(false)函数返回的数据路径是否为目录名称,否则打印不存在,返回false,退出程序。
GetDataDir()函数在util.cpp

const boost::filesystem::path &GetDataDir(bool fNetSpecific)
{
    namespace fs = boost::filesystem;

    LOCK(csPathCached);

    fs::path &path = fNetSpecific ? pathCachedNetSpecific : pathCached;

    // This can be called during exceptions by LogPrintf(), so we cache the
    // value so we don't have to do memory allocations after that.
    if (!path.empty())
        return path;

    if (mapArgs.count("-datadir")) {
        path = fs::system_complete(mapArgs["-datadir"]);
        if (!fs::is_directory(path)) {
            path = "";
            return path;
        }
    } else {
        path = GetDefaultDataDir();
    }
    if (fNetSpecific)
        path /= BaseParams().DataDir();

    fs::create_directories(path);

    return path;
}

代码第二行执行LOCK(csPathCached);查找csPathCached

util.cpp 484line
static CCriticalSection csPathCached;
sync.n 92line
class CCriticalSection : public AnnotatedMixin<boost::recursive_mutex>
{
public:
    ~CCriticalSection() {
        DeleteLock((void*)this);
    }
};

Critical Section为线程中的访问临界资源,多个线程必须互斥地对它进行访问,即保证在该代码后面的全局变量在程序运行过程中不会被其他线程对其后的变量进行篡改。

sync.h 174line
#define LOCK(cs) CCriticalBlock criticalblock(cs, #cs, __FILE__, __LINE__)

通过其定义,我们可以看出LOCK并不是一个单独的函数,而是一个宏定义,与前面的CCriticalSection对象结合实现对包含代码在各线程中进行互斥加锁处理,防止后续代码中涉及的全局变量被不同线程抢夺。
path根据fNetSpecific 确定路径类型(网络路径或本地路径),程序中对GetDataDir(false)函数传入的参数为false,即使用本地文件目录。路径不为空则返回路径;参数重包含“-datadir”则获取对应目录值,否则获取默认目录。
GetDefaultDataDir()也在util.cpp中,可以看到其中的默认目录地址

// Windows < Vista: C:\Documents and Settings\Username\Application Data\Bitcoin
    // Windows >= Vista: C:\Users\Username\AppData\Roaming\Bitcoin
    // Mac: ~/Library/Application Support/Bitcoin
    // Unix: ~/.bitcoin

是网络目录的话,获取并创建数据目录,返回目录。
之后读取配置文件

//bitcoin.cpp
try
        {
            ReadConfigFile(mapArgs, mapMultiArgs);
        } catch (const std::exception& e) {
            fprintf(stderr,"Error reading configuration file: %s\n", e.what());
            return false;
        }

查找ReadConfigFile这个函数

//util.cpp 531line
void ReadConfigFile(map<string, string>& mapSettingsRet,
                    map<string, vector<string> >& mapMultiSettingsRet)
{
    boost::filesystem::ifstream streamConfig(GetConfigFile());
    if (!streamConfig.good())
        return; // No bitcoin.conf file is OK

    set<string> setOptions;
    setOptions.insert("*");

    for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it)
    {
        // Don't overwrite existing settings so command line settings override bitcoin.conf
        string strKey = string("-") + it->string_key;
        string strValue = it->value[0];
        InterpretNegativeSetting(strKey, strValue);
        if (mapSettingsRet.count(strKey) == 0)
            mapSettingsRet[strKey] = strValue;
        mapMultiSettingsRet[strKey].push_back(strValue);
    }
    // If datadir is changed in .conf file:
    ClearDatadirCache();
}

函数最后是为防止配置文件中设置了数据目录参数datadir,通过ClearDatadirCache()函数将数据文件路径参数设置为空目录,这样下次进入GetDataDir()时,我们将会根据新的datadir创建数据目录。
首先调用GetConfigFile()

boost::filesystem::path GetConfigFile()
{
    boost::filesystem::path pathConfigFile(GetArg("-conf", BITCOIN_CONF_FILENAME));
    if (!pathConfigFile.is_complete())
        pathConfigFile = GetDataDir(false) / pathConfigFile;

    return pathConfigFile;
}

关注其中的BITCOIN_CONF_FILENAME,可以找到其定义

//util.cpp 102line
const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf";
const char * const BITCOIN_PID_FILENAME = "bitcoind.pid";

比特币的后台配置文件为bitcoin.conf这个我们在源码编译的时候就接触过了,一开始报错找不到,需要自己新建。
再看GetArg,在参数中查找“-conf”,存在的话使用之前5.参数解析中保存的值,否则使用默认的“bitcoin.conf”

感觉太长了,分开写到另一篇好了。

更新中。。。。。

猜你喜欢

转载自blog.csdn.net/m0_37847176/article/details/81167128