比特币源码学习0.13 (四)--私钥公钥

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37847176/article/details/81450432

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


目录


9)交易标准与签名字节数

非标准交易

    fRequireStandard = !GetBoolArg("-acceptnonstdtxn", !Params().RequireStandard());
    if (Params().RequireStandard() && !fRequireStandard)
        return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString()));

Params().RequireStandard()就是返回fRequireStandard

//chainparams.h class CChainParams
/** Policy: Filter transactions that do not match well-defined patterns */
    bool RequireStandard() const { return fRequireStandard; }
    bool fRequireStandard;

params()是在step2中定义的,CChainParams是基类,有三个子类这个之前也介绍过了,三个子类是基于不同的网络的,在主网中fRequireStandard=true,测试网和私有网都是false。也就是主网只接受标准交易,测试网与私有网可以接受非标准交易。
签名字节数

 nBytesPerSigOp = GetArg("-bytespersigop", nBytesPerSigOp);

默认的签名操作字节数为20

//src/policy/policy.cpp
unsigned int nBytesPerSigOp = DEFAULT_BYTES_PER_SIGOP;
//policy.h
/** Default for -bytespersigop */
static const unsigned int DEFAULT_BYTES_PER_SIGOP = 20;

10)钱包相关参数

I 钱包开关

#ifdef ENABLE_WALLET
    if (!CWallet::ParameterInteraction())
        return false;
#endif // ENABLE_WALLET

ENABLE_WALLET宏定义开关在configure.ac文件中,可以在源码编译的时候控制该宏定义的开关

//configure.ac
# Enable wallet
AC_ARG_ENABLE([wallet],
  [AS_HELP_STRING([--disable-wallet],
  [disable wallet (enabled by default)])],
  [enable_wallet=$enableval],
  [enable_wallet=yes])

可以看到默认是打开钱包的,我们可以在运行比特币钱包客户端时通过disablewallet参数关闭钱包功能。
再看CWallet::ParameterInteraction(),如果设置了开启钱包,那么与钱包相关的交互参数必须设置正确,否则程序返回。CWallet::ParameterInteraction()位于src/wallet/wallet.cpp中,分析函数中的各个参数
II -mintxfee
每kb低于mintxfee的费用被视为交易创建时的未付费

if (mapArgs.count("-mintxfee"))
    {
        CAmount n = 0;
        if (ParseMoney(mapArgs["-mintxfee"], n) && n > 0)
            CWallet::minTxFee = CFeeRate(n);
        else
            return InitError(AmountErrMsg("mintxfee", mapArgs["-mintxfee"]));
    }

ParseMoney()位于utilmoneystr.cpp,用于解析传入的数字,转为聪为单位的费用(包括小数的处理,数据格式正确与否)传回计算后的数值。这段的参数处理代码与minRelayTxFee参数的处理基本一致。

//amount.h
static const CAmount COIN = 100000000;

III -fallbackfee
当没有足够的信息用以估算费用时默认使用的费率。
代码中先解析参数是否正确,另外判断参数如果超过最高费率HIGH_TX_FEE_PER_KB会报警告⚠️

//main.h
//! Discourage users to set fees higher than this amount (in satoshis) per kB
static const CAmount HIGH_TX_FEE_PER_KB = 0.01 * COIN;

那么HIGH_TX_FEE_PER_KB为0.01BTC
III -paytxfee与maxtxfee
分别代表支付交易手续费与最高交易手续费,如果高于最高费率HIGH_TX_FEE_PER_KB有警告提示,若是低于最低费率::minRelayTxFee,则报错退出程序。
IV -txconfirmtarget

nTxConfirmTarget = GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);

从注释来看,txconfirmtarget表示如果没有设置paytxfee,则使用足够的费用,以便交易可能在平均n个块(默认值:%u)内开始确认。

//src/wallet/wallet.h
//! -txconfirmtarget default
static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 2;

这里的默认值为2。如果设为6,你的交易获得首个确认将花费平均6个区块链验证时间。从0.14版本开始就是6个了,也就是我们说的比特币每一笔交易都需要经过6个区块确认才能算真正的交易成功。
V spendzeroconfchange
在发送交易时花费未确认的更改,也就是可以花费0确认的费用

 bSpendZeroConfChange = GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);

在这里是默认可以的

 //src/wallet/wallet.h
 //! Default for -spendzeroconfchange
static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true;
//! Default for -sendfreetransactions
static const bool DEFAULT_SEND_FREE_TRANSACTIONS = false;

VI fSendFreeTransactions
如果可能,发送0费用的交易

fSendFreeTransactions = GetBoolArg("-sendfreetransactions", DEFAULT_SEND_FREE_TRANSACTIONS);

默认为不可以,默认不可以发送0手续费的交易


11)交易相关参数
 fIsBareMultisigStd = GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
    fAcceptDatacarrier = GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER);
    nMaxDatacarrierBytes = GetArg("-datacarriersize", nMaxDatacarrierBytes);

I -permitbaremultisig
从注释来看是传递非P2SH多重签名脚本,默认是允许的

Relay non-P2SH multisig (default: %u)"), DEFAULT_PERMIT_BAREMULTISIG
//main.h
/** Default for -permitbaremultisig */
static const bool DEFAULT_PERMIT_BAREMULTISIG = true;

也就是默认非P2SH多重签名的交易在全网传播。
II -datacarrier、-datacarriersize
在帮助信息中找到注释,-datacarrier表示传播和挖矿包含交易以外数据信息的交易,默认设置为true;再看datacarriersize表示包含数据的交易大小默认值

//src/script/standard.cpp
unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY;
//src/script/standard.h
static const unsigned int MAX_OP_RETURN_RELAY = 83; 

其默认值为83字节


12)各种参数

I mocktime
用于回归测试,从新时期(这里指1970年,从输出time” : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT) 推测)开始用n秒替换真实时间

    // Option to startup with mocktime set (used for regression testing):
    SetMockTime(GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op

调用函数SetMockTime,执行赋值操作,一句代码。

//utlitime.cpp
static int64_t nMockTime = 0; //!< For unit testing
void SetMockTime(int64_t nMockTimeIn)
{
    nMockTime = nMockTimeIn;
}

从注释可以看到,mocktime是用于单元测试的,默认值为0。
II peerbloomfilter

if (GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS))
        nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM);
//main.h
static const bool DEFAULT_PEERBLOOMFILTERS = true;

Bloom滤波器支持块过滤和事务处理,默认为true,关于bloom的具体信息可以看《精通比特币(第二版)》8.9Bloom过滤器。在支持过滤器的前提下,程序中设置了当前运行节点的服务模式

//net.cpp
ServiceFlags nLocalServices = NODE_NETWORK;

ServiceFlags是枚举类型,nLocalServices是初始赋值为NODE_NETWORK,在上述代码中又增加赋值NODE_BLOOM,即具备全节点信息存储与bloom过滤器功能,这两者是所有客户端默认具备的。

//protocol.h
/** nServices flags */
enum ServiceFlags : uint64_t {
    // Nothing
    NODE_NONE = 0,
    // NODE_NETWORK means that the node is capable of serving the block chain. It is currently
    // set by all Bitcoin Core nodes, and is unset by SPV clients or other peers that just want
    // network services but don't provide them.
    NODE_NETWORK = (1 << 0),
    // NODE_GETUTXO means the node is capable of responding to the getutxo protocol request.
    // Bitcoin Core does not support this but a patch set called Bitcoin XT does.
    // See BIP 64 for details on how this is implemented.
    NODE_GETUTXO = (1 << 1),
    // NODE_BLOOM means the node is capable and willing to handle bloom-filtered connections.
    // Bitcoin Core nodes used to support this by default, without advertising this bit,
    // but no longer do as of protocol version 70011 (= NO_BLOOM_VERSION)
    NODE_BLOOM = (1 << 2),
    // Indicates that a node can be asked for blocks and transactions including
    // witness data.
    NODE_WITNESS = (1 << 3),
    ···
};

III -rpcserialversion
从帮助信息来看,该参数表示在非冗长模式、非隔离见证(0)或隔离见证(1)模式下原始交易或区块以16进制序列化方式呈现。默认值为1

//src/rpc/server.h
static const unsigned int DEFAULT_RPC_SERIALIZE_VERSION = 1;

代码主要是对取值做判断,该值只能为0或1

if (GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) < 0)
        return InitError("rpcserialversion must be non-negative.");

    if (GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) > 1)
        return InitError("unknown rpcserialversion requested.");

IV maxtipage

 nMaxTipAge = GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE);

结合帮助信息和默认值来看,该参数表示当我们运行的节点包含的区块信息落后主网最长点24小时后,比特币客户端将进行区块同步下载(initial block download)操作。

//main.h
static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60;

关于initial block download(IBD)从官网的解释来看,并不是只能在刚启动时执行,而是当前节点信息落后全网最长链24小时或144个块就会执行。


13)mempoolreplacement
mempoolreplacement:Enable transaction replacement in the memory pool

表示能够替换交易池的交易,这个解释有些笼统,去bitcoinwiki查找transaction replacement,所谓交易替换是指可以在拥有全节点的客户端中替换交易池的交易,即针对同一输入,可以用花费了该部分或全部该输入金额的交易替换交易池中的交易。因此交易池中的交易是可以被替换的,前提是替换的交易产生于同一输入

fEnableReplacement = GetBoolArg("-mempoolreplacement", DEFAULT_ENABLE_REPLACEMENT);
    if ((!fEnableReplacement) && mapArgs.count("-mempoolreplacement")) {
        // Minimal effort at forwards compatibility
        std::string strReplacementModeList = GetArg("-mempoolreplacement", "");  // default is impossible
        std::vector<std::string> vstrReplacementModes;
        boost::split(vstrReplacementModes, strReplacementModeList, boost::is_any_of(","));
        fEnableReplacement = (std::find(vstrReplacementModes.begin(), vstrReplacementModes.end(), "fee") != vstrReplacementModes.end());
    }

先看下其默认值

//main.h
/** Default for -mempoolreplacement */
static const bool DEFAULT_ENABLE_REPLACEMENT = true;

默认打开交易替换,解释一下boost::split,其中vstrReplacementModes是用来存储分割的结果的容器,strReplacementModeList是要分割的内容,按“,“分割,这段就是查找交易池替换模式内容,判断是否包含”fee”模式,如果包含则fEnableReplacement=true.


14)bip9params
首先查看帮助信息

-bip9params=deployment:start:end Use given start/end times for specified bip9 deployment (regtest-only)

bip9params是比特币在私有网测试时使用的参数,作为执行部署、执行部署开始和部署结束时间。bip9信号与激活的相关概念可以参考《精通比特币(第二版)》10.14使用区块版本发出软分叉信号。
bip9之前BIP-34,BIP-66 和 BIP-65 使用的机制成功地激活了三个软分叉,但通过使用块版本的整数值来实现,每次只能激活一个分叉,bip9将块版本解释为bit字段而不是一个整数,以及一些其他的规范。
回到代码部分,主要是针对bip9params参数的值进行私有网络测试部署,以测试软分叉后软件是否运行正常。


从这里开始,之前参考的作者写的就不适合我继续参考了,开始参考另一位博主了–Splay–

9.4 application initialization
 // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log
1)椭圆加密曲线

初始化椭圆曲线

// Initialize elliptic curve code
    ECC_Start();
    globalVerifyHandle.reset(new ECCVerifyHandle());

先看ECC_Start(),实现在key.cpp

void ECC_Start() {
    assert(secp256k1_context_sign == NULL);

    secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
    assert(ctx != NULL);

    {
        // Pass in a random blinding seed to the secp256k1 context.
        unsigned char seed[32];
        LockObject(seed);
        GetRandBytes(seed, 32);
        bool ret = secp256k1_context_randomize(ctx, seed);
        assert(ret);
        UnlockObject(seed);
    }
    secp256k1_context_sign = ctx;
}
2) initsanitycheck

可用性检测,未通过则报错

// Sanity check
    if (!InitSanityCheck())
        return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), _(PACKAGE_NAME)));

主要功能是确保比特币在可用的环境中运行,并提供所有必要的库支持。

//init.cpp
/** Sanity checks
 *  Ensure that Bitcoin is running in a usable environment with all
 *  necessary library support.
 */
bool InitSanityCheck(void)
{
    if(!ECC_InitSanityCheck()) {
        InitError("Elliptic curve cryptography sanity check failure. Aborting.");
        return false;
    }
    if (!glibc_sanity_test() || !glibcxx_sanity_test())
        return false;

    return true;
}

主要是三个验证,ECC_InitSanityCheck()为椭圆曲线加密结果的完整性验证,glibc_sanity_test() 和 glibcxx_sanity_test()验证当前运行环境是否支持C/C++运行环境
I ECC_nitSanityCheck
椭圆曲线加密结果验证

//key.cpp
bool ECC_InitSanityCheck() {
    CKey key;
    key.MakeNewKey(true);
    CPubKey pubkey = key.GetPubKey();
    return key.VerifyPubKey(pubkey);
}
//key.h
/** Check that required EC support is available at runtime. */
bool ECC_InitSanityCheck(void);

首先定义私钥对象,CKey类型

//key.h
/** An encapsulated private key. */
class CKey
{
private:
    //! Whether this private key is valid. We check for correctness when modifying the key
    //! data, so fValid should always correspond to the actual state.
    bool fValid;
    //! Whether the public key corresponding to this private key is (to be) compressed.
    bool fCompressed;
    //! The actual byte data
    unsigned char vch[32];
    //! Check whether the 32-byte array pointed to be vch is valid keydata.
    bool static Check(const unsigned char* vch);

可以看到私钥是保存在长度为32的字符串vch中,我们知道私钥是256位,256/8=32字节。fValid参数用于表示私钥是否有效,该参数是在私钥发生变化时进行相应的修改,即私钥值有效为true。fCompressed表示与这个私钥相符合的公钥是否被压缩,true为压缩公钥。Check检查这32位字节是否是有效的密钥数据。

//key.h class CKey内
public:
    //! Construct an invalid private key.
    CKey() : fValid(false), fCompressed(false)
    {
        LockObject(vch);
    }

定义对象调用构造函数时,该私钥是无效的,对应的公钥是不压缩的。调用 LockObject锁定私钥,从注释分析这个函数

//src/support/pagelocker.h
Functions for directly locking/unlocking memory objects. Intended for non-dynamically allocated structures.
//用于直接锁定/解锁内存对象的函数。用于非动态分配结构。
template <typename T>
void LockObject(const T& t)
{
    LockedPageManager::Instance().LockRange((void*)(&t), sizeof(T));
}

LockRange就是在所有管理范围内,会新增一个锁的计数,具体代码不再展开
之后要创建私钥,来看MakeNewKey()这个函数,传入的参数true代表与私钥相符合的公钥是压缩的

//key.h
//! Generate a new private key using a cryptographic PRNG.
    void MakeNewKey(bool fCompressed);
//key.cpp
void CKey::MakeNewKey(bool fCompressedIn) {
    do {
        GetStrongRandBytes(vch, sizeof(vch));
    } while (!Check(vch));
    fValid = true;
    fCompressed = fCompressedIn;
}

从注释看这个函数通过使用加密PRNG(伪随机数,可以参考《图解密码技术》第12章随机数也可以百度)生成私钥。通过GetStrongRandBytes循环获取私钥,直到满足 Check的条件为止。找到满足的私钥后,设置该密钥为有效,是否压缩赋值。

//random.cpp
void GetStrongRandBytes(unsigned char* out, int num)
{
    assert(num <= 32);
    CSHA512 hasher;
    unsigned char buf[64];
    // First source: OpenSSL's RNG
    RandAddSeedPerfmon();
    GetRandBytes(buf, 32);
    hasher.Write(buf, 32);
    // Second source: OS RNG
    GetOSRand(buf);
    hasher.Write(buf, 32);
    // Produce output
    hasher.Finalize(buf);
    memcpy(out, buf, num);
    memory_cleanse(buf, 64);
}

通过OpenSSL’s RNG获得随机数,然后通过OS RNG获取随机数,生成哈希值,这里使用的是SHA512哈希算法,不过私钥是256位,所以我们只需要32位,之后清除随机数的内存。
Check函数的实现

//key.cpp
bool CKey::Check(const unsigned char *vch) {
    return secp256k1_ec_seckey_verify(secp256k1_context_sign, vch);
}

这个函数是调用libsecp256k1库实现随机数的验证,libsecp256k1库的源码也包含在比特币源码中,位于src/secp256k1文件夹中,在secp256k1.h中有该函数的声明

/** Verify an ECDSA secret key.
 *
 *  Returns: 1: secret key is valid
 *           0: secret key is invalid
 *  Args:    ctx: pointer to a context object (cannot be NULL)
 *  In:      seckey: pointer to a 32-byte secret key (cannot be NULL)
 */

这个函数的功能是验证基于椭圆曲线创建的密钥。
下一步,创建公钥CPubKey pubkey = key.GetPubKey();先看CPubKey类

//pubkey.h class CPubKey
/**
     * Just store the serialized data.
     * Its length can very cheaply be computed from the first byte.
     */
    unsigned char vch[65];
    ···

包含一个参数vch[65]主要用于存储序列化的公钥值,可以通过第一个字节即vch[0]获取公钥的长度,如果该值为2或3则为压缩公钥长度为33,值为4,6,7则为非压缩公钥长度为65。不是上述值,则该公钥值无效。
再看 key.GetPubKey(),该函数通过调用secp256k1库提供的函数首先通过secp256k1_ec_pubkey_create函数创建公钥值,再通过secp256k1_ec_pubkey_serialize函数实现压缩或非压缩公钥序列值的计算。
最后验证公钥 return key.VerifyPubKey(pubkey);

bool CKey::VerifyPubKey(const CPubKey& pubkey) const {
    if (pubkey.IsCompressed() != fCompressed) {
        return false;
    }
    unsigned char rnd[8];
    std::string str = "Bitcoin key verification\n";
    GetRandBytes(rnd, sizeof(rnd));
    uint256 hash;
    CHash256().Write((unsigned char*)str.data(), str.size()).Write(rnd, sizeof(rnd)).Finalize(hash.begin());
    std::vector<unsigned char> vchSig;
    Sign(hash, vchSig);
    return pubkey.Verify(hash, vchSig);
}

获取8字节的随机数,把”Bitcoin key verification”和生成的随机数共同计算哈希值,在sign函数通过该哈希值基于ECADSA算法实现签名的计算,利用签名信息验证获取的公钥的有效性,验证函数为Verify()

 //pubkey.h
 /**
     * Verify a DER signature (~72 bytes).
     * If this public key is not fully valid, the return value will be false.
     */
    bool Verify(const uint256& hash, const std::vector<unsigned char>& vchSig) const;

这个验证函数是验证DER格式的签名,DER是一种编码方案
II C与C++运行环境验证
glibc_sanity_test() 、 !glibcxx_sanity_test()这两个函数就是验证运行环境中C/C++运行库的有效性,即比特币核心软件能否在当前环境中正常运行。

3) 锁定目录结构

确保只有一个比特币进程在使用数据目录

// Make sure only a single Bitcoin process is using the data directory.
    boost::filesystem::path pathLockFile = GetDataDir() / ".lock";
    FILE* file = fopen(pathLockFile.string().c_str(), "a"); // empty lock file; created if it doesn't exist.
    if (file) fclose(file);

    try {
        static boost::interprocess::file_lock lock(pathLockFile.string().c_str());
        if (!lock.try_lock())
            return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), strDataDir, _(PACKAGE_NAME)));
    } catch(const boost::interprocess::interprocess_exception& e) {
        return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running.") + " %s.", strDataDir, _(PACKAGE_NAME), e.what()));
    }
4)创建pid文件

对于非windows系统创建进程的pid文件

#ifndef WIN32
    CreatePidFile(GetPidFile(), getpid());
#endif

参考文章http://siwind.iteye.com/blog/1753517可以知道

(1) pid文件的内容:pid文件为文本文件,内容只有一行, 记录了该进程的ID。 
用cat命令可以看到。 
(2) pid文件的作用:防止进程启动多个副本。只有获得pid文件(固定路径固定文件名)写入权限(F_WRLCK)的进程才能正常启动并把自身的PID写入该文件中。其它同一个程序的多余进程则自动退出。 

那么这段代码创建pid文件的目的就是防止进程启动多个副本,从而打乱原有的消息传输。

5)参数设置

I 限定日志大小

if (GetBoolArg("-shrinkdebugfile", !fDebug))
        ShrinkDebugFile();

-shrinkdebugfile:Shrink debug.log file on client startup (default: 1 when no -debug)
限制日志文件的大小,如果没有设置-debug参数,那么默认值为1

如果设置了这个参数,则调用函数ShrinkDebugFile

//util.cpp
void ShrinkDebugFile()
{
    // Scroll debug.log if it's getting too big
    boost::filesystem::path pathLog = GetDataDir() / "debug.log";
    FILE* file = fopen(pathLog.string().c_str(), "r");
    if (file && boost::filesystem::file_size(pathLog) > 10 * 1000000)
    {
        // Restart the file with some of the end
        std::vector <char> vch(200000,0);
        fseek(file, -((long)vch.size()), SEEK_END);
        int nBytes = fread(begin_ptr(vch), 1, vch.size(), file);
        fclose(file);

        file = fopen(pathLog.string().c_str(), "w");
        if (file)
        {
            fwrite(begin_ptr(vch), 1, nBytes, file);
            fclose(file);
        }
    }
    else if (file != NULL)
        fclose(file);
}

判断debug.log文件大小超过10*1000000(10M)的话就重新读取文件最后200000字节的内容重新保存到debug.log文件中。
II deuglog显示处理

    if (fPrintToDebugLog)
        OpenDebugLog();

默认是打印到测试日志的

//util.cpp
bool fPrintToDebugLog = true;

OpenDebugLog()函数实现如下

//util.cpp
static void DebugPrintInit()
{
    assert(mutexDebugLog == NULL);
    mutexDebugLog = new boost::mutex();
    vMsgsBeforeOpenLog = new list<string>;
}
void OpenDebugLog()
{
    boost::call_once(&DebugPrintInit, debugPrintInitFlag);
    boost::mutex::scoped_lock scoped_lock(*mutexDebugLog);

    assert(fileout == NULL);
    assert(vMsgsBeforeOpenLog);
    boost::filesystem::path pathDebug = GetDataDir() / "debug.log";
    fileout = fopen(pathDebug.string().c_str(), "a");
    if (fileout) setbuf(fileout, NULL); // unbuffered

    // dump buffered messages from before we opened the log
    while (!vMsgsBeforeOpenLog->empty()) {
        FileWriteStr(vMsgsBeforeOpenLog->front(), fileout);
        vMsgsBeforeOpenLog->pop_front();
    }

    delete vMsgsBeforeOpenLog;
    vMsgsBeforeOpenLog = NULL;
}

Boost线程库提供了boost::call_once来支持“一次实现”,并且定义了一个标志boost::once_flag及一个初始化这个标志的宏BOOST_ONCE_INIT。并且是在编译期间初始化而不是运行期间

boost::call_once表示在多线程访问该语句时始终只执行一次调用的函数,来确保mutexDebugLog和 vMsgsBeforeOpenLog的初始化是线程安全的。

//util.cpp
static boost::once_flag debugPrintInitFlag = BOOST_ONCE_INIT;

其中第一个参数是被调用的函数地址,第二个参数类型为boost::once_flag,并初始化这个标志为BOOST_ONCE_INIT
在函数DebugPrintInit()中定义了变量mutexDebugLog,类型为boost::mutex(),表示为互斥锁,在OpenDebugLog()函数中是使用了boost::mutex::scoped_lock来上锁,scoped_lock是能够保证在作用域范围内是互斥访问的,离开作用域时由析构函数自动解锁。在DebugPrintInit()中创建了对象vMsgsBeforeOpenLog,类型为链表,此时链表为空,在OpenDebugLog()中需要在打开日志前转储缓冲信息,就是把vMsgsBeforeOpenLog的信息转到debug.log中,那么vMsgsBeforeOpenLog的内容是怎么获得的呢
在util.cpp的LogPrintStr函数中

// buffer if we haven't opened the log yet
        if (fileout == NULL) {
            assert(vMsgsBeforeOpenLog);
            ret = strTimestamped.length();
            vMsgsBeforeOpenLog->push_back(strTimestamped);
        }

这里就是把日志先存入vMsgsBeforeOpenLog在log文件未打开的时候,所以在OpenDebugLog()之前vMsgsBeforeOpenLog变量已经被创建了,然后LogPrintStr又被调用了很多次,每次都写入一写内容。
III 打印提示信息

 if (!fLogTimestamps)
        LogPrintf("Startup time: %s\n", DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime()));
    LogPrintf("Default data directory %s\n", GetDefaultDataDir().string());
    LogPrintf("Using data directory %s\n", strDataDir);
    LogPrintf("Using config file %s\n", GetConfigFile().string());
    LogPrintf("Using at most %i connections (%i file descriptors available)\n", nMaxConnections, nFD);
    std::ostringstream strErrors;

如果没有设置打印时间戳,就打印起始时间,后面打印默认地址、数据目录之类的信息。


(五)草稿中。。。

没有更多推荐了,返回首页