4P by GN⁺ 9일전 | ★ favorite | 댓글 1개
  • 리눅스 커널을 직접 빌드하고 최소한의 사용자 공간을 구성하여 ‘마이크로 리눅스 배포판’을 만드는 과정을 단계별로 설명
  • 운영체제 커널의 역할, 리눅스 배포판의 구성 요소, 그리고 커널과 사용자 공간의 관계를 기초부터 다룸
  • RISC-V 아키텍처(QEMU의 riscv64 virt 머신)를 예시로 사용하지만, x86 등 다른 아키텍처에도 동일한 원리 적용 가능
  • init 프로세스, initramfs, 그리고 Go로 작성한 간단한 셸을 포함한 직접 실행 가능한 최소 리눅스 환경을 구축
  • 마지막으로 u-root 프로젝트를 이용해 실제 유용한 마이크로 배포판을 만드는 방법을 소개하며, 리눅스 시스템 구조 전반의 이해를 돕는 입문 가이드로 마무리

운영체제 커널이란 무엇인가

  • 커널은 하드웨어 자원 관리와 프로그램 실행 제어를 담당하는 운영체제의 핵심 구성 요소
    • 단일 코어 환경에서도 여러 프로그램이 동시에 실행되는 것처럼 보이게 하는 멀티태스킹 관리 기능 제공
  • 커널은 입출력 장치 제어를 추상화하여 애플리케이션이 하드웨어 세부 주소나 레지스터 값을 직접 다루지 않도록 함
    • 예를 들어, 프로그램은 단순히 “표준 출력에 메시지를 쓰라”고 요청하고, 커널이 실제 하드웨어와의 상호작용을 처리
  • 파일시스템 인터페이스를 통해 고수준의 데이터 접근 방식을 제공
    • 파일은 단순히 디스크 데이터가 아니라, 커널과 통신하는 논리적 인터페이스로 동작
  • 커널은 프로세스 간 격리 및 통신 모델을 제공하여, 각 애플리케이션이 독립적으로 실행되거나 협력할 수 있도록 함
  • Linux 커널은 오픈소스이며 다양한 아키텍처에서 동작 가능, 전 세계적으로 가장 널리 사용되는 커널 중 하나

리눅스 배포판이란 무엇인가

  • 리눅스 커널만으로는 사용자가 웹 브라우저나 GUI 앱을 실행할 수 없으며, 커널 위에 여러 계층의 소프트웨어 인프라가 필요
  • 네트워크 설정, IP 할당, VPN 관리 등은 커널이 아닌 상위 사용자 공간 프로그램이 담당
  • 따라서 리눅스 배포판은 커널 + 사용자 공간 인프라의 조합으로 정의됨
  • 배포판은 커널이 제공하는 기본 기능 위에 패키지, 도구, 설정, 초기화 프로세스(init) 등을 포함
  • 배포판의 복잡도는 다양하며, Arch Linux처럼 최소 구성부터 Ubuntu처럼 사용자 친화적 구성까지 존재

커널 외부의 인프라: 사용자 공간과 init 프로세스

  • 커널이 부팅을 마치면 가장 먼저 PID 1번 프로세스인 init 을 실행
    • init은 이후 모든 사용자 공간 프로세스의 조상으로, 시스템의 서비스와 도구를 순차적으로 실행
  • init이 실행하는 각종 프로세스와 도구의 집합이 리눅스 배포판의 실질적 구성 요소
  • 배포판이 복잡해질수록 불필요한 기능이 쌓여 “bloated” 하다는 비판을 받기도 함
  • 반대로, 커스텀 마이크로 배포판을 만들면 최소한의 기능만 포함된 경량 시스템을 구축 가능

RISC-V용 리눅스 커널 빌드

  • x86 환경에서 크로스 컴파일 도구체인을 이용해 RISC-V용 커널을 빌드
    • kernel.org에서 linux-6.5.2.tar.xz 소스 다운로드 후 make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig 실행
  • menuconfig를 통해 커널 설정을 시각적으로 편집 가능
  • make -j16으로 병렬 빌드 후 arch/riscv/boot/Image 생성
  • QEMU에서 qemu-system-riscv64 -machine virt -kernel arch/riscv/boot/Image로 부팅
    • 부팅 로그에서 SBI 레이어 감지, UART 초기화, printk 활성화 등의 메시지 확인 가능

첫 번째 장애: 루트 파일시스템 없음

  • 커널 부팅 중 VFS: Unable to mount root fs 오류로 커널 패닉 발생
    • 원인: 루트 파일시스템(initramfs)이 제공되지 않음
  • 파일시스템은 디스크뿐 아니라 RAM 기반(initramfs) 으로도 구성 가능
  • initramfscpio 포맷으로 패키징되며, QEMU에서는 -initrd 옵션으로 로드 가능

initramfs 구축과 “Hello world” 실행

  • 최소 요구사항은 /init 바이너리 존재
    • init.c 작성 후 정적 링크(-static)로 빌드
    • cpio -o -H newc < file_list.txt > initramfs.cpio로 패키징
  • QEMU 실행 시 “Hello world” 출력 후 init 종료로 다시 커널 패닉 발생
    • 해결: init이 종료되지 않도록 무한 루프 추가

Go로 작성한 간단한 셸 추가

  • initforkexecl을 사용해 /little_shell 실행
  • little_shell.go는 사용자 입력을 받아 명령을 에코 출력하는 단순 셸
    • GOOS=linux GOARCH=riscv64 go build little_shell.go로 RISC-V용 빌드
  • initlittle_shell 모두 UART를 통해 출력 공유
    • 표준 입출력은 파일 핸들로 관리되며, fork 시 상속됨
  • 결과적으로 “Hello from init”과 셸 입력이 교차 출력되는 기초 리눅스 환경 완성

커널의 역할 정리

  • 하드웨어 추상화: 사용자 프로그램은 UART나 디바이스 세부 정보를 몰라도 출력 가능
  • 고수준 인터페이스 제공: 파일시스템을 통해 다른 바이너리(little_shell) 접근
  • 프로세스 격리: init과 셸은 독립된 메모리 공간에서 실행
  • 커널은 복잡한 하드웨어 위에서 안정적이고 이식성 높은 실행 기반을 제공

운영체제의 정의

  • 커널만을 운영체제로 보기도 하고, 배포판 전체를 운영체제로 보기도 함
  • 중요한 것은 커널과 사용자 공간의 역할 경계와 상호작용 구조를 이해하는 것

u-root로 실제 유용한 마이크로 배포판 만들기

  • u-root 프로젝트Go 기반 사용자 공간 도구 세트를 제공
    • u-root는 리눅스 커널 위에서 실행되는 사용자 공간 부트로더 및 셸 환경 포함
  • 설치 후 GOOS=linux GOARCH=riscv64 u-root 명령으로 initramfs 자동 생성
    • /tmp/initramfs.linux_riscv64.cpio 파일을 QEMU에서 실행 가능
  • 부팅 시 “Welcome to u-root!” 배너와 함께 기본 셸 프롬프트 제공
    • ls, pwd, echo 등 기본 명령어 지원, 탭 완성 기능 포함

네트워크 연결 실습

  • QEMU에 virtio-net-devicevirtio-rng-pci 장치 추가
    • -device virtio-net-device,netdev=usernet -netdev user,id=usernet 옵션 사용
  • u-rootdhclient로 DHCP를 통해 IP 자동 할당
    • 예시: eth010.0.2.15/24 할당
  • wget http://google.com으로 외부 네트워크 접근 성공, index.html 다운로드 확인

패키지 관리자와 init의 중요성

  • 일반 배포판은 패키지 관리자를 통해 소프트웨어를 동적으로 설치·업데이트
    • 본 실습은 임베디드형 접근으로, 전체 이미지를 재빌드해야 함
  • init은 단순한 프로세스 실행기가 아니라 디바이스 초기화, 서비스 관리, 시스템 부팅 제어의 핵심 구성 요소
    • u-rootinit 소스코드를 통해 다양한 장치(/dev) 설정 과정을 확인 가능

GitHub 저장소

  • 본 가이드의 전체 코드와 예제는 popovicu/linux-micro-distro에서 제공
    • initramfs 이미지 빌드 및 실습 재현 가능
Hacker News 의견
  • 몇 달째 마이크로 리눅스 배포판을 직접 만들고 있음
    사용자 모드는 단일 정적 바이너리 하나로 구성되어 있고, confidential microVM 컨테이너를 지원하기 위한 몇 개의 파일만 있음
    특히 initramfs 구조가 흥미로움. 커널이 cpio 아카이브를 풀고 tmpfs로 진입해 /init을 실행하는 과정이 마치 마법 같음
    여러 개의 cpio 아카이브를 이어 붙일 수도 있고, 각각 압축 가능하며 순서대로 오버레이됨
    이 단순하면서도 우아한 설계 덕분에 직접 언팩 코드를 작성하면서 많은 걸 배움

  • 최근에는 qemu가 주요 아키텍처에서 uftrace를 지원하기 시작했음
    전문가들이 “이걸 어떻게 디버깅하지?”라고 물을 때 바로 그 답이 됨
    관련 내용은 이 스레드에서 참고 가능함

  • 나도 비슷한 프로젝트를 진행 중임 — azathos
    직접 만든 toy init, shell, 그리고 몇 가지 유틸리티를 포함하고 있음
    디버깅용으로 GNU coreutils를 넣었고, 지금은 프레임버퍼에 윈도우를 그리는 기능에 집중하고 있음

  • 이 프로젝트 정말 멋짐. 98년에 플로피 기반 “배포판”을 만들어 Windows PC 이미징을 UDP 브로드캐스트로 하던 시절이 떠오름
    “make bzimage”, init 스크립트 오류, 무한 재부팅… 추억이 많음
    요즘 방식도 크게 다르지 않은 게 흥미로움. Raspberry Pi용으로 포팅하면 재미있고 교육적일 것 같음. 직접 시도해볼지도 모름

    • 나도 98년에 Mandrake Linux를 NetBIOS와 ISDN으로 설치하려다 체크섬 오류 때문에 수십 번 다시 시작했던 기억이 있음
      결국 친구가 sftp 내용을 CD로 구워줘서 해결했는데, 그때는 2배속으로만 쓸 수 있었음
  • 이걸 클라우드 이미지로 (예: Vultr, DigitalOcean) 실행하거나 GUI를 띄워 Firefox를 돌리는 게 얼마나 어려울지 궁금함

    • 클라우드 이미지로 돌리는 건 비교적 쉬움. 커널의 기본 드라이버만 있으면 되고, 이미지를 설치하면 됨
      다른 배포판으로 부팅한 뒤 kexec로 자신의 커널을 실행해 메모리 상에서 설치하는 방식도 가능함
      실제 구현 예시는 nixos-anywhere를 참고할 수 있음
    • 네트워크와 스토리지를 위한 virtio 드라이버를 포함한 이미지를 만들고 qcow2로 변환해 DigitalOcean 등에 등록하면 됨
      생각보다 간단한 작업임
  • 이 프로젝트를 Raspberry Pi 대상으로 만든 버전이 있다면 정말 흥미로울 것 같음

  • 왜 이런 걸 직접 만드는지 궁금했는데, 그냥 Gentoo로 리눅스를 탐구하는 건 안 되는지 생각해봄

    • Gentoo는 “소스에서 빌드”하지만, 패키지 매니저가 대부분의 일을 대신해줌
      사용자 공간 커스터마이징은 가능하지만, 리눅스 자체를 배우기엔 적합하지 않음
      stage3 tarball만 봐도 이미 “미니 배포판” 수준임
  • 학습용으로는 정말 좋고, 빠르게 완성하려면 buildroot가 좋은 선택지임

  • 이 글 덕분에 정말 많은 걸 배움. 정보량이 풍부한 포스트라 감사함