MongoDB源码分析——mongo主程序入口分析
mongo主程序入口分析
mongo是MongoDB提供的一个执行JavaScript脚本的客户端工具,可以用来和服务端交互,2.6版本的MongoDB已经是使用了V8 JS引擎,由于本人对JS几乎没研究,所以本篇只是大概研究一下执行过程,对于JS的东西就先略过。
mongo的main函数在dbshell.cpp文件中,同mongod一样提供了Windows和Linux两个版本,然后调用int _main( int argc, char* argv[], char **envp )函数。
mongo::isShell = true; //用来判断是否启动shell
setupSignals(); //设置Linux信号处理函数
mongo::shell_utils::RecordMyLocation( argv[ 0 ] );
shellGlobalParams.url = "test";//默认连接到test数据库
//同mongod,根据命令行初始化全局变量
mongo::runGlobalInitializersOrDie(argc, argv, envp);
// hide password from ps output
for ( int i = 0; i < (argc-1); ++i ) {
if ( !strcmp(argv[i], "-p") || !strcmp( argv[i], "--password" ) ) {
char* arg = argv[i + 1];
while ( *arg ) {
*arg++ = 'x';
}
}
}
if (!mongo::serverGlobalParams.quiet)
cout << "MongoDB shell version: " << mongo::versionString << endl;
mongo::StartupTest::runTests();
...
//如果没有配置nodb,则生成连接服务器的js脚本,现在还不会执行
//在之后初始化V8引擎后才会连接服务器
if (!shellGlobalParams.nodb) { // connect to db
stringstream ss;
if (mongo::serverGlobalParams.quiet)
ss << "__quiet = true;";
ss << "db = connect( \""
<< fixHost(shellGlobalParams.url, shellGlobalParams.dbhost, shellGlobalParams.port)
<< "\")";
mongo::shell_utils::_dbConnect = ss.str();
//如果需要认证,则让用户输入密码
if (shellGlobalParams.usingPassword && shellGlobalParams.password.empty()) {
shellGlobalParams.password = mongo::askPassword();
}
}
下面部分代码生成一堆JS验证信息,同样验证也会在V8初始化之后进行(在mongo::shell_utils::initScope函数中)。
stringstream authStringStream;
authStringStream << "(function() { " << endl;
if (!shellGlobalParams.authenticationMechanism.empty()) {
authStringStream << "DB.prototype._defaultAuthenticationMechanism = \"" <<
escape(shellGlobalParams.authenticationMechanism) << "\";" << endl;
}
if (!shellGlobalParams.gssapiServiceName.empty()) {
authStringStream << "DB.prototype._defaultGssapiServiceName = \"" <<
escape(shellGlobalParams.gssapiServiceName) << "\";" << endl;
}
if (!shellGlobalParams.nodb && shellGlobalParams.username.size()) {
authStringStream << "var username = \"" << escape(shellGlobalParams.username) << "\";" <<
endl;
if (shellGlobalParams.usingPassword) {
authStringStream << "var password = \"" << escape(shellGlobalParams.password) << "\";"
<< endl;
}
if (shellGlobalParams.authenticationDatabase.empty()) {
authStringStream << "var authDb = db;" << endl;
}
else {
authStringStream << "var authDb = db.getSiblingDB(\""
<< escape(shellGlobalParams.authenticationDatabase) << "\");" << endl;
}
authStringStream << "authDb._authOrThrow({ " <<
saslCommandUserFieldName << ": username ";
if (shellGlobalParams.usingPassword) {
authStringStream << ", " << saslCommandPasswordFieldName << ": password ";
}
if (!shellGlobalParams.gssapiHostName.empty()) {
authStringStream << ", " << saslCommandServiceHostnameFieldName << ": \""
<< escape(shellGlobalParams.gssapiHostName) << '"' << endl;
}
authStringStream << "});" << endl;
}
authStringStream << "}())";
mongo::shell_utils::_dbAuth = authStringStream.str();
上面部分一如既往的进行全局变量初始化,之后下面部分将会初始化V8引擎
mongo::ScriptEngine::setConnectCallback( mongo::shell_utils::onConnect );
mongo::ScriptEngine::setup();
mongo::globalScriptEngine->setScopeInitCallback( mongo::shell_utils::initScope );
auto_ptr< mongo::Scope > scope( mongo::globalScriptEngine->newScope() );
shellMainScope = scope.get();
首先初始化ScriptEngine,然后设置Scope初始化回调函数initScope ,之后调用newScope()函数返回V8Scope对象,同时连接数据库,根据之前生成的信息进行验证。
V8初始化之后首先会去执行mongorc.js配置文件,这个有点像vim的.vimrc文件,所以一些必须要先执行的代码可以写到mongorc.js文件中。
std::string rcGlobalLocation;
#ifndef _WIN32
rcGlobalLocation = "/etc/mongorc.js" ;
#else
wchar_t programDataPath[MAX_PATH];
if ( S_OK == SHGetFolderPathW(NULL,
CSIDL_COMMON_APPDATA,
NULL,
0,
programDataPath) ) {
rcGlobalLocation = str::stream() << toUtf8String(programDataPath)
<< "\\MongoDB\\mongorc.js";
}
#endif
if ( !rcGlobalLocation.empty() && ::mongo::shell_utils::fileExists(rcGlobalLocation) ) {
if ( ! scope->execFile( rcGlobalLocation , false , true ) ) {
cout << "The \"" << rcGlobalLocation << "\" file could not be executed" << endl;
}
}
从代码中可以看出mongorc.js文件在Windows下路径是%appdata%\MongoDB\mongorc.js,在Linux下面是”/etc/mongorc.js”。
之后一段不是很重要的信息直接略过,来看最后一部分,完成之前的所有配置之后,肯定是启动shell,等待用户输入,执行命令。
shellHistoryInit();
string prompt;
int promptType;
while ( 1 ) {
...
char * line = shellReadline( prompt.c_str() );
...
bool wascmd = false;
{
string cmd = linePtr;
if ( cmd.find( " " ) > 0 )
cmd = cmd.substr( 0 , cmd.find( " " ) );
//判断用户输入是否是命令,如果是则去执行命令
if ( cmd.find( "\"" ) == string::npos ) {
try {
scope->exec( (string)"__iscmd__ = shellHelper[\"" + cmd + "\"];" , "(shellhelp1)" , false , true , true );
if ( scope->getBoolean( "__iscmd__" ) ) {
scope->exec( (string)"shellHelper( \"" + cmd + "\" , \"" + code.substr( cmd.size() ) + "\");" , "(shellhelp2)" , false , true , false );
wascmd = true;
}
}
catch ( std::exception& e ) {
cout << "error2:" << e.what() << endl;
wascmd = true;
}
}
}
//如果不是命令则执行js脚本。
if ( ! wascmd ) {
try {
if ( scope->exec( code.c_str() , "(shell)" , false , true , false ) )
scope->exec( "shellPrintHelper( __lastres__ );" , "(shell2)" , true , true , false );
}
catch ( std::exception& e ) {
cout << "error:" << e.what() << endl;
}
}
//记录用户输入历史
shellHistoryAdd( code.c_str() );
free( line );
}
shellHistoryDone();
上面的代码中我删除了前面部分的判断逻辑,只保留了js的执行部分。可以看到整个流程的核心是scope->exec(…)函数,scope其实是V8Scope对象。
由于目前js方面水平有限,mongo的启动就暂时分析到此,以后有空再深入研究一下V8引擎和js部分的代码。