初始化和启动模块(2)

初始化和启动模块(2)

https://www.jianshu.com/p/c8bb938ca17e

Ⅲ:AppInit()函数解读

这个函数定义在bitcoind.cpp中的63行,这是个重要的函数,我们来一行行解析这个函数。
如下图所示为这个函数的注释和定义:

由这个注释(start)可以知道,这个函数标志着比特币程序真正的开始。函数的返回值类型为bool类型,输入的参数为一个整数型参数和一个元素是字符指针的数组类型参数。


(一)bitcoind.cpp中的65-68行:

这是函数最开始定义的三个变量:

boost::thread_group threadGroup;
CScheduler scheduler;
bool fRet = false;

①boost::thread_group是一个管理一组线程的类,它由boost库提供,可以实现对多个线程统一管理。详细解释可以参考下面网站:

http://blog.csdn.net/hanshuobest/article/details/53984660

那么threadGroup对象作用就是可以统一管理多个线程。
②CScheduler为比特币源码的线程调度类:它是用于对后台任务管理的类,它管理的是在后台中定期的或者一段时间运行的任务。它的定义在src/Scheduler.h中的第37行,如图所示:

它包含下面的7个方法:
a.void schedule(Function f, boost::chrono::system_clock::time_point t=boost::chrono::system_clock::now());
//在时间t时刻或者之后调用f函数;
b.void scheduleFromNow(Function f, int64_t deltaMilliSeconds);
//从现在开始deltaMilliSeconds时间段内执行f函数;
c.void scheduleEvery(Function f, int64_t deltaMilliSeconds);
//每隔deltaMilliSeconds时间就周期性的执行函数f;
d.void serviceQueue();
//如果没有出错就一直运行;
e.void stop(bool drain=false);
//当他们完成了当前服务的任何任务或者没有剩下的工作要做了就立刻停止运行serviceQueue的所有线程;
f.size_t getQueueInfo(boost::chrono::system_clock::time_point &first, boost::chrono::system_clock::time_point &last) const;
//返回等待服务的任务数,以及第一个和最后一个任务的时间;
g.bool AreThreadsServicingQueue() const;
//如果在serviceQueue()中运行了线程,则返回true;
它的实现是在src/Scheduler.cpp中的14行,并且对三个变量进行了初始化,如图所示:

③最后为一个bool类型的fRet变量,并且赋值为false


(二)bitcoind.cpp中的第74行:

gArgs.ParseParameters(argc, argv);
这里调用了一个ParseParameters()函数
这是AppInit()函数调用的第一个函数。这个函数主要作用是解析命令行参数
它的定义代码放在util.cpp中的第386行。

这个函数又是一个较复杂的函数,让我们分析它的实现逻辑。
(1)LOCK(cs_args);互斥锁函数
把这个函数中的LOCK和cs_args分别解析:
①cs_args变量参数的定义在util.h的第198行,对它的定义为:
CCriticalSection cs_args;
所以有必要了解它的类CCriticalSection
CCriticalSection类定义在sync.h的第91行,如图所示:

由对这个类的解释文字可以知道:

//封装的boost互斥锁:支持递归锁定,但不支持等待;
//TODO:我们应该避免在默认情况下使用递归锁。

由定义代码可以知道这个类继承AnnotatedMixin类,而且有boost::recursive_mutex的类模板。可以知道CCriticalSection类定义的对象都是boost::recursive_mutex的类,而这是个互斥的类。所以cs_args为一个互斥类对象。
②LOCK()函数的定义在sync.h的第175行,如图所示:

由代码可以知道,这是个宏定义,它和CCriticalSection类的对象cs_args结合,实现的是对包含的代码在各线程中进行互斥加锁处理,防止后续代码中涉及的全局变量被不同的线程抢夺
(2)清空映射存储

    mapArgs.clear();
    mapMultiArgs.clear();

其中mapArgsmapMultiArgs的定义在util.h中的199和200行,如图所示:

mapArgs是单个输入参数及其对应值的映射存储;
mapMultiArgs是单个输入参数及其多个对应值的存储。
程序使用这两个变量,并且使用clear的方法进行了清空映射存储的操作。
(3)for循环解析输入参数

这个for循环主要的方法逻辑是:

对所有输入参数进行逐个的解析,来获取其参数及其值。对于输入的参数及其值放入到mapArgs中存储,对于参数对应的所有值通过vector的变量放入到mapMultiArgs变量中存储。

到此,AppInit()函数调用的第一个函数ParseParameters()就介绍完了。

总结ParseParameters()流程:
①在程序一开始就给全局变量加上一个互斥锁;
②清空内存中的映射存储;
③开始解析输入的参数,把对应参数映射到mapArgs和mapMultiArgs变量中。


(三)bitcoind.cpp代码中的第77-95行

这个部分在ParseParameters()函数之后,它主要是帮助和版本信息。具体代码如下图所示:

正如这段代码的注释所示:在处理数据目录操作前,先完成版本与帮助命令的处理。所以,通过这段代码,比特币后台进程可以根据用户输入的相应参数来输出对应的版本与帮助信息。

3-1.版本信息

如下图所示,当在命令行输入bitcoind -version时,会出来如下结果:

会很容易看到客户端的版本为v0.15.1.0。
(1)代码内容中的if判断语句是判断bitcoind的后台进程参数中是否含有“-?”、“-h”、“-help”或者“-version”命令,如果包含则执行If语句中的代码,执行完成后返回true,程序运行结束。否则不执行其包含的内容,跳出if语句,继续执行其后语句。
此处判断是否包含这几个参数的方法为IsArgSet,这个函数的声明在util.h中的第212行:

它是ArgsManager类中的一个方法,注释上对它的描述为:

如果给定的参数已经手动设置,返回true。

这个函数的定义在util.cpp的第430行,如图所示:

LOCK(cs_args);是互斥锁函数,前面已经详细解释过这个函数;
return mapArgs.count(strArg);其中mapArgs该变量中存储了用户输入的所有参数及其值。所以此处通过mapArgs查找是否包含“-?”、“-h”、“-help”或者“-version”,如果查找到了则返回true,反之为false。还需要说明的是程序通过map类型的变量实现对参数的存储,由于其采用的是键值对存储方式,对于参数信息的快速查找相比于使用数组或队列方式优势很明显。
(2)当符合if判断条件后,if内容的第一行代码为:
std::string strUsage = strprintf(_("%s Daemon"), _(PACKAGE_NAME)) + " " + _("version") + " " + FormatFullVersion() + "\n";
它是通过strUsage字符串变量存储包含比特币后台进程名称与版本信息内容。
①strprintf函数为字符串格式化命令,主要功能是把格式化的数据写入某个字符串中。此处是将PACKAGE_NAME写入_("%s Daemon")中,生成PACKAGE_NAME Daemon形式的字符串内容。PACKAGE_NAME的定义位于config/bitcoin-config.h中,其定义在代码的353行:

注意:bitcoin-config.h需要编译才能出现,详细编译过程可以参考:
《在ubuntu系统下编译bitcoin源码过程》
FormatFullVersion()函数的功能是输出比特币核心的完整版本信息。该函数的实现位于clientversion.cpp中的第81行,如图所示:

其中的CLIENT_BUILD函数的定义在clientversion.cpp的71行,它的定义为:
const std::string CLIENT_BUILD(BUILD_DESC CLIENT_VERSION_SUFFIX);
a.其中CLIENT_VERSION_SUFFIX的定义在clientversion.cpp的第21行:

b.而BUILD_DESC的定义在clientversion.cpp的第61行:通过分析#ifndef BUILD_DESC,我们可以判断BUILD_DESC将在BUILD_DESC_FROM_UNKNOWN函数中执行,该函数采用的是预编译实现方式,这样的好处是对于小型、通用性函数采用预编译方式可以提高程序的执行效率。BUILD_DESC_FROM_UNKNOWN函数的宏定义在当前文件的第58行,代码内容如下:

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

这个函数4次调用了DO_STRINGIZE()函数,而这个函数在clientversion.h的第21-22行实现:

由它的注释可以知道:

在执行宏替换之后,将参数X转换为字符串。
不要把它们合并成一个宏!

那么自然maj min rev build就是 宏定义变量,这4个宏定义变量分别是在clientversion.cpp的第67行中的调用位置传入的,分别是:

CLIENT_VERSION_MAJOR
CLIENT_VERSION_MINOR
CLIENT_VERSION_REVISION
CLIENT_VERSION_BUILD

而上面4个宏定义变量是位于/config/bitcoin-config.h中的第12-24行:

像这样就会在命令行中输出版本号:

v0.15.1.0

③版本的许可信息
再回到bitcoind.cpp的77-95行的if语句部分,如果输入参数中包含"-version",则会执行第81行到84行的语句,其中代码内容为:

 if (gArgs.IsArgSet("-version"))
        {
            strUsage += FormatParagraph(LicenseInfo());
        }

strUsage变量将FormatParagraph(LicenseInfo());的内容拼接,组成完整的输出信息,而这个环节中主要看FormatParagraph()函数,这个函数的声明在utilstrencodings.h中的第128行:

,对它的注释为:

将一段文本格式化为固定宽度,为其添加空格
缩进到任何添加的行。

它的实现在utilstrencodings.cpp中的第543行:

这些代码主要就是实现输出的版本信息文本的格式处理。
那么,可以知道这个函数在这里的作用就是对比特币的版本信息进行格式化处理。
版本的许可信息内容在LicenseInfo()函数中,这个函数在init.cpp中的第528行实现:这个函数中使用了一个CopyrightHolders()函数,这个函数在util.h的320行定义:


这个函数在util.cpp中的第886行实现:

它的作用主要是补全许可信息。
所以在命令行输入bitcoind -version命令后会输出如下所示内容:


3-2.帮助信息

帮助信息的输出位于在版本信息的后面,当if语句发现输入的参数有-? -h -help的时候,将会输出帮助信息。其中调用了HelpMessage()函数,该函数的声明在init.h中的66行:

对它的注释为:

帮助UI和守护进程共享选项(用于-help)

它的实现在init.cpp中的第332行,部分代码如图:

而它的参数HMM_BITCOIND为比特币的后台进程帮助信息,它在init.h的61行定义,属于HelpMessageMode的其中一个成员。
总之帮助信息的目的就是在忘记或者不会的命令时,可以通过在命令行中输入bitcoind -?bitcoind -hbitcoind -help得到帮助信息,帮助信息中主要是为后台进程涉及参数的使用方法说明。

3-3.输出版本和帮助信息

回到bitcoind.cpp的代码,继续看代码93行:
fprintf(stdout, "%s", strUsage.c_str());
这个命令是对版本和帮助信息进行输出,其中stdout为控制台对象,linux中对应的是终端。
到此就完成了版本和帮助信息的处理流程。

猜你喜欢

转载自blog.csdn.net/TuxedoLinux/article/details/86485879