- 이 글은 리눅스 커널을 직접 빌드하고 최소한의 사용자 공간을 구성하여 ‘마이크로 리눅스 배포판’을 만드는 과정을 단계별로 설명
-
운영체제 커널의 역할, 리눅스 배포판의 구성 요소, 그리고 커널과 사용자 공간의 관계를 기초부터 다룸
- 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) 으로도 구성 가능
-
initramfs는 cpio 포맷으로 패키징되며, QEMU에서는 -initrd 옵션으로 로드 가능
initramfs 구축과 “Hello world” 실행
- 최소 요구사항은 /init 바이너리 존재
-
init.c 작성 후 정적 링크(-static)로 빌드
-
cpio -o -H newc < file_list.txt > initramfs.cpio로 패키징
- QEMU 실행 시 “Hello world” 출력 후 init 종료로 다시 커널 패닉 발생
- 해결: init이 종료되지 않도록 무한 루프 추가
Go로 작성한 간단한 셸 추가
-
init이 fork와 execl을 사용해 /little_shell 실행
-
little_shell.go는 사용자 입력을 받아 명령을 에코 출력하는 단순 셸
-
GOOS=linux GOARCH=riscv64 go build little_shell.go로 RISC-V용 빌드
-
init과 little_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-device와 virtio-rng-pci 장치 추가
-
-device virtio-net-device,netdev=usernet -netdev user,id=usernet 옵션 사용
-
u-root의 dhclient로 DHCP를 통해 IP 자동 할당
- 예시: eth0에 10.0.2.15/24 할당
-
wget http://google.com으로 외부 네트워크 접근 성공, index.html 다운로드 확인
패키지 관리자와 init의 중요성
- 일반 배포판은 패키지 관리자를 통해 소프트웨어를 동적으로 설치·업데이트
- 본 실습은 임베디드형 접근으로, 전체 이미지를 재빌드해야 함
-
init은 단순한 프로세스 실행기가 아니라 디바이스 초기화, 서비스 관리, 시스템 부팅 제어의 핵심 구성 요소
-
u-root의 init 소스코드를 통해 다양한 장치(/dev) 설정 과정을 확인 가능
GitHub 저장소