[Unity] 내 게임은 어떻게 실행되는가
![[Unity] 내 게임은 어떻게 실행되는가](/_next/image?url=https%3A%2F%2Fdata.develog.develrocket.com%2Fupload%2Fdevelog%2Fuser_1776858831954%2F1782016224393-vrpbxo%2Ff589607d-7160-4925-8f45-0a8edb90c4c7.png&w=3840&q=75)
댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!
이 글은 <Unity Native 이해를 위한 학습 로드맵> 시리즈의 첫 번째 글입니다. 이 시리즈는 Unity의 Fake Null 문제에 대해 포스팅하려다가 그에 앞서 스스로 이해가 부족한 부분이 있어서 시작되었습니다.
CPU가 프로그램을 실행하기 위해 파일을 연다. 그 파일의 일부는 대충 이렇게 생겼다. 01100110 10101010 . 사람은 이해할 수 없지만, 컴퓨터는 이 언어를 가장 빠르게 실행한다. 그 언어의 포맷은 CPU마다 다르지만, 통틀어서 기계어라 부른다.
우리가 C++로 코드를 작성하면, 컴파일러(clang이나 MSVC 등)가 그 코드를 기계어로 번역한다. 이 과정을 통해 .exe나 .dll 같은 파일이 생성되는데, 둘은 모두 컴퓨터가 실행할 수 있는 기계어 파일이다.
.exe는 사용자가 실행하는 진입점.dll은 진입점은 없으나 다른 파일에 의해 실행된다.이 기계어 파일은 네이티브 코드(Native Code) 라고도 부른다.
C#도 컴파일 언어다. 그러나 C# 컴파일러(csc나 Roslyn)는 컴파일의 결과가 기계어가 아니다. 대신 C# 코드를 IL(Intermediate Language, 중간 언어)로 변환한다.
IL은 기계어가 아니므로 CPU가 실행할 수 없다. 따라서 이 IL을 기계어로 변환시켜주는 과정이 따로 필요한데, 이 때 필요한 실행 환경이 CLR(Common Language Runtime)이다. 이 실행 환경 안에서 이루어지는 IL → 기계어 번역 과정이 바로 JIT(Just In Time) 컴파일이다.
C++은 컴파일 즉시 기계어로 번역된다 했다. 이 기계어는 CPU에서 바로 실행 가능한 형태일 것이다.
C#은 컴파일 결과가 IL이다. CLR이 실제 프로그램 실행 도중에 JIT 컴파일을 통해 IL을 기계어로 번역하고, 그렇게 나온 기계어를 CPU가 실행한다.
이 차이가 두 언어의 성격을 완전히 구분 짓는데, 가장 중요한 것은 C++의 컴파일 결과는 플랫폼에 종속된다는 점이고, C#은 그렇지 않다는 점이다.
즉, C++은 컴파일 시점에 특정 플랫폼의 기계어가 결정된다. 예를들어 Windows로 컴파일 했다면 Android에서는 실행되지 않는다. C#은 IL로 컴파일하고, 실행 시점에 JIT컴파일을 통해 기계어로 번역한다. 따라서 ‘이론적으로는’ 어느 플랫폼이든 CLR이 설치되어있다면 실행 가능하다.
Mono는 닷넷 이전부터 존재하던 오픈소스 CLR 구현체다.
IL2CPP는 Unity가 자체 개발한 방식으로, IL을 C++ 코드로 변환한 뒤 다시 기계어로 컴파일한다.
Unity는 오늘날 모바일(Android, IOS), 콘솔(PS5, Xbox, Switch 등) 플랫폼에서 IL2CPP를 기본으로 채택한다. 그 이유는 성능과 보안에 있다.
예를 들어, IOS는 Apple의 정책 상 런타임에 코드 생성을 금지하기에, JIT 방식인 Mono는 사용할 수 없다. 따라서 우리는 IOS를 대상으로 Mono를 사용할 수 없다. 애초에 선택지가 IL2CPP밖에 없다는 것이다.
또 예를 들면, Android는 역사적으로 Microsoft의 CLR 대상이 아니었다. 다행히 Mono는 Android에 대해 JIT 백엔드를 구현해두었기에, 우리는 Mono를 사용하여 Android에서 프로그램을 실행할 수 있었다. 그러나 Mono JIT은 ARM환경에서 성능이 낮다. 런타임 변환 비용이 있기 때문이다. 그리고 IL은 기계어에 비해 역공학이 쉬우므로 코드 보호 측면에서 불리하다. 따라서 IL2CPP를 사용함으로써 성능 문제와 보안 문제를 극복한다.
이제 학습 목표였던 아래 질문에 답할 수 있다.
기계어. ARM, x86, x64 등 다양하고, 세부로 들어가면 이들도 나뉜다.
C++은 컴파일 시점에 플랫폼이 결정된다. 플랫폼에 맞는 기계어가 결과물로 나온다. C#은 컴파일 시점엔 IL이다. JIT 컴파일 방식으로 런타임에 CLR이 기계어로 번역한다. 하지만 보안/성능 상의 이유로 AOT만을 허용하는 플랫폼에서는, IL2CPP를 통해 타겟 플랫폼에 맞는 기계어로 미리 컴파일해야한다. 플랫폼에서는 기계어를 실행할 뿐이다.
C#과 C은 언어이다. 그 소스코드 자체는 플랫폼 중립적이다. 어떤 플랫폼이든 C#과 C의 코드가 의미하는 것은 항상 동일하게 그 자신이다.
실행 환경은 언어 그대로 프로그램이 실행되는 환경이므로, JIT 컴파일도 이 환경에서 이루어진다. CLR도 Runtime이다. 즉 CLR 구현체가 없다면 JIT 컴파일도 없다. 참고로 Runtime은 JIT 컴파일 만을 의미하는 것은 아니다. Runtime에는 힙 메모리 할당(malloc), GC, 타입 검사(캐스팅, 리플랙션, is/as 연산자), try-catch-finally, 스레드 관리, 메모리 불법 접근과 같은 보안 검증 등 많은 일이 이루어진다.
이제 Mono와 IL2CPP에 대해서도 충분히 알았다.