index.js 파일안에 내가 쓴 javascript코드는 잘 실행되는데, 조금 더 큰 그림을 보고 싶었다. 그래서 nodejs 소스코드를 다운받아서 중요한것 같은 부분만 읽보고 실행 순서를 추측해 보았다.
심플하게 정리
- node index.js → main()함수 실행
- Global Module( console.log , timer 등등의 기본 모듈) 로드
- 내 index.js 로드 및 실행
- event-loop 실행
실제 코드
아래는 꼬리에 꼬리를 물듯이 node.js 안에서의 함수 호출과 정의를 따라 가본 과정이다. 언어는 C++과 Javascript이다. 함수의 내용물중 내가 잘 모르거나, 필요없어보이는 것들은 삭제하였다.
// 시작은 main 함수!
// src/node_main.cc : 94
int main(int argc, char* argv[]) {
return node::Start(argc, argv);
}
// src/node.cc : 3036
int Start(int argc, char** argv) {
// 아래는 무엇을 하는지 잘 모르겠지만, V8 을 쓰기위해서 준비하는것 같다
PlatformInit();
Init(&args, &exec_args);
InitializeV8Platform(per_process_opts->v8_thread_pool_size);
V8::Initialize();
// 들어가는 인자값이 다른 Start()함수가 3개정도 되는것 같다
const int exit_code = Start(uv_default_loop(), args, exec_args);
}
// src.node.cc : 2988
inline int Start(uv_loop_t* event_loop, const std::vector<std::string>& args, const std::vector<std::string>& exec_args) {
// isolate는 javascript 코드를 실행하는 Virtual Machine의 독립된 복사본이라고 합니다
Isolate* const isolate = NewIsolate(allocator.get(), event_loop);
// 또 Start() 함수.
exit_code = Start(isolate, isolate_data.get(), args, exec_args);
}
// src.node.cc : 2881
inline int Start(Isolate* isolate, IsolateData* isolate_data, const std::vector<std::string>& args, const std::vector<std::string>& exec_args) {{
Environment env(isolate_data, context, v8_platform.GetTracingAgentWriter());
env.Start(args, exec_args, v8_is_profiling); // process object 생성
// 이 부분이 중요하다.
// lib/internal/bootstrap/loaders.js & lib/internal/bootstrap/node.js을 메모리에 로드한뒤 실행시킨다.
// 나의 index.js코드도 여기서 실행된다.
// 아래의 긴~코드들은 여기를 시작점으로 한다.
LoadEnvironment(&env);
// 여기서 이제 루프를 돌린다.
uv_run(env.event_loop(), UV_RUN_DEFAULT);
}
// src/node.cc : 2116
void LoadEnvironment(Environment* env) {
Local<String> loaders_name =
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
MaybeLocal<Function> loaders_bootstrapper =
GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name); // loaders.js 코드를 실행한다
Local<String> node_name =
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/node.js");
MaybeLocal<Function> node_bootstrapper =
GetBootstrapper(env, NodeBootstrapperSource(env), node_name); // node.js 코드를 실행한다
}
// src/node.cc : 2068
static MaybeLocal<Function> GetBootstrapper( Environment* env, Local<String> source, Local<String> script_name) {
// javascript 코드(string)를 실행한다. 가상머신이라는게, javascript 코드를 실행하면서 메모리를 관리해주는 역할을 하는것 같다.
MaybeLocal<Value> bootstrapper_v = ExecuteString(env, source, script_name);
}
// src/node.cc : 1008
static MaybeLocal<Value> ExecuteString(Environment* env, Local<String> source, Local<String> filename) {
// 컴파일 하고
MaybeLocal<Script> script = Script::Compile(env->context(), source, &origin);
// 실행 !
MaybeLocal<Value> result = script.ToLocalChecked()->Run(env->context());
}
loader.js와 node.js를 로드해서 실행하는데, 해당 파일들 안에서 일어나는 일들을 확인해 보자
// loader.js를 실행하고 나서, node.js를 실행하는데, node.js안에서는 이렇게 함수가 호출되어있다.
// 즉, loader.js에서 load에 필요한 함수들을 정의한 뒤에, node.js에서 그걸 사용하면서 실제로 load를 진행한다.
startup();
// lib/internal/bootstrap/node.js : 31
function startup() {
// Make process.argv[1] into a full path.
const path = NativeModule.require('path');
process.argv[1] = path.resolve(process.argv[1]);
// loader.js를 먼저 실행했기 때문에 node.js에서 가져와 쓸 수 있다
const CJSModule = NativeModule.require('internal/modules/cjs/loader.js/loader');
preloadModules();
const fs = NativeModule.require('fs');
const filename = CJSModule._resolveFilename(process.argv[1]);
const source = fs.readFileSync(filename, 'utf-8'); // 나의 index.js파일을 읽어들인다
checkScriptSyntax(source, filename); // 문법 체크
CJSModule.runMain();
}
// lib/internal/modules/cjs/loader.js : 733
Module.runMain = function() {
// argv[1] 안에 index.js가 들어있다.(아마도...)
Module._load(process.argv[1], null, true);
// Handle any nextTicks added in the first tick of the program
process._tickCallback();
};
// lib/internal/modules/cjs/loader.js : 506
Module._load = function(request, parent, isMain) {
// Don't call updateChildren(), Module constructor already does.
var module = new Module(filename, parent);
tryModuleLoad(module, filename);
return module.exports;
};
// lib/internal/modules/cjs/loader.js : 539
function tryModuleLoad(module, filename) {
module.load(filename);
}
// lib/internal/modules/cjs/loader.js : 593
Module.prototype.load = function(filename) {
Module._extensions[extension](this, filename); // == Module._extensions['.js'](this, filename)
};
// lib/internal/modules/cjs/loader.js : 702
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8'); // 파일을 HardDisk에서 읽어 들여서
module._compile(stripBOM(content), filename); // 컴파일!
};
// lib/internal/modules/cjs/loader.js : 654
Module.prototype._compile = function(content, filename) {
// create wrapper function
var wrapper = Module.wrap(content);
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true
});
};
// lib/internal/modules/cjs/loader.js : 125
Module.wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
// lib/internal/modules/cjs/loader.js : 129
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
// wrapper가 실행되면 이렇게 된다.
(function (exports, require, module, __filename, __dirname) {
// 내 index.js code가 여기에 들어간다
// 이런 연유로 exports()나 require()을 내 코드에서 쓸 수 있다
});
// lib/vm.js : 287
function runInThisContext(code, options) {
return createScript(code, options).runInThisContext(options);
}
// lib/vm.js : 89
// Script의 public 함수
runInThisContext(options) {
return super.runInThisContext(...args);
}
// lib/vm.js : 40
// ContextifyScript가 super다
class Script extends ContextifyScript { ~~ }
// lib/vm.js : 25
const {
ContextifyScript,
makeContext,
isContext: _isContext,
compileFunction: _compileFunction
} = internalBinding('contextify');
// src/node_contextify.cc
// 이렇게 등록했기 때문에 아까, javascript 단에서 super.runInThisContext()를 쓸 수 있게된것이다.( c++ 함수를 javascript에서도 쓸수있도록 연결 )
env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext);
// src/node_contextify.cc
static void RunInThisContext(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
// Javascript가상머신이 javascript코드를 실행한다
EvalMachine(env, timeout, display_errors, break_on_sigint, args);
}
// src/node_contextify.cc
static bool EvalMachine(Environment* env,
const int64_t timeout,
const bool display_errors,
const bool break_on_sigint,
const FunctionCallbackInfo<Value>& args) {
Local<Script> script = unbound_script->BindToCurrentContext();
result = script->Run(env->context());
}
// 여기까지가 LoadEnvirenment !
// 이 모든 과정이 끝난 다음에, node.cc로 다시 돌아간다. 그리고 event-loop를 돌린다.
참고 자료
마지막으로
지적과 질문은 환영입니다. 읽어주셔서 감사합니다.