C언어의 시대는 끝났나? 어셈블리 파일 하나 없이 Zig로 x86 커널 띄우기


시스템 프로그래밍, 특히 OS 개발을 ‘찍먹’해볼 때 가장 큰 진입 장벽이 무엇인지 아십니까?

복잡한 메모리 관리? 인터럽트 처리? 아닙니다. 바로 툴체인(Toolchain) 세팅 입니다.

GCC 크로스 컴파일러를 빌드하고, 링커 스크립트와 씨름하고, GRUB 이미지를 굽는 과정에서 이미 진이 다 빠지죠. 그런데 최근 Hacker News에서 꽤 흥미로운 프로젝트를 발견했습니다. 별도의 어셈블리 파일(.S) 하나 없이, 오직 Zig 언어만으로 작성된 미니멀 x86 커널입니다.

오늘은 이 프로젝트를 통해 왜 많은 시니어 엔지니어들이 Rust뿐만 아니라 Zig를 ‘C의 진정한 후계자’로 주목하고 있는지, 그 기술적 함의를 깊게 파보겠습니다.

1. 툴체인 지옥에서의 해방

이 프로젝트(zig-minimal-kernel-x86)의 가장 충격적인 점은 코드 자체가 아니라 빌드 과정 입니다. 보통의 OS 개발 튜토리얼은 binutils 설치부터 시작하지만, 이건 다릅니다.

# Zig 설치 후
zig build
zig build run

이게 끝입니다. 맥북(Apple Silicon)이든 리눅스 머신이든 상관없습니다. Zig는 컴파일러 자체에 LLVM 백엔드와 링커, 그리고 크로스 컴파일 타겟이 내장되어 있습니다. x86-freestanding-none 타겟을 지정하는 것만으로, 호스트 OS에 의존하지 않는 순수한 바이너리를 뱉어냅니다.

저는 이 지점에서 15년 묵은 체증이 내려가는 기분을 느꼈습니다. C/C++ 생태계가 수십 년간 해결하지 못한 ‘의존성 및 빌드 환경의 파편화’ 문제를 Zig는 언어 레벨에서 해결하고 들어갑니다.

2. 어셈블리 파일이 없다? (Zero Assembly Files)

커널 진입점(Entry point)은 보통 boot.S 같은 어셈블리 파일로 작성하여 스택을 초기화하고 C 함수(kmain)로 점프하는 것이 정석입니다. 하지만 이 프로젝트는 그 관행을 깼습니다.

Zig의 comptime과 인라인 어셈블리 기능을 활용해, 멀티부트(Multiboot) 헤더와 진입점을 .zig 파일 하나에 통합했습니다.

Zig Kernel Screenshot

기술적으로는 naked 호출 규약과 asm volatile을 사용한 것이지만, 프로젝트 구조 관리 측면에서 단일 언어로 모든 것을 제어한다 는 것은 유지보수성에 엄청난 이점을 줍니다.

3. VGA 버퍼 직접 제어: C보다 안전하고 명확하다

화면에 글자를 찍기 위해 0xB8000 메모리 주소에 직접 접근하는 코드를 봅시다. C언어였다면 매크로와 포인터 캐스팅이 난무했을 겁니다. Zig는 이를 어떻게 처리할까요?

  • Volatile의 명시성: Zig는 메모리 맵 I/O(MMIO)를 다룰 때 volatile 키워드를 통해 컴파일러 최적화를 방지함을 명확히 합니다.
  • 타입 안전성: 임의의 정수를 포인터로 변환하는 과정이 C보다 엄격하지만, 일단 정의되면 구조체처럼 깔끔하게 다룰 수 있습니다.
// Zig 스타일의 하드웨어 제어 (개념적 예시)
const vga_buffer = @as([*]volatile u16, @ptrFromInt(0xB8000));

이런 코드를 보면 Zig가 ‘더 나은 C(Better C)‘를 지향한다는 말이 체감됩니다. Rust의 unsafe 블록과 Raw Pointer 처리가 다소 장황하게 느껴질 때가 있는데, Zig는 위험한 작업을 하되 그 의도를 명확히 드러내는 실용적인 노선을 택했습니다.

Hacker News의 반응: “왜 굳이 Zig인가?”

이 글에 달린 HN 댓글들의 반응도 흥미롭습니다. 엔지니어들이라면 당연히 던질법한 질문들이 쏟아졌죠.

  1. “Rust로 짤 때보다 코드가 훨씬 짧아서 놀랐다”

    • 저도 동의합니다. Rust로 베어메탈(Bare metal) 개발을 하려면 no_std, Cargo.toml 설정, 타겟 스펙 JSON 등 챙겨야 할 게 많습니다. Zig는 기본적으로 저수준 제어를 가정하고 설계된 느낌이라 코드가 간결합니다.
  2. “이게 커널이냐? 그냥 베어메탈 프로그램이지.”

    • 한 유저가 지적했듯, 엄밀히 말하면 스케줄러나 시스템 콜이 없으니 ‘커널’이라기보단 ‘프리스탠딩 바이너리’가 맞습니다. 하지만 LLVM에서도 OS 없는 환경을 ‘Baremetal’이라 칭하니, 용어 논쟁보다는 진입 장벽을 낮췄다 는 점에 점수를 주고 싶습니다.
  3. “왜 C를 안 쓰고?”

    • 여기에 대한 답글이 걸작입니다. “Zig는 C보다 저수준(lower level)이면서 동시에 더 안전하다.” C의 풋건(Footgun: 발등 찍기)을 방지하면서도, C가 가진 제어권을 100% 보장합니다.

Principal Engineer의 시선: Zig는 ‘장난감’이 아니다

솔직히 고백하자면, 몇 년 전만 해도 저는 Zig를 “C를 대체하겠다는 또 하나의 야심 찬 시도” 정도로 치부했습니다. 하지만 이 프로젝트와 최근의 bun 런타임 등의 행보를 보며 생각이 바뀌었습니다.

Zig는 ‘모던 C’가 갖춰야 할 덕목을 완벽하게 갖췄습니다.

  • 투명성: 숨겨진 제어 흐름이나 과한 런타임 오버헤드가 없습니다.
  • 호환성: 기존 C 헤더를 그대로 include 해서 쓸 수 있습니다(이건 정말 킬러 기능입니다).
  • 빌드 시스템: Make나 CMake의 고통을 끝낼 잠재력이 있습니다.

물론, 아직 프로덕션 레벨의 거대한 OS를 Zig로 작성하기엔 생태계가 Rust만큼 성숙하지 않았습니다. 하지만 임베디드 시스템, 부트로더, 혹은 WASM 런타임 같은 ‘Extreme Low-Level’ 영역에서는 Zig가 Rust보다 더 매력적인 선택지가 될 수 있습니다.

결론: 주말에 심심하다면 이 리포지토리를 클론해 보세요. zig build run을 입력하고 QEMU 창에 뜬 컬러 텍스트를 보는 순간, 잊고 있었던 시스템 프로그래밍의 순수한 즐거움을 다시 느끼게 될 겁니다.

References: