후드 # 1에서 Node.js-툴 알아보기
본문
나는 최근 The Conf라는 거대한 브라질 컨퍼런스에서 연설하도록 부름 받았습니다.
컨퍼런스의 핵심은 포르투갈어로 말하는 브라질인 뿐만 아니라 온라인에서 녹음 된 대화를 온라인으로 시청함으로써 다른 사람들이 앞으로 혜택을 누릴 수 있도록 영어로 컨텐츠를 작성하는 것입니다.
이전 프레젠테이션에서 제공 한 내용이 원하는 만큼 고급스럽지 않고 깊었다 고 생각했습니다. 그래서 Node.js, JavaScript 및 전체 Node.js 생태계가 실제로 어떻게 작동 하는지에 대한 이야기를 작성하기로 결정했습니다. 이것은 대부분의 프로그래머가 물건 만 사용하지만 실제로는 자신이 하는 일이나 작동 방식을 전혀 알지 못하기 때문입니다.
현재 세계에서 이것은 "괜찮다". 우리는 많은 라이브러리를 가지고 있으며, 우리는 프로세서의 아키텍처에 관한 특정 책에 대한 책과 더 많은 책을 읽을 필요가 없어서 간단한 시계를 조립할 수 있다. 그러나 이것은 우리를 매우 게으르게 만들었습니다. 결국 스택 오버플로 코드를 복사하여 붙여 넣기가 훨씬 쉽습니다.
그래서 이를 염두에 두고 Node.js 내부를 심층 분석하기로 결정했습니다. 적어도 어떻게 사물이 서로 접착 되는지, 그리고 대부분의 코드가 실제로 Node.js 환경에서 실행되는 방식을 보여 주기로 했습니다.
이 주제에 관한 몇 가지 기사 중 첫 번째 기사인데, 제가 이야기를 하기 위해 편집하고 공부했습니다. 나는 많은 것들이 있기 때문에 이 첫 번째 기사에서 모든 참고 문헌을 게시하지는 않을 것입니다. 대신 전체 내용을 여러 기사로 나누고 각 기사는 연구의 일부를 다루며 마지막 기사에서는 참고 문헌과 슬라이드를 내 연설에 게시합니다.
목표
이 전체 시리즈의 목표는 Node.js가 내부적으로 어떻게 작동하는지 이해하는 것입니다. 이것은 주로 Node.js와 JavaScript가 라이브러리로 인해 세계적인 유명 인사이기 때문입니다. 후드 아래에서 작동합니다. 이를 위해 몇 가지 주제를 다룰 것입니다.
1. Node.js 란 무엇입니까?
- Brief history
- Brief history of JavaScript itself
- Elements that are part of Node.js
2. I / O 파일 읽기 함수 호출을 따릅니다.
3. 자바스크립트
1. 후드 아래에서 어떻게 작동합니까?
1. 콜 스택
2. 메모리 할당
4. Libuv
- What is libuv?
- Why do we need it?
- EventLoop
- Microtasks and Macrotasks
5.V8
1. v8이란 무엇입니까?
2. 개요
1. Esprima를 사용한 추상 구문 트리
3. 오래된 컴파일 파이프 라인
1. 전체 codegen
2. Crankshaft
- Hydrogen
- Lithium
4. 새로운 컴파일 파이프 라인
1. Ignition
2. TurboFan
1. 숨겨진 클래스 및 변수 할당
5. 가비지 콜렉션
6. 컴파일러 최적화
- Constant Folding
- Induction Variable Analysis
- Rematerialization
- Removing Recursion
- Deforestation
- Peephole Optimisations
- Inline Expansion
- Inline Caching
- Dead Code Elimination
- Code Block Reordering
- Jump Threading
- Trampolines
- Commom subexpression elimination
Node.js 란 무엇입니까
Node.js는 Ryan Dahl (원래의 제작자)에 의해 "V8 엔진에서 실행되는 라이브러리 세트로 서버에서 JavaScript 코드를 실행할 수 있게 해주는 라이브러리 세트"로 정의되며, Wikipedia는 이를 "오픈 소스, 크로스 -브라우저 외부에서 코드를 실행하는 JavaScript 런타임 환경. "
본질적으로 Node.js는 브라우저 도메인 외부에서 JS를 실행할 수있는 런타임입니다. 그러나 이것이 서버 측 Javascript의 첫 번째 구현은 아닙니다. 1995 년에 Netscape는 서버에서 LiveScript (이전 JavaScript)를 실행할 수 있는 Netscape Enterprise Server를 구현했습니다.
Node.js의 간략한 역사
Node.js는 2009 년에 처음 출시되었으며 Ryan Dahl이 작성했으며 나중에 Joyent가 후원했습니다.
런타임의 전체 출처는 많은 동시 연결을 처리 할 수 있는 Apache HTTP Server (가장 인기 있는 웹 서버)의 제한된 가능성으로 시작됩니다.
또한 Dahl은 순차적 인 코드 작성 방식을 비판하면서 여러 동시 연결의 경우 전체 프로세스 차단 또는 여러 실행 스택으로 이어질 수 있었습니다.
Node.js는 2009 년 11 월 8 일 JSConf EU에서 처음 발표되었습니다. 최근 작성된 libuv 및 저수준 I / O API에서 제공하는 이벤트 루프인 V8을 결합했습니다.
JavaScript 자체의 간략한 역사
Javascript는 ECMAScript 사양을 준수하고 TC39에 의해 유지 관리되는 "고수준의 해석 된 스크립팅 언어"로 정의됩니다.
JS는 1995 년 Brendan Eich에 의해 Netscape 브라우저에 스크립팅 언어로 일하면서 만들어졌습니다. JavaScript는 HTML과 웹 디자이너간에 "접착 어"를 사용하려는 Marc Andreessen의 아이디어를 충족시키기 위해 단독으로 작성되었으며, 코드와 코드를 직접 작성하는 방식으로 이미지 및 플러그인과 같은 구성 요소를 쉽게 조립할 수 있어야 합니다. 웹 페이지 마크 업.
Brendan Eich는 Schscape 언어를 Netscape에 구현하기 위해 채용 되었지만 Netscape 네비게이터에 Java를 포함 시키기 위해 Sun Microsystems와 Netscape 간의 파트너십으로 인해 그의 초점은 비슷한 구문으로 Java와 유사한 언어를 작성하는 것으로 전환되었습니다. . 다른 제안들로부터 JavaScript의 아이디어를 방어하기 위해 Eich는 10 일 동안 작동하는 프로토 타입을 작성했습니다.
ECMA 사양은 1 년 후 Netscape가 표준 사양을 개척하기 위해 JavaScript 언어를 ECMA International에 제출 한 후 다른 브라우저 공급 업체가 Netscape에서 수행 한 작업을 기반으로 구현할 수 있게 되었습니다. 이로 인해 1997 년 최초의 ECMA-262 표준이 되었습니다. ECMAScript-3은 1999 년 12 월에 릴리스되었으며 JavaScript 언어의 현대적인 기준선입니다. ECMAScript 4는 Microsoft가 경쟁 제안이 없고 .NET 언어 서버 측의 부분적이지만 다양한 구현이 있었음에도 불구하고 IE에서 적절한 JavaScript를 협력하거나 구현할 의도가 없었기 때문에 문제가 되었습니다.
2005 년 오픈 소스 및 개발자 커뮤니티는 JavaScript로 수행 할 수있는 작업을 혁신 하기 위해 노력했습니다. 2005 년 Jesse James Garret은 AJAX라는 초안을 발표하여 jQuery, Prototype 및 MooTools와 같은 오픈 소스 라이브러리가 이끄는 JavaScript 사용의 르네상스 결과를 얻었습니다. 2008 년에이 전체 커뮤니티가 JS를 다시 사용하기 시작한 후 ECMAScript 5가 발표되어 2009 년에 시작되었습니다.
Node.js를 구성하는 요소
Node.js는 몇 가지 종속성으로 구성됩니다.
- V8
- Libuv
- http-parser
- c-ares
- OpenSSL
- zlib
이 이미지에는 완벽한 설명이 있습니다.
이를 통해 Node.js를 V8과 Libuv의 두 부분으로 나눌 수 있습니다. V8은 약 70 % C ++ 및 30 % JavaScript이며 Libuv는 거의 C로 작성되었습니다.
예제-I / O 함수 호출
우리의 목표를 달성하기 위해 (그리고 우리가 할 일에 대한 명확한 로드맵을 갖기 위해) 파일을 읽고 화면에 인쇄하는 간단한 프로그램을 작성하는 것부터 시작할 것입니다. 이 코드는 프로그래머가 작성할 수 있는 최적의 코드는 아니지만, 우리가 거쳐야 할 모든 부분에 대한 연구 대상이 될 목적을 달성한다는 것을 알 수 있습니다.
Node.js 소스를 자세히 살펴보면 lib와 src라는 두 가지 기본 폴더가 있습니다. lib 폴더는 프로젝트에 필요한 모든 함수와 모듈의 모든 JavaScript 정의를 포함하는 폴더입니다. src 폴더는 그와 함께 제공되는 C ++ 구현으로, Libuv와 V8이 상주 하는 곳이며, fs, http, crypto 등의 모듈에 대한 모든 구현이 상주 합니다.
이 간단한 프로그램을 보자.
const fs = require('fs')
const path = require('path')
const filePath = path.resolve(`../myDir/myFile.md`)
// Parses the buffer into a string
function callback (data) {
return data.toString()
}
// Transforms the function into a promise
const readFileAsync = (filePath) => {
return new Promise((resolve, reject) => {
fs.readFile(filePath, (err, data) => {
if (err) return reject(err)
return resolve(callback(data))
})
})
}
(() => {
readFileAsync(filePath)
.then(console.log)
.catch(console.error)
})()
예, util.promisify와 fs.promises가 있지만 콜백을 약속으로 수동으로 변환하여 실제로 어떻게 작동하는지 더 잘 이해할 수 있기를 원했습니다.
이 기사에서 다루는 모든 예제는 이 프로그램과 관련이 있습니다. 그리고 이것은 fs.readFile이 V8 또는 JavaScript의 일부가 아니기 때문입니다. 이 함수는 Node.js에 의해 로컬 OS에 대한 C ++ 바인딩으로 만 구현되지만 fs.readFile (path, cb)로 사용하는 고급 API는 JavaScript에서 완전히 구현되어 해당 바인딩을 호출합니다. 이 특정 readFile 함수의 전체 소스 코드는 다음과 같습니다 (전체 파일의 길이는 1850 행이지만 참조에 있음).
// https://github.com/nodejs/node/blob/0e03c449e35e4951e9e9c962ff279ec271e62010/lib/fs.js#L46
const binding = internalBinding('fs');
// https://github.com/nodejs/node/blob/0e03c449e35e4951e9e9c962ff279ec271e62010/lib/fs.js#L58
const { FSReqCallback, statValues } = binding;
// https://github.com/nodejs/node/blob/0e03c449e35e4951e9e9c962ff279ec271e62010/lib/fs.js#L283
function readFile(path, options, callback) {
callback = maybeCallback(callback || options);
options = getOptions(options, { flag: 'r' });
if (!ReadFileContext)
ReadFileContext = require('internal/fs/read_file_context');
const context = new ReadFileContext(callback, options.encoding);
context.isUserFd = isFd(path); // File descriptor ownership
const req = new FSReqCallback();
req.context = context;
req.oncomplete = readFileAfterOpen;
if (context.isUserFd) {
process.nextTick(function tick() {
req.oncomplete(null, path);
});
return;
}
path = getValidatedPath(path);
binding.open(pathModule.toNamespacedPath(path),
stringToFlags(options.flag || 'r'),
0o666,
req);
}
면책 조항 : 현재 최신 커밋 0e03c449e35e4951e9e9c962ff279ec271e62010으로 Github 소스 링크에 코드 참조를 붙여 넣습니다.이 문서는 내가 작성한 시점에서 항상 올바른 구현을 가리키게 됩니다.
5 행 참조? 또 다른 JS 파일 인 read_file_context에 대한 호출이 필요합니다 (참조에도 있음). fs.readFile 소스 코드의 끝에 파일 디스크립터를 열기 위한 C ++ 호출 인 binding.open에 대한 호출이 있으며 경로, C ++ fopen 플래그, 파일 모드 권한을 8 진 형식으로 전달합니다 (0o는 마지막으로 req 변수는 파일 컨텍스트를 수신하는 비동기 콜백 함수입니다.
모든 것과 함께 비공개 내부 C ++ 바인딩 로더 인 internalBinding이 있습니다. NativeModule.require를 통해 사용할 수 있기 때문에 최종 사용자 (예 : 우리)는 액세스 할 수 없습니다. 이것은 실제로 C ++ 코드를 로드 하는 것입니다 . 그리고 이것은 우리가 V8, ALOT에 의존하는 곳입니다.
따라서 기본적으로 위의 코드에서 internalBinding ( 'fs')을 사용하여 fs 바인딩이 필요합니다. internalBinding ( 'fs')은 src / node_file.cc를 호출하고 로드 합니다 (이 전체 파일은 네임 스페이스 fs에 있음). FSReqCallback 및 statValues 함수에 대한 C ++ 구현.
FSReqCallback 함수는 fs.readFile을 호출 할 때 사용되는 비동기 콜백입니다 (fs.readFileSync를 사용하는 경우 FSReqWrapSync라는 다른 함수가 여기에 정의되어 있음). 모든 메소드와 구현은 여기에 정의되고 여기에 바인딩으로 노출됩니다.
// https://github.com/nodejs/node/blob/0e03c449e35e4951e9e9c962ff279ec271e62010/src/node_file.cc
FileHandleReadWrap::FileHandleReadWrap(FileHandle* handle, Local<Object> obj)
: ReqWrap(handle->env(), obj, AsyncWrap::PROVIDER_FSREQCALLBACK),
file_handle_(handle) {}
void FSReqCallback::Reject(Local<Value> reject) {
MakeCallback(env()->oncomplete_string(), 1, &reject);
}
void FSReqCallback::ResolveStat(const uv_stat_t* stat) {
Resolve(FillGlobalStatsArray(env(), use_bigint(), stat));
}
void FSReqCallback::Resolve(Local<Value> value) {
Local<Value> argv[2] {
Null(env()->isolate()),
value
};
MakeCallback(env()->oncomplete_string(),
value->IsUndefined() ? 1 : arraysize(argv),
argv);
}
void FSReqCallback::SetReturnValue(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().SetUndefined();
}
void NewFSReqCallback(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
Environment* env = Environment::GetCurrent(args);
new FSReqCallback(env, args.This(), args[0]->IsTrue());
}
// Create FunctionTemplate for FSReqCallback
Local<FunctionTemplate> fst = env->NewFunctionTemplate(NewFSReqCallback);
fst->InstanceTemplate()->SetInternalFieldCount(1);
fst->Inherit(AsyncWrap::GetConstructorTemplate(env));
Local<String> wrapString =
FIXED_ONE_BYTE_STRING(isolate, "FSReqCallback");
fst->SetClassName(wrapString);
target
->Set(context, wrapString,
fst->GetFunction(env->context()).ToLocalChecked())
.Check();
이 마지막 비트에는 생성자 정의가 있습니다 : Local <FunctionTemplate> fst = env-> NewFunctionTemplate (NewFSReqCallback). 기본적으로 new FSReqCallback()을 호출하면 NewFSReqCallback이 호출됩니다. 이제 컨텍스트 속성이 target-> Set (context, wrapString, fst-> GetFunction) 부분에 어떻게 표시 되는지와 onReject 및 :: Resolve에서 oncomplete도 정의되고 사용되는 방법을 참조하십시오.
req 변수는 컨텍스트로 참조되고 req.context로 설정된 새 ReadFileContext 호출의 결과에 따라 작성됩니다. 이는 req 변수가 FSReqCallback() 함수로 빌드 된 요청 콜백의 C ++ 바인딩 표현이며 해당 컨텍스트를 콜백으로 설정하고 oncomplete 이벤트를 수신함을 의미합니다.
결론
지금 우리는 많이 보지 못했습니다. 그러나 이후 기사에서는 실제 작업 방식과 함수를 사용하여 툴링을 더 잘 이해하는 방법에 대해 점점 더 많이 살펴볼 것입니다!
- 이전글초고속 다국어 프로그래밍 놀이터 "코드"소개 19.09.15
- 다음글Nodejs에서 Api 개발 시작하기 19.09.15