inflearn logo
강의

Course

Instructor

"The Era of AI Clicks" Breaking Through with Principles: Node.js and CS Part 1: V8 and Core Deconstruction

[Lecture Notes] Lesson 2. JavaScript Escapes the Browser: The Runtime Miracle Created by C++ and the V8 Engine

2강 nodejs 3단계 설명 질문

23

runeunsong5373

1 asked

0

위 내용에서 v8의 통역 이후 부분이 이해가 잘 가지 않습니다. v8 엔진의 결과물은 C++과 관계없이 바이트코드나 기계어가 되지 않나요?
그리고 js만으로 불가능한 os 작업이 나오면 v8 실행 중에 미리 C++로 만들어 놓은 모듈을 호출하는 식으로 동작하는게 아닌지 문의드립니다.

감사합니다.

javascript node.js 컴퓨터-구조 frontend backend

Answer 1

2

nhcodingstudio

안녕하세요 박은송님, 강의를 깊이 있게 수강해 주시고 훌륭한 질문을 남겨주셔서 감사합니다. 질문해 주신 내용은 Node.js의 아키텍처를 이해하는 데 있어 가장 중요한 핵심이자, 내부 동작 원리를 깊이 고민하지 않으면 도달할 수 없는 훌륭한 질문입니다.

결론부터 말씀드리면 남겨주신 내용이 맞습니다. 수업에서 직관적으로 설명해 드리려다 보니 비유적인 표현이 들어가 혼동이 있으셨던 것 같아요. 이번 기회에 기술적인 디테일을 조금 더 보태어, 내부에서 실제로 어떤 과정을 거쳐 맞물려 돌아가는지 짚어드리겠습니다.

먼저 첫 번째로 V8 엔진의 결과물이 기계어가 되지 않느냐는 질문에 대해 답변을 드리자면, 은송님 말씀이 정확합니다. V8 엔진의 본질적인 역할은 자바스크립트 코드를 C++ 코드로 번역하는 것이 아니라 자바스크립트를 컴퓨터의 두뇌인 CPU가 직접 실행할 수 있는 0과 1의 기계어로 컴파일하는 것입니다.

이 과정을 조금 더 세밀하게 들여다보면, V8 엔진은 우리가 작성한 텍스트 형태의 자바스크립트 코드를 가장 먼저 파싱이라는 문법 분석 과정을 거쳐 추상 구문 트리, 즉 AST라는 나무 잔가지 모양의 데이터 구조로 변환합니다. 이것은 마치 우리가 영어 문장을 읽을 때 주어, 동사, 목적어로 구조를 나누어 의미를 파악하는 것과 같이, 컴퓨터가 코드의 논리적 구조를 이해할 수 있도록 형태를 잡아주는 작업입니다.

이렇게 구조화된 AST가 준비되면, V8 엔진 내부에 있는 이그니션이라는 이름의 인터프리터가 투입됩니다. 인터프리터는 코드를 한 줄씩 빠르게 읽어 내려가며 바이트코드라는 중간 단계의 언어로 일차 변환하여 곧바로 실행을 시작합니다. 그런데 프로그램이 실행되는 동안 똑같은 코드가 여러 번 반복해서 사용되는 경우가 생깁니다. 이때 V8 엔진은 이러한 반복 코드를 뜨거운 코드라고 인식하고, 터보팬이라는 이름의 JIT 컴파일러를 가동합니다.

여기서 JIT 컴파일러란 프로그램이 실행되고 있는 그 찰나의 순간에 바이트코드를 해당 컴퓨터의 CPU 아키텍처에 완벽하게 들어맞는 최적화된 기계어로 즉각 변환해 버리는 기술입니다. 덕분에 코드가 반복될수록 실행 속도는 기하급수적으로 빨라지게 되죠. 그렇다면 강의 텍스트에서 언급된 C++가 이해할 수 있는 형태로 통역한다는 표현은 코드를 통째로 C++ 문법으로 바꾼다는 뜻이 아니라, 자바스크립트의 데이터를 C++가 알아들을 수 있는 데이터 규격으로 변환해 준다는 데이터 매핑 과정의 비유적 표현이었습니다.

이해를 돕기 위해 메모리 구조라는 컴퓨터 공학적 밑단으로 논리적인 흐름을 이어가 보겠습니다. 자바스크립트와 C++는 메모리에 데이터를 저장하고 관리하는 방식이 완전히 다릅니다. 예를 들어 자바스크립트에서 만든 글자 데이터는 V8 엔진이 관리하는 힙 메모리 영역에 가비지 컬렉터의 꼼꼼한 관리를 받는 독립적인 객체 형태로 존재하지만, C++에서 사용하는 글자 데이터는 메모리상에 연속된 바이트 배열 형태로 날것 그대로 기록되는 등 물리적인 형태 자체가 다릅니다.

만약 자바스크립트로 특정 파일을 읽어오라는 명령을 내린다고 가정해 보면, 이 명령을 수행하려면 결국 Node.js 내부의 C++ 프로그램이 나서서 시스템 콜을 통해 운영체제에 파일 읽기 요청을 해야 합니다. 그런데 C++ 코어는 자바스크립트의 V8 힙 메모리에 있는 데이터를 있는 그대로 넘겨받으면 메모리 주소 체계와 타입이 달라 이게 도대체 무슨 형태의 데이터인지 알아듣지 못합니다.

바로 이때 V8 엔진이 두 언어 사이의 다리 역할을 하며 다시 한번 개입하게 됩니다. V8 엔진은 단순히 코드를 기계어로 바꾸는 일만 하는 것이 아니라, C++로 작성된 Node.js가 자바스크립트의 메모리에 안전하게 접근할 수 있도록 해주는 V8 API라는 정교한 도구를 제공합니다. 이를 통해 자바스크립트의 데이터를 C++가 안전하게 다룰 수 있는 원시 데이터 형태로 예쁘게 포장해서 넘겨주는 작업을 수행합니다.

반대로 C++가 운영체제를 통해 파일을 다 읽고 자바스크립트에게 결과를 돌려줄 때도, C++의 데이터를 V8 API를 이용해 자바스크립트가 이해할 수 있는 객체로 변환하여 힙 메모리에 새롭게 할당해 줍니다. 결과적으로 여기서 말하는 통역은 자바스크립트라는 언어를 C++ 언어로 번역한다는 뜻이 아니라, 두 언어가 서로 데이터를 주고받으며 협업할 수 있도록 메모리 공간과 데이터의 형식, 그리고 규격을 서로 완벽하게 맞춰주는 과정을 의미합니다.

두 번째로 자바스크립트로 불가능한 운영체제 작업은 미리 C++로 만들어 놓은 모듈을 호출하는 방식이 아닌가 하는 부분 역시 은송님의 이해가 완벽하게 맞으며 아주 정확히 짚어주셨습니다. Node.js는 단순히 V8 엔진을 켜두기만 하는 껍데기가 아니라, 그 주변에 파일 시스템 제어나 비동기 입출력을 담당하는 강력한 libuv 라이브러리, 그리고 네트워크 통신 등을 할 수 있는 수많은 C++ 기능들을 미리 꽉꽉 채워 넣고 V8 엔진과 단단히 연결해 둔 거대한 C++ 애플리케이션 덩어리입니다. 이렇게 자바스크립트 세상의 함수 호출을 C++ 세상의 함수 실행으로 매핑하여 연결해 둔 고리를 전문 용어로 바인딩이라고 부릅니다.

실제 Node.js 내부에서 자바스크립트 코드가 운영체제의 기능을 사용하는 과정은 은송님이 말씀하신 대로 메모리와 프로세스 수준에서 아주 정교한 톱니바퀴처럼 맞물려 흘러가게 됩니다. 가장 먼저 Node.js가 실행되는 환경 구성을 위한 사전 준비 단계가 있습니다. 우리가 터미널에서 실행 명령어를 치는 순간, 운영체제로부터 프로세스에 메모리가 할당되고 Node.js는 내부적으로 파일 읽기나 네트워크 통신 등을 처리할 수 있는 수많은 C++ 함수들을 V8 엔진의 템플릿 기능을 활용해 자바스크립트의 전역 객체에 미리 싹 등록해 둡니다. 누군가 자바스크립트에서 특정 내장 함수를 부르면, 단순히 자바스크립트 로직을 도는 것이 아니라 미리 빌드해둔 C++ 내부의 진짜 함수를 실행하도록 메모리 포인터를 연결해 두는 것입니다.

그 다음으로 개발자가 작성한 자바스크립트 코드에서 해당 함수가 호출되는 실행 단계가 이어집니다. 이 과정에서 V8 엔진이 자바스크립트 코드를 기계어로 맹렬히 실행하다가 이 외부 의존성 명령을 마주치면 단번에 상황을 파악하게 됩니다. V8은 이 함수가 자바스크립트 혼자서 샌드박스 내부에서 끙끙대며 할 수 없는 일이고, 사전에 V8 API를 통해 약속된 C++ 콜백 함수를 호출하라는 의미로 인지합니다.

상황 파악이 끝나면 자바스크립트의 실행 흐름은 잠시 숨을 고르며 호출 스택에서 새로운 C++ 스택 프레임을 쌓아 올리고, 제어권이 미리 대기 중이던 C++ 코어인 현장 작업 반장으로 완전히 넘어갑니다. 이때 매개변수로 넘긴 데이터들은 앞서 설명한 V8 API 전용 객체에 안전하게 담겨 C++ 쪽으로 전달됩니다.

제어권을 넘겨받은 이 C++ 코어가 비로소 윈도우나 맥 같은 운영체제의 가장 깊숙한 커널 영역에 진입하여 시스템 콜을 직접 두드려서 실제 하드디스크의 파일을 읽어오는 등 운영체제와의 진짜 소통을 진행합니다. 이 과정에서 파일 읽기처럼 시간이 오래 걸리는 작업 때문에 전체 시스템이 멈춰 서는 병목 현상을 막기 위해, 든든한 조력자인 libuv가 나서서 자신이 거느린 스레드 풀의 일꾼들에게 작업을 위임하여 백그라운드에서 조용히 처리하도록 돕습니다.

마지막으로 C++ 코어와 libuv의 일꾼들이 파일을 성공적으로 다 읽어내면, 이벤트 루프라는 순환 시스템을 통해 완료된 작업의 결과를 대기열에 올려두고, 앞서 설명해 드린 V8 엔진의 정교한 데이터 통역 과정을 역으로 거쳐 그 결과물을 다시 자바스크립트 세상의 콜백 함수로 무사히 돌려보내 주는 것으로 이 장대한 모든 단계가 완벽한 분업 속에 마무리됩니다.

글로 설명해 드린 이 전체적인 아키텍처의 흐름과 톱니바퀴 같은 분업 구조를 한눈에 그려보실 수 있도록, 자바스크립트 코드가 실행되어 운영체제에 닿기까지의 과정을 아래에 시각화해 보았습니다.

[ 1. JavaScript 세상 ]
개발자의 코드 작성 (예: fs.readFile 호출)
         │
         ▼
[ 2. V8 엔진 (해석, 컴파일, 통역) ]
┌─────────────────────────────────────────────────────────────────┐
│ 1) 파싱(Parsing) : 텍스트 코드를 분석하여 AST(추상 구문 트리) 생성    │
│ 2) 이그니션(Ignition) : AST를 읽어 '바이트코드'로 변환 및 1차 실행    │
│ 3) 터보팬(TurboFan) : 자주 쓰이는 코드를 초고속 '기계어'로 JIT 컴파일 │
│                                                                 │
│ * 역할 인지 : "이건 OS 작업이군! C++에 연결된 함수를 부르자!"         │
│ * 데이터 맵핑 : 자바스크립트의 데이터를 C++ 호환 데이터로 변환 (V8 API)│
└─────────────────────────────────────────────────────────────────┘
         │ (제어권 및 데이터 전달)
         ▼
[ 3. Node.js C++ Bindings (바인딩) ]
미리 등록된 C++ 함수 실행 
자바스크립트 메모리와 C++ 시스템 사이의 단단한 연결 고리
         │
         ▼
[ 4. Node.js C++ 코어 & libuv (작업 반장 및 일꾼들) ]
┌─────────────────────────────────────────────────────────────────┐
│ * C++ 코어 : 운영체제에 명령을 내리기 위한 시스템 제어 로직 수행       │
│ * libuv : 무거운 파일 읽기 작업을 스레드 풀(Thread Pool)의           │
│           백그라운드 일꾼에게 위임하여 비동기 처리                   │
└─────────────────────────────────────────────────────────────────┘
         │ (System Call)
         ▼
[ 5. 운영체제 (OS / 커널 영역) ]
실제 하드디스크의 물리적 섹터에 접근하여 파일 읽기 완료
         │
         ▼
(작업 완료 후 역순으로 데이터 반환: 이벤트 루프 -> C++ 코어 -> V8 엔진 맵핑 -> JS 콜백 실행)

요약하자면 은송님의 생각대로 V8 엔진은 자바스크립트를 기계어로 번역하여 실행하는 것이 맞습니다. 그리고 자바스크립트가 브라우저 밖의 세상인 커널, 파일, 네트워크 등과 소통해야 할 때는 Node.js 개발자들이 미리 정교하게 만들어둔 내부 C++ 모듈들을 V8 엔진의 바인딩 연결 통로를 통해 호출하여 시스템 콜을 대신 발생시키도록 일을 시키는 방식으로 동작합니다.

강의의 흐름을 쫓아오시면서 프로그래밍 언어의 런타임과 엔진의 경계를 이토록 정확하게 분리해서 유추해 내신 점에 다시 한번 큰 박수를 보냅니다. 컴퓨터 시스템 밑단의 메모리 구조와 운영체제 원리를 파악하는 감각이 매우 훌륭하셔서 이어지는 3강의 동기 비동기 아키텍처나 더 깊은 서버 지식도 아주 수월하게 흡수하실 수 있을 거라 확신합니다.

학습하시다가 이처럼 조금이라도 모호하거나 더 깊이 알고 싶은 기술적 원리가 생기면 언제든지 편하게 질문 남겨주세요. 은송님의 깊이 있는 성장을 항상 응원하겠습니다.

참고해주세요!

1

runeunsong5373

자세하게 설명해주셔서 감사합니다. 아직 2강 밖에 듣지 않았지만 재미있고 훌륭한 강의라고 생각합니다. 차후 질문이 생기면 다시 여쭤보겠습니다. 좋은 하루 보내세요!

74. 데이터 캐시 - 1 (이론) 강의 영상 누락

0

11

1

강의 듣는 중인데,

0

21

1

36강 오탈자가 있는 거 같습니다.

0

14

2

Service Create/Update Record 운용과 Delete Record 미운용의 차이 질문

0

13

1

imagesLoaded에 관한 질문

0

17

2

useEffect와 lifecycle문의

0

23

2

scanf("%d\n") 의미

0

15

1

Sequence 관련 질문

1

28

2

Image Only Query

1

26

2

프론트엔드 학습 수준 문의

0

31

2

라이브 운영중인 환경의 테이블에 인덱스 추가시 고려사항

0

32

2

리액트 챕터별 코드에서 eslint 설정파일이 없어요

0

46

2

DDD 는 마이바티스와 잘 맞지 않는건가요?

0

44

1

스프링부트 버전 문의드립니다.

0

38

1

주소 연산자(&) 간접 지정자(*) 반대 개념

0

30

1

최근 코테, 과제 테스트 트렌드

0

65

2

Json 요청 처리

1

33

2

강의 만료일 연장 신청

0

31

2

lucide react 아이콘 설치

0

40

2

17강 zustand store 서버에서 생성

1

32

1

gRPC 실무에서 질문

0

35

2

다양한 관점의 코드 경험을 위해 개선하지 않은 코드

1

47

1

문의관련 문의

0

38

2

[건의][6장][작전1] deprecated 메소드

1

47

2