比特幣源碼學習0.13(一)

源碼0.13.2版的,在clion中打開的



默認文件在src文件夾下,這篇是一邊看菜菜子_forest的簡書上的研讀筆記一邊記錄的。同時在看《精通比特幣》、《圖解密碼技術》、《區塊鏈技術指南》

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)// 進入AppInit2函數,進行第二步的核心初始化
- 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”

感覺太長了,分開寫到另一篇好了。
比特幣源碼學習(二)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章