<bitcoind.cpp> 에서 시작.
2018년 5월 15일 master branch 기준으로 분석 시작.
현재 bitcoin 소스는 c++11 표준이하 코드를 사용하고 있는 것 같다. linux 환경 하에서 동작하는 코드를 기준으로 분석 시작!
int main(int argc, char* argv[])
{
// 32bit 시스템에서 glibc 2.10 이상 버전을 사용하는 경우, 강제로 1 arena 로 세팅.
// fallback locale 설정 및 path locale 설정.
SetupEnvironment();
// Connect bitcoind signal handlers
// 함수 이름처럼 ui 를 사용하지 않는 경우의 로깅 callback 등록.
// 이 곳에서는 ThreadSafeMessageBox, ThreadSafeQuestion, InitMessage 에 대한 callback 등록.
// 각각은 ui_interface.h 를 참고.
noui_connect();
// command line argument 들을 가지고 AppInit() 호출.
return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE);
}
AppInit() 의 내용은 아래와 같다.
//////////////////////////////////////////////////////////////////////////////
//
// Start
//
static bool AppInit(int argc, char* argv[])
{
bool fRet = false;
//
// Parameters
//
// If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()
// 이 소스 분석에서는 Qt 를 사용하지 않는 버전의 소스를 따라가기로 한다.
// ArgsManager (util.h 참고) 의 m_available_args 에 현재 가용한 옵션들과 그에 대한 도움말을 세팅
// 컴파일 환경 또는 옵션에 따라서 win32 용, wallet, zmq, UPnP 용 옵션들에 대한 세팅도 진행.
// 실제 SetupServerArgs() 의 내용은 init.cpp 를 참고.
SetupServerArgs();
#if HAVE_DECL_DAEMON // daemon() 을 사용할 수 있다면...
// ArgsManager 에 이에 대한 옵션과 도움말도 추가한다.
gArgs.AddArg("-daemon", _("Run in the background as a daemon and accept commands"), false, OptionsCategory::OPTIONS);
#endif
// command line argument 들을 가지고 ArgsManager 의 ParseParameters() 메소드를 수행.
// (해당 메소드의 코드는 util.cpp 에서 찾을 수 있다.)
// 전달된 command line argument 들을 각각 모두 검사하는데 우선 '=' 문자가 있는지 찾아서 key=val 포멧으로 구분하려고 시도.
// --foo 형태의 key 를 -foo 형태의 key로 치환 작업도 진행.
// -nofoo 형태의 argument 는 -foo=bool(true|false) 형태로 치환 후 bool 이 true 면 ArgsManager의 m_override_args[key].clear() 호출
// 그 외에의 경우는 ArgsManager 의 m_override_args[key].push_back() 호출하여 저장.
// 여기서 m_override_args 는 std::map<std::string, std::vector<std::string>> 타입.
// 만약 command line argument 에 -includeconf 가 존재하고 값이 존재한다면, 해당 argument 는 제거.
gArgs.ParseParameters(argc, argv);
// Process help and version before taking care about datadir
// command line argument 에 "-?", "-h", "-help" 또는 "-version" 이 존재한다면...
if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
// _() 도 함수. (소스는 util.h 참고) - 사용자의 언어에 맞춘 메세지가 등록되어 있다면 해당 메세지로 치환해주는 역할
std::string strUsage = strprintf(_("%s Daemon"), _(PACKAGE_NAME)) + " " + _("version") + " " + FormatFullVersion() + "\n";
// command line argument 에 "-version"이 존재한다면
if (gArgs.IsArgSet("-version"))
{
// LicenseInfo() 소스는 init.cpp 를 참고, FormatParagraph() 는 utilstrencodings.cpp 참고
// 결국 특정 포멧으로 출력되도록 문자열을 formatting.
strUsage += FormatParagraph(LicenseInfo());
}
else
{
strUsage += "\n" + _("Usage:") + "\n" +
" bitcoind [options] " + strprintf(_("Start %s Daemon"), _(PACKAGE_NAME)) + "\n";
strUsage += "\n" + gArgs.GetHelpMessage();
}
// 표준 출력(실행 화면)에 결과 출력
fprintf(stdout, "%s", strUsage.c_str());
return true;
}
try
{
// GetDataDir() 은 util.cpp 참고.
// cache 된 path 가 존재한다면 그것을 리턴,
// command line arguments 에 "-datadir" 로 주어진 패스를 절대 경로로 변환 뒤 디렉토리 만들고 하위에 "wallets" 디렉토리도 생성
// 또는 각 os 환경의 default 위치에 datadir 을 만들고 하위에 wallets 디렉토리까지 생성
// - util.cpp 의 GetDefaultDataDir() 참고.
if (!fs::is_directory(GetDataDir(false)))
{
fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
return false;
}
try
{
// 상위의 ParseParameters() 와 유사한 작업을 하는데 command line arguments 가 아니라 이제 config file 을 읽어들인다.
// "-conf" 로 별도의 설정파일 경로를 지정하지 않았다면 "bitcoin.conf"
// 단, 해당 결과는 ArgsManager 의 m_config_args 에 저장.
// 사용자가 "-noincludeconf" 라고 명시하지 않았다면, 설정 파일에서 "includeconf" 에 명시된 파일에서도 설정들을 읽어들인다.
// 역시 m_config_args 에 저장!
gArgs.ReadConfigFiles();
} catch (const std::exception& e) {
fprintf(stderr,"Error reading configuration file: %s\n", e.what());
return false;
}
// Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
try {
// gArgs.GetChainName() 에서는
// command line argument 들 또는 설정 파일들 상에서 "-regtest" 또는 "-testnet" 관련 설정이 있는지 살펴보고
// 적절하게 CBaseChainParams::REGTEST, CBaseChainParams::TESTNET, CBaseChainParams::MAIN 을
// (chainparamsbase.cpp 참고) 리턴하고, 이 리턴 값을 바탕으로 CBaseChainParams 인스턴스를 생성하여
// static::unique_ptr<CBaseChainParams> globalChainBaseParams 에 할당.
// gArgs.SelectConfigNetwork() 도 호출하여 ArgsManager 의 m_network 에 CBaseChainParams::REGTEST,
// CBaseChainParams::TESTNET, CBaseChainParams::MAIN 값 중 하나를 세팅.
// static std::unique_ptr<CChainParams> globalChainParams 에도 CMainParams(), CTestNetParams(), CRegTestParams() 중 하나를 생성하여 세팅.
SelectParams(gArgs.GetChainName());
} catch (const std::exception& e) {
fprintf(stderr, "Error: %s\n", e.what());
return false;
}
// Error out when loose non-argument tokens are encountered on command line
for (int i = 1; i < argc; i++) {
// command line argument 들의 시작 character 가 '-' 이 아니라면
if (!IsSwitchChar(argv[i][0])) {
fprintf(stderr, "Error: Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i]);
return false;
}
}
// -server defaults to true for bitcoind but not for the GUI so do this here
// "-server" 설정 값이 기존에 설정되어 있다면 그대로 리턴, 그게 아니라면 강제로 m_override_args["-server"] = {1} 로 설정.
gArgs.SoftSetBoolArg("-server", true);
// Set this early so that parameter interactions go to console
// 광역 logger 초기화. (g_logger, logging.cpp 참고)
// g_logger 관련 필드인 m_print_to_file, m_file_path, m_print_to_console, m_log_timestamps, m_log_time_micros 값을 세팅.
// "-logips" 설정에 따라 global 변수인 fLogIPs 에 boolean 값 세팅.
// "Bitcoin Core" + version_string 출력
InitLogging();
// 서로 연관을 줄 수 있는 파라미터들을 rule base 로 조정. (자세한 소스는 init.cpp 참고)
InitParameterInteraction();
// "-sysperms" 설정이 없다면 file mode create mask 를 umask(022) 로 설정. 파일이나 디렉토리 생성시 022 가 default turn off 될 것. user: rwx, group:r-x, other:r-x
// SIGTERM, SIGINT 에 대한 signal handler 를 HandleSIGTERM, 단순히 fRequestShutdown 전역 변수의 값을 true 로 세팅.
// SIGHUP 에 대한 signal handler 를 HandleSIGHUP, g_logger->m_reopen_file = true 로 세팅.
// SIGPIPE 는 무시. 패킷 전송 중 프로그램이 죽을 수 있는 가능성 방지.
// memory allocation 에 실패했을 경우, "Error: Out of memory. Terminating.\n" 이란 로그를 찍고 깨끗한 메모리 상태에서
// 종료될 수 있도록(SIGABRT 이용) std::set_new_handler() 에 새로운 핸들러 등록.
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;
}
// Elliptic curve code 에 대한 초기화를 수행
// "sse4" instruction 을 지원하는지 여부에 따라 SHA256 변환 알고리즘 로직 선택
// 동작하기 위한 ECC, glibc, Random 관련 sanity check.
// Data directory 에 ".lock" 이 존재하는지 체크하고, 없다면 생성 후 해당 파일에 locking 을 시도해 봄.
if (!AppInitSanityChecks())
{
// InitError will have been called with detailed error, which ends up on console
return false;
}
// "-daemon" 설정이 존재한다면
if (gArgs.GetBoolArg("-daemon", false))
{
#if HAVE_DECL_DAEMON
#if defined(MAC_OSX)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
fprintf(stdout, "Bitcoin server starting\n");
// Daemonize
// bitcoind 를 실행시켰던 터미널이 종료되더라도 bitcoind 가 계속 background 에서 실행되도록 하려면
// daemonize 는 필수.
// (daemonize 시에 해당 프로세스의 현재 working directory 를 변경하지 않고
// standard in, standard out, standard err 를 모두 /dev/null 로 리다렉트 시킴.)
if (daemon(1, 0)) { // don't chdir (1), do close FDs (0)
fprintf(stderr, "Error: daemon() failed: %s\n", strerror(errno));
return false;
}
#if defined(MAC_OSX)
#pragma GCC diagnostic pop
#endif
#else
fprintf(stderr, "Error: -daemon is not supported on this operating system\n");
return false;
#endif // HAVE_DECL_DAEMON
}
// Lock data directory after daemonization
// 위에서는 단순히 data directory 에 locking 이 가능한지 체크만 해봤다면, 여기서는 daemonize() 직후
// data directory 에 lock 을 걸고 다른 프로세스가 접근할 수 없도록 함(advisory locking)
// 이 lock 은 이 프로세세가 종료될 때까지 이 lock 을 소유하고 있음.
if (!AppInitLockDataDirectory())
{
// If locking the data directory failed, exit immediately
return false;
}
// 이것도 분량이 너무 많기 때문에 다음 번에 계속해서 관련 내용 포스팅 예정.
fRet = AppInitMain();
}
catch (const std::exception& e) {
PrintExceptionContinue(&e, "AppInit()");
} catch (...) {
PrintExceptionContinue(nullptr, "AppInit()");
}
// 이하 함수들은 모두 init.cpp 를 참고.
if (!fRet)
{
// fRet 이 false 라면(bitcoind 실행에 실패했다면), 생성되어 있던 쓰레드들이 더 이상 작업을 처리하지 않도록 표시.
Interrupt();
} else {
// fRet 이 true 라면(bitcoind 실행에 성공했다면), 위에서 signal handler 에서 값을 세팅했던 fRequestShutdown 의
// 값을 200 ms 마다 체크하여 false 라면 계속 동일 체크 수행.
// 그렇지 않다면, Interrupt() 를 호출하여 생성되어 있던 쓰레드들이 더 이상 작업을 처리하지 않도록 표시.
WaitForShutdown();
}
// 실제 clean up 수행.
Shutdown();
return fRet;
}
✅ @jayson.jeong, congratulations on making your first post! I gave you an upvote!
Please give me a follow and take a moment to read this post regarding commenting and spam.
(tl;dr - if you spam, you will be flagged!)