比特币源码研读之十九
近期一直在忙我们区块链研习社代币QYB的事,从开始测试到发布已经过去2周了,一切运行正常,大家普遍反映通过使用QYB进行挖矿和转账体验,真实地感受到了比特币挖矿和比特币相关知识,比自己看文章和书籍更直观,这也是我们研习社的初衷,就是通过不同的方式让大家学习区块链知识,让大家真正通过学习来提升自己的认知,提升自己对区块链的理解。
还有我仍然坚持在我们研习社的千聊平台中开讲《比特币编程》系列课程,比特币相关的课程已达8讲,收听人数也在逐步增加,社长和听众也给了很多正面反馈,让我更有信心继续讲下去。
虽然目前事很多,但源码研读这事我是不会停的,我会继续坚持写下去,因为近期有很多朋友找到我,希望我能继续写,把源码解读完,我自己也很想继续写下去,所以,只要我有时间,一定会坚持写下去的,请各位看官监督!
下面我们正式开始第十九篇源码研读,这篇文章我们将开始AppInitMain函数源码的研读与解析。
本文主要涉及的源码文件包括:
src/bitcond.cpp、src/init.h、src/init.cpp、src/util.h、src/util.cpp
一、daemon参数解析
在解析AppInitMain函数之前,我们先来看对daemon参数,该参数应用于比特币核心的bitcond.exe控制台程序,该程序为比特币核心的后台服务程序。daemon参数的解析代码:
if (GetBoolArg("-daemon", false))
{
#if HAVE_DECL_DAEMON
fprintf(stdout,"Bitcoin server starting\n");
//Daemonize
if(daemon(1, 0)) { // don't chdir (1), do close FDs (0)
fprintf(stderr, "Error: daemon() failed: %s\n",strerror(errno));
return false;
}
#else
fprintf(stderr,"Error: -daemon is not supported on this operating system\n");
returnfalse;
#endif // HAVE_DECL_DAEMON
}
在该代码块中,程序首先通过GetBoolArg函数判断判断是否在启动时设置了守护进程daemon参数,如果设置了则继续执行该参数相关的代码,否则将在控制台中输出当前系统不支持守护进程的错误提示。
设置信息后其具体执行内容为:
(1)判断是否定包含了HAVE_DECL_DAEMON宏定义,该宏定义在未经编译的源码中是不包含的,需经过./configure配置后才会出现,经过configure后,其放置于src/config/bitcoin-config.h文件中,一般来说HAVE_DECL_DAEMON是默认定义的,其定义如下:
通过定义的注释我们可以看到,如果该宏定义表示的数值为1,则定义了daemon,否则不定义。一般来说该值为1。
(2)如果为1则执行fprintf(stdout, "Bitcoin server starting\n");,其表示在控制台中输出"Bitcoin server starting\n"信息,表明比特币后台守护进程在运行;
(3)通过执行daemon(1, 0)开启守护进程操作,我们看到在daemon函数中设置了两个参数,这两个参数的含义是什么呢?我们首先来看看daemon函数的定义:
#include
int daemon(int nochdir, int noclose);
参数:
当nochdir为零时,当前目录变为根目录,否则不变;
当noclose为零时,标准输入、标准输出和错误输出重导向为/dev/null,也就是不输出任何信息,否则照样输出。
返回值:
deamon()调用了fork(),如果fork成功,那么父进程就调用_exit(2)退出,所以看到的错误信息全部是子进程产生的。如果成功函数返回0,否则返回-1并设置errno。
从该函数的定义和参数解释我们可以看出,该函数为守护进程根据输入的参数启动该进程。我们当前设置的参数为1和0,根据其注释我们可以得知,该守护进程将当前目录设为根目录,即程序中的相对目录是从该目录开始的,第二个参数设置为0则表示不输出任何信息。
最后如果daemon()函数返回为0时则正常运行,为-1时则输出errorno对应的错误提示,并返回false,程序退出,同时我们在运行时可根据该错误进行比特币守护进程的配置。
二、AppInitMain与WaitForShutdown
此处先分析AppInitMain与WaitForShutDown的关系。我们可以看到AppInitMain返回值直接影响了WaitForShutdown的运行情况,其执行流程如图所示:
(1)首先执行AppInitMain函数,执行相应的初始化过程;
(2)如果初始化过程运行正常,fRet则为true,否则为false;
(3)如果为true,则程序执行WaitForShutdown函数,进入程序关闭请求等待状态,我们来看下bitcoind.cpp中的WaitForShutdown函数:
void WaitForShutdown(boost::thread_group* threadGroup)
{
bool fShutdown = ShutdownRequested();
// Tell the main threads to shutdown.
while (!fShutdown)
{
MilliSleep(200);
fShutdown = ShutdownRequested();
}
if (threadGroup)
{
Interrupt(*threadGroup);
threadGroup->join_all();
}
}
如果fShutdown为false,则每隔200毫秒循环执行关闭请求函数ShutdownRequested(Init.cpp)的获取,直到该返回值为true时,则通知主线程执行关闭程序,其执行过程与fRet为false的代码一致,见(4);
(4)如果为false,则表明初始化失败,程序将关闭,此时将执行Interrupt函数,该函数定义于init.h,实现与init.cpp中,具体代码如下:
void Interrupt(boost::thread_group& threadGroup)
{
InterruptHTTPServer();
InterruptHTTPRPC();
InterruptRPC();
InterruptREST();
InterruptTorControl();
if (g_connman)
g_connman->Interrupt();
threadGroup.interrupt_all();
}
我们看到该代码实现HTTPServer、HTTPRPC、RPC、REST以及TorControl、connman等功能线程的中断,最后通过完成线程组的中断操作;
(5)随后通过threadGroup.join_all();实现所有中断线程的收回操作,相当于释放线程所占用的空间;
(6)最后是调用Shutdown();操作,实现程序中所有正在运行模块的关闭与停止操作。
三、小结
本研读记录主要分析了daemon参数、AppInitMain与WaitForShutDown执行过程的分析,后续将深入剖析AppInitMain的内容,因为该函数中包含了比特币核心中各模块的初始化工作,对我们了解各模块的运行过程至关重要,敬请大家期待。
比特币源码研读班班长 菜菜子