比特幣學習筆記(四)---解讀入口部分源碼

我我的目前使用的源碼版本是最新的開發版master,介於0.18-0.19版本之間,若是有人發現代碼不一致,可自行尋找其它版本代碼解析文章查看。node

迴歸正題,開始看源碼,咱們從程序bitcoind開始看起。ios

 

 

 

bitcoind的入口函數是這麼寫的。編程

int main(int argc, char* argv[]) { #ifdef WIN32 util::WinCmdLineArgs winArgs; std::tie(argc, argv) = winArgs.get(); #endif SetupEnvironment(); // Connect bitcoind signal handlers
 noui_connect(); return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); }

WIN平臺下的ifdef暫且忽略,剩下的就是SetupEnvironment(),noui_connect(),AppInit()這三個函數。安全

SetupEnvironment

首先來看SetupEnvironment,代碼具體實現以下網絡

void SetupEnvironment() { #ifdef HAVE_MALLOPT_ARENA_MAX // glibc-specific: On 32-bit systems set the number of arenas to 1. 這裏判斷若是是32位系統(sizeof(void*) == 4),即只分配一個arena內存區,防止虛擬地址空間 // By default, since glibc 2.10, the C library will create up to two heap 過分使用。自從glibc 2.10版本後,針對每一個核心,C標準庫都會默認建立兩個堆內存區。這是公 // arenas per core. This is known to cause excessive virtual address space 認會致使內存地址空間過分使用的問題。 // usage in our usage. Work around it by setting the maximum number of 科普:arena:能夠理解爲一個較大且連續的內存分配區,須要手動來管理。 // arenas to 1. 科普2:64位系統一個指針的長度爲8字節
    if (sizeof(void*) == 4) { mallopt(M_ARENA_MAX, 1); } #endif
    // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale 在大部分POSIX系統(好比Linux,但非BSD),環境的locale即系統區域設置可能會無效,假如當前的本地化設置無效,將拋出該運行時異常,同時在捕獲後將該值設置爲「C」。 // may be invalid, in which case the "C" locale is used as fallback. Tips:locale決定當前程序所使用的語言編碼、日期格式、數字格式及其它與區域有關的設置,詳情參考:http://blog.csdn.net/haiross/article/details/45074355
#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); } #elif defined(WIN32)
    // Set the default input/output charset is utf-8
 SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8); #endif
    // The path locale is lazy initialized and to avoid deinitialization errors 路徑區域設置是採用懶初始化,即延遲初始化的方式,而且能夠避免多線程環境下的析構過程錯 // in multithreading environments, it is set explicitly by the main thread. 誤,經過主線程來顯式設置。一個dummy locale能夠用來提取fs::path使用的內部默認locale(路 // A dummy locale is used to extract the internal default locale, used by 徑),而後就能夠顯式設置該路徑。 // fs::path, which is then used to explicitly imbue the path. imbue:該函數將要設置的loc值與流和該流相關的流緩衝區(若是有的話)關聯起來,做爲新的系統區域設置對象。詳情參考:http://www.cplusplus.com/reference/ios/ios/imbue/
    std::locale loc = fs::path::imbue(std::locale::classic()); #ifndef WIN32 fs::path::imbue(loc); #else fs::path::imbue(std::locale(loc, new std::codecvt_utf8_utf16<wchar_t>())); #endif }

英文部分我已經作了簡單翻譯,而且加了註釋,總之該函數設置了堆內存區、loc本地化參數等信息。多線程

noui_connect

接下來咱們來看noui_connect()這個函數,其代碼以下:ide

void noui_connect() { noui_ThreadSafeMessageBoxConn = uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBox); noui_ThreadSafeQuestionConn = uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestion); noui_InitMessageConn = uiInterface.InitMessage_connect(noui_InitMessage); }

這段代碼一點註釋都沒有,不少人看到這裏的時候估計會跟我同樣一臉懵逼,實際上在仔細跟下代碼後會發現,這其實就是註冊三個消息的回調函數。函數

其中noui_ThreadSafeMessageBox爲消息彈出框提示消息,noui_ThreadSafeQuestion爲用戶詢問的交互消息,noui_InitMessage爲程序初始化過程當中的消息。測試

在這裏,uiInterface是在src\ui_interface.h文件的最後聲明的,該變量爲全局變量:ui

extern CClientUIInterface uiInterface;

uiInterface是在src\ui_interface.cpp文件裏面定義的:

CClientUIInterface uiInterface;

中間的ThreadSafeMessageBox、ThreadSafeQuestion、InitMessage是定義在ui_interface.h文件中的三個信號量boost::signals2::signal

signals2基於Boost的另外一個信號庫signals,實現了線程安全的觀察者模式。在signals2庫中,其被稱爲信號/插槽機制(signals and slots,另有說法爲時間處理機制,event/event handler),一個信號關聯了多個插槽,當信號發出時,全部關聯它的插槽都會被調用。

三個信號量的定義內容以下:

Path: bitcoin\src\ui_interface.h

#define ADD_SIGNALS_DECL_WRAPPER(signal_name, rtype, ...)                                  \ rtype signal_name(__VA_ARGS__); \ using signal_name##Sig = rtype(__VA_ARGS__); \ boost::signals2::connection signal_name##_connect(std::function<signal_name##Sig> fn); /** Show message box. */ ADD_SIGNALS_DECL_WRAPPER(ThreadSafeMessageBox, bool, const std::string& message, const std::string& caption, unsigned int style); /** If possible, ask the user a question. If not, falls back to ThreadSafeMessageBox(noninteractive_message, caption, style) and returns false. */ ADD_SIGNALS_DECL_WRAPPER(ThreadSafeQuestion, bool, const std::string& message, const std::string& noninteractive_message, const std::string& caption, unsigned int style); /** Progress message during initialization. */ ADD_SIGNALS_DECL_WRAPPER(InitMessage, void, const std::string& message);

以第一個爲例,宏

ADD_SIGNALS_DECL_WRAPPER

裏面的bool表示返回值是布爾類型,形參分別爲std::string&類型的message和std::string&類型的caption以及無符號int類型的style。回到src\noui.cpp中,對比下槽函數的定義是同樣的:

bool noui_ThreadSafeMessageBox(const std::string& message, const std::string& caption, unsigned int style)

tips: 當進行信號和槽函數定義,即便用SIGNAL()和SLOT()時只能用變量類型,不能出現變量名。

connect函數

connect()函數是signal的插槽管理操做函數,它把插槽鏈接到信號上,至關於爲信號(事件)增長了一個處理器。

鏈接成功則返回一個connection對象,可對鏈接進行斷開、重連、測試鏈接狀態等操做。

參考:http://blog.csdn.net/zengraoli/article/details/9697841

說下信號和槽的概念,信號:通常是用來傳參或者是一種邏輯的調用者。槽:是用來接收並處理信號的函數,完成信號想要實現的功能。

舉個例子,好比你在網頁上點擊提交按鈕的時候就觸發一個信號,該操做信號就會被傳遞到全部綁定該信號的控件上,接着由對應的控件函數進行響應,完成提交的功能。

AppInit

最後咱們來看Appinit函數,如下是函數的代碼。

////////////////////////////////////////////////////////////////////////////// //
// Start // static bool AppInit(int argc, char* argv[]) { InitInterfaces interfaces; interfaces.chain = interfaces::MakeChain(); bool fRet = false; util::ThreadRename("init"); //
    // Parameters //
    // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()
 SetupServerArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { return InitError(strprintf("Error parsing command line arguments: %s\n", error)); } // Process help and version before taking care about datadir
    if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { std::string strUsage = PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n"; if (gArgs.IsArgSet("-version")) { strUsage += FormatParagraph(LicenseInfo()) + "\n"; } else { strUsage += "\nUsage: bitcoind [options] Start " PACKAGE_NAME " Daemon\n"; strUsage += "\n" + gArgs.GetHelpMessage(); } tfm::format(std::cout, "%s", strUsage.c_str()); return true; } try { if (!CheckDataDirOption()) { return InitError(strprintf("Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""))); } if (!gArgs.ReadConfigFiles(error, true)) { return InitError(strprintf("Error reading configuration file: %s\n", error)); } // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
        try { SelectParams(gArgs.GetChainName()); } catch (const std::exception& e) { return InitError(strprintf("%s\n", e.what())); } // Error out when loose non-argument tokens are encountered on command line
        for (int i = 1; i < argc; i++) { if (!IsSwitchChar(argv[i][0])) { return InitError(strprintf("Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i])); } } // -server defaults to true for bitcoind but not for the GUI so do this here
        gArgs.SoftSetBoolArg("-server", true); // Set this early so that parameter interactions go to console
 InitLogging(); InitParameterInteraction(); if (!AppInitBasicSetup()) { // InitError will have been called with detailed error, which ends up on console
            return false; } if (!AppInitParameterInteraction()) { // InitError will have been called with detailed error, which ends up on console
            return false; } if (!AppInitSanityChecks()) { // InitError will have been called with detailed error, which ends up on console
            return false; } if (gArgs.GetBoolArg("-daemon", false)) { #if HAVE_DECL_DAEMON
#if defined(MAC_OSX)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif tfm::format(std::cout, PACKAGE_NAME " daemon starting\n"); // Daemonize
            if (daemon(1, 0)) { // don't chdir (1), do close FDs (0)
                return InitError(strprintf("daemon() failed: %s\n", strerror(errno))); } #if defined(MAC_OSX)
#pragma GCC diagnostic pop
#endif
#else
            return InitError("-daemon is not supported on this operating system\n"); #endif // HAVE_DECL_DAEMON } // Lock data directory after daemonization
        if (!AppInitLockDataDirectory()) { // If locking the data directory failed, exit immediately
            return false; } fRet = AppInitMain(interfaces); } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInit()"); } catch (...) { PrintExceptionContinue(nullptr, "AppInit()"); } if (!fRet) { Interrupt(); } else { WaitForShutdown(); } Shutdown(interfaces); return fRet; }

咱們先來看開頭幾句,0.18版本的代碼和早期版本的代碼不一樣,一上來主要作了如下幾件事情(看註釋)

//建立一個區塊實例,這個區塊實例在後面會繼續初始化
 InitInterfaces interfaces; interfaces.chain = interfaces::MakeChain(); bool fRet = false; //重命名線程
    util::ThreadRename("init"); //
    // Parameters //
    // 若是使用了QT, parameters/bitcoin.conf已經在 qt/bitcoin.cpp's main()中進行了序列化,不然將在這裏進行第一次初始化
    SetupServerArgs();

而後是跟以往版本代碼同樣的參數解析ParseParmeters

 

 

 

AppInit函數中執行的第一個函數爲ParseParameters,經過其字面意思咱們能夠看出其主要功能爲解析外部傳入的參數。其實現代碼位於src/util.cpp中。

(1)互斥鎖

該函數的第一行代碼爲LOCK(cs_args);LOCK和cs_args都沒有在該函數中定義,所以咱們就須要查找其定義所在,以便咱們理解其真正含義。經過查找cs_args定義位於src/util.cpp的上方,其定義爲:

CCriticalSection cs_args;

CCriticalSection又爲什麼物,有過線程編程經驗的應該知道Critical Section爲線程中的訪問臨界資源,多個線程必須互斥地對它進行訪問,即保證在該代碼後面的全局變量在程序運行過程當中不會被其餘線程對其後的變量進行篡改。那麼CCriticalSection類在哪定義呢?經過查找發現其在src/sync.h中實現了定義,從定義能夠看出該類繼承自boost::recursive_mutex互斥類。

class CCriticalSection : publicAnnotatedMixin { public: ~CCriticalSection() { DeleteLock((void*)this); } };

瞭解了cs_args爲互斥類對象,咱們再來看LOCK函數,其又爲什麼物呢?咱們再次來到src/sync.h,能夠找到其定義以下。

#define LOCK(cs) CCriticalBlock PASTE2(criticalblock,__COUNTER__)(cs, #cs, __FILE__, __LINE__)

經過其定義,咱們能夠看出LOCK並非一個單獨的函數,而是一個宏定義,與前面的CCriticalSection對象結合實現對包含代碼在各線程中進行互斥加鎖處理,防止後續代碼中涉及的全局變量被不一樣線程搶奪。

(2)參數解析

bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::string& error) { LOCK(cs_args); m_override_args.clear(); for (int i = 1; i < argc; i++) { std::string key(argv[i]); if (key == "-") break; //bitcoin-tx using stdin
        std::string val; size_t is_index = key.find('='); if (is_index != std::string::npos) { val = key.substr(is_index + 1); key.erase(is_index); } #ifdef WIN32 key = ToLower(key); if (key[0] == '/') key[0] = '-'; #endif

        if (key[0] != '-') break; // Transform --foo to -foo
        if (key.length() > 1 && key[1] == '-') key.erase(0, 1); const unsigned int flags = FlagsOfKnownArg(key); if (flags) { if (!InterpretOption(key, val, flags, m_override_args, error)) { return false; } } else { error = strprintf("Invalid parameter %s", key.c_str()); return false; } } // we do not allow -includeconf from command line, so we clear it here
    auto it = m_override_args.find("-includeconf"); if (it != m_override_args.end()) { if (it->second.size() > 0) { for (const auto& ic : it->second) { error += "-includeconf cannot be used from commandline; -includeconf=" + ic + "\n"; } return false; } } return true; }

在這段代碼中,比特幣最新源碼拋棄了過去使用兩個map進行參數存儲的作法,而是改成了使用m_override_args這一個map對參數進行KV式存儲,這個map的定義咱們能夠在system.h中找到

std::map<std::string, std::vector<std::string>> m_override_args GUARDED_BY(cs_args);

在使用這個變量時,程序對其使用clear方法進行了清空操做。

m_override_args.clear();

隨後經過for循環實現對全部參數進行逐個解析,獲取參數及其值。

參數處理

獲取了參數以後,接下來咱們來進行下一步,參數處理

 

 

 

// Process help and version before taking care about datadir
    if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { std::string strUsage = PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n"; if (gArgs.IsArgSet("-version")) { strUsage += FormatParagraph(LicenseInfo()) + "\n"; } else { strUsage += "\nUsage: bitcoind [options] Start " PACKAGE_NAME " Daemon\n"; strUsage += "\n" + gArgs.GetHelpMessage(); } tfm::format(std::cout, "%s", strUsage.c_str()); return true; }

這段代碼的註釋的含義爲:在處理數據目錄操做前,先完成版本與幫助命令的處理。因此,經過這段代碼,比特幣後臺進程將可根據用戶輸入相應參數給出對應的程序版本與幫助信息。

好比,當咱們輸入version參數的時候

 

 而當咱們輸入--help或者-h參數的時候

 

 看完了這兩個特殊參數處理,咱們接下來繼續向下看

try { if (!CheckDataDirOption()) { return InitError(strprintf("Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""))); } if (!gArgs.ReadConfigFiles(error, true)) { return InitError(strprintf("Error reading configuration file: %s\n", error)); } // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
        try { SelectParams(gArgs.GetChainName()); } catch (const std::exception& e) { return InitError(strprintf("%s\n", e.what())); } // Error out when loose non-argument tokens are encountered on command line
        for (int i = 1; i < argc; i++) { if (!IsSwitchChar(argv[i][0])) { return InitError(strprintf("Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i])); } } // -server defaults to true for bitcoind but not for the GUI so do this here
        gArgs.SoftSetBoolArg("-server", true);

這段代碼主要在處理配置文件的相關參數。

其中CheckDataDirOption是根據參數檢測配置文件是否存在,若是存在,gArgs.ReadConfigFiles則會去具體讀取配置文件裏的參數,SelectParams(gArgs.GetChainName())會根據網絡參數(main,regtest,test)進行對應的參數檢測以及初始化。

就拿咱們使用的testnet來講,其初始化部分以下:

/** * Testnet (v3) */
class CTestNetParams : public CChainParams { public: CTestNetParams() { strNetworkID = "test"; consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105"); consensus.BIP34Height = 21111; consensus.BIP34Hash = uint256S("0x0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8"); consensus.BIP65Height = 581885; // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6
        consensus.BIP66Height = 330776; // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182
        consensus.CSVHeight = 770112; // 00000000025e930139bac5c6c31a403776da130831ab85be56578f3fa75369bb
        consensus.SegwitHeight = 834624; // 00000000002b980fcd729daaa248fd9316a5200e9b367f4ff2c42453e84201ca
        consensus.powLimit = uint256S("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
        consensus.nPowTargetSpacing = 10 * 60; consensus.fPowAllowMinDifficultyBlocks = true; consensus.fPowNoRetargeting = false; consensus.nRuleChangeActivationThreshold = 1512; // 75% for testchains
        consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing
        consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008
        consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 // The best chain should have at least this much work.
        consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000007dbe94253893cbd463"); // By default assume that the signatures in ancestors of this block are valid.
        consensus.defaultAssumeValid = uint256S("0x0000000000000037a8cd3e06cd5edbfe9dd1dbcc5dacab279376ef7cfc2b4c75"); //1354312
 pchMessageStart[0] = 0x0b; pchMessageStart[1] = 0x11; pchMessageStart[2] = 0x09; pchMessageStart[3] = 0x07; nDefaultPort = 18333; nPruneAfterHeight = 1000; m_assumed_blockchain_size = 30; m_assumed_chain_state_size = 2; genesis = CreateGenesisBlock(1296688602, 414098458, 0x1d00ffff, 1, 50 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); assert(consensus.hashGenesisBlock == uint256S("0x000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")); assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")); vFixedSeeds.clear(); vSeeds.clear(); // nodes with support for servicebits filtering should be at the top
        vSeeds.emplace_back("testnet-seed.bitcoin.jonasschnelli.ch"); vSeeds.emplace_back("seed.tbtc.petertodd.org"); vSeeds.emplace_back("seed.testnet.bitcoin.sprovoost.nl"); vSeeds.emplace_back("testnet-seed.bluematt.me"); // Just a static list of stable node(s), only supports x9
 base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111); base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196); base58Prefixes[SECRET_KEY] =     std::vector<unsigned char>(1,239); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; bech32_hrp = "tb"; vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); fDefaultConsistencyChecks = false; fRequireStandard = false; m_is_test_chain = true; checkpointData = { { {546, uint256S("000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70")}, } }; chainTxData = ChainTxData{ // Data from rpc: getchaintxstats 4096 0000000000000037a8cd3e06cd5edbfe9dd1dbcc5dacab279376ef7cfc2b4c75
            /* nTime */ 1531929919, /* nTxCount */ 19438708, /* dTxRate */ 0.626 }; } };

在這個類裏面,設置了大量參數的初始值。

而至於

// -server defaults to true for bitcoind but not for the GUI so do this here
        gArgs.SoftSetBoolArg("-server", true);

英文註釋已經很明確說明了,bitcoind的時候,server參數默認爲true

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息