Skip to content
Published on

커널을 빌드하고 부팅하기 — 내 노트북 말고 VM에서

공유하기
Authors

들어가며 — 읽기와 실행 사이

1편에서 지도를 그렸고 2편에서 코드를 찾는 법을 다뤘습니다. 여기까지는 텍스트를 다루는 일이었습니다. 이 글부터는 그 텍스트를 실제로 실행되는 커널로 만듭니다.

이 시리즈의 이웃 글 중 이 편에 가장 가까운 것은 커널 파라미터 튜닝 가이드입니다. 그 글은 이미 돌아가는 커널의 동작을 sysctl과 부트 파라미터로 바꾸는 이야기입니다. 이 글은 그 아래 층입니다. 애초에 그 커널을 소스에서 만들어 내고, 만든 것을 안전하게 부팅하는 이야기입니다. 튜닝으로 바꿀 수 있는 것은 커널이 노출한 손잡이뿐이지만, 빌드로는 그 손잡이 자체를 만들거나 없앨 수 있습니다.

한 가지를 먼저 말해 두겠습니다. 이 글은 실습을 가상 머신 안에서 하라고 강하게 권합니다. 그 이유는 6절에 몰아 두었지만, 결론만 먼저 적자면 부팅되지 않는 커널은 부팅되지 않는 컴퓨터이기 때문입니다.

1. 빌드가 만들어 내는 것들

make를 치기 전에 무엇이 나오는지 알아 두면 나중에 헤매지 않습니다. 산출물은 크게 넷입니다.

  • vmlinux — 트리 최상위에 생기는 ELF 실행 파일입니다. 압축되지 않았고 심볼이 들어 있습니다. 부팅에는 쓰이지 않지만 디버깅에는 이게 필요합니다. 6편에서 GDB를 붙일 때 쓰는 파일이 바로 이것입니다.
  • 부팅 이미지 — x86에서는 arch/x86/boot/bzImage입니다. 커널 공식 문서 Documentation/admin-guide/README.rst도 컴파일 후 이 경로의 이미지를 부팅 커널이 있는 자리로 복사하라고 안내합니다.
  • 모듈.ko 파일들입니다. 설정에서 m으로 표시한 것들이 여기 해당합니다.
  • System.mapModule.symvers — 심볼 주소표와 모듈 심볼 정보입니다. 오류 로그를 해독할 때 쓰입니다.

빌드 자체를 시작하기 전에 도구 버전을 한 번 확인해 두는 편이 좋습니다. Documentation/process/changes.rst가 최소 요구 버전을 표로 관리합니다. 2026년 8월 19일 기준으로 확인한 값은 이렇습니다.

도구최소 버전확인 명령
GNU C8.1gcc --version
GNU make4.0make --version
Clang/LLVM (선택)17.0.1clang --version
Rust (선택)1.85.0rustc --version
bindgen (선택)0.71.1bindgen --version

배포판에서 개발 도구를 설치하는 명령은 이 정도입니다.

# Debian/Ubuntu 계열
sudo apt install build-essential libncurses-dev bison flex libssl-dev \
     libelf-dev bc dwarves qemu-system-x86

# Fedora/RHEL 계열
sudo dnf install gcc make ncurses-devel bison flex openssl-devel \
     elfutils-libelf-devel bc dwarves qemu-system-x86

libncurses-dev가 없으면 make menuconfig 화면이 뜨지 않고, libssl-dev가 없으면 모듈 서명 단계에서 멈추고, libelf-devdwarves가 없으면 BTF 관련 설정에서 막힙니다. 이 셋이 초심자를 가장 자주 세우는 항목입니다.

2. .config — 무엇을 통제하는가

커널 빌드의 거의 모든 것은 트리 최상위의 .config 파일 하나로 결정됩니다. 이 파일은 사람이 직접 쓰는 것이 아니라 Kconfig 도구가 생성합니다.

내용은 이런 모양의 줄이 수천 개 이어진 텍스트입니다.

CONFIG_SMP=y
CONFIG_MODULES=y
CONFIG_KPROBES=y
# CONFIG_DEBUG_INFO_NONE is not set
CONFIG_LOCALVERSION="-study"

값은 셋 중 하나입니다.

  • y — 커널 이미지에 직접 포함합니다.
  • m — 모듈로 빌드합니다. 나중에 insmod로 올리고 rmmod로 내릴 수 있습니다.
  • 주석 처리된 "is not set" — 끕니다.

여기서 초심자가 놓치기 쉬운 사실이 하나 있습니다. .config를 손으로 편집하면 안 됩니다. 옵션들 사이에 의존 관계가 있어서, 어떤 항목을 켜면 다른 항목이 자동으로 켜지거나 선택 가능해집니다. 손으로 한 줄만 바꾸면 그 의존 관계가 반영되지 않은 채로 남고, 빌드가 이상하게 실패합니다. 편집이 필요하면 항상 도구를 거치세요.

# 설정을 손댄 뒤에는 반드시 이걸 한 번 돌려서 의존 관계를 정리합니다
make olddefconfig

스크립트로 특정 항목만 바꿔야 한다면 트리 안의 전용 도구를 씁니다. 커널 문서 Documentation/admin-guide/quickly-build-trimmed-linux.rst도 이 방식을 씁니다.

# 디버그 심볼 끄기 — 문서가 제시하는 형태 그대로
./scripts/config --file .config -d DEBUG_INFO \
  -d DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT -d DEBUG_INFO_DWARF4 \
  -d DEBUG_INFO_DWARF5 -e CONFIG_DEBUG_INFO_NONE
make olddefconfig

-e가 켜기, -d가 끄기, -m이 모듈로 만들기입니다.

3. 설정을 만드는 네 가지 방법

defconfig — 무난한 출발점

make defconfig

아키텍처가 준비해 둔 기본 조합을 그대로 씁니다. arch/x86/configs/ 아래에 이 파일들이 있습니다. 무엇을 켜야 할지 전혀 모를 때의 정답입니다.

지금 쓰는 커널의 설정 가져오기

배포판 커널과 최대한 비슷하게 만들고 싶다면 지금 돌아가는 커널의 설정을 그대로 가져올 수 있습니다.

# 배포판이 /boot에 설정을 남겨 둔 경우
cp /boot/config-$(uname -r) .config
make olddefconfig

make olddefconfig가 중요합니다. 가져온 설정은 다른 버전의 커널 것이라 새 옵션이 빠져 있는데, 이 명령이 빠진 항목을 기본값으로 채웁니다. 비슷한 이름의 make oldconfig는 같은 일을 하되 새 옵션마다 사람에게 물어봅니다. 수백 번 답하고 싶지 않다면 olddefconfig를 쓰세요.

localmodconfig — 빌드 시간을 줄이는 지름길

지금 실제로 로드돼 있는 모듈만 남기고 나머지를 전부 꺼 버립니다. 앞서 언급한 커널 문서가 제시하는 형태는 이렇습니다.

yes "" | make localmodconfig

앞의 yes ""는 이 명령이 중간에 던지는 질문에 전부 기본값으로 답하기 위한 것입니다.

효과는 큽니다. 빌드 대상이 크게 줄어 시간과 디스크가 확 절약됩니다. 대신 대가가 있고, 같은 문서가 이를 명시합니다. 최근에 쓰지 않은 기능의 모듈이 빠져 기능을 잃을 수 있습니다. 실습 중에 USB 장치를 꽂았는데 인식되지 않는다면 이 설정 때문일 가능성이 높습니다.

make menuconfig

터미널 기반 메뉴가 뜹니다. 화면 안에서 알아 둘 것은 셋뿐입니다.

  • / 키로 검색. 이게 없으면 수만 개 항목에서 원하는 것을 못 찾습니다. CONFIG_ 접두사를 빼고 이름만 넣으면 됩니다.
  • 검색 결과에 나오는 의존 관계. 어떤 항목이 회색으로 비활성화돼 있다면 반드시 이유가 있습니다. 검색 화면이 그 이유를 알려 줍니다.
  • ? 키로 도움말. 각 항목이 무엇을 하는지 Kconfig에 적힌 설명이 나옵니다.

Kconfig 문서는 menuconfig 외에 nconfig, xconfig, gconfig도 제공한다고 설명합니다. 화면만 다르고 결과는 같습니다.

4. 빌드

명령 자체는 짧습니다

make -j $(nproc --all)

커널 문서가 쓰는 형태 그대로입니다. -j 뒤의 숫자가 동시에 돌릴 컴파일 작업 수입니다.

-j 숫자의 현실

여기서 흔한 오해를 하나 정리하겠습니다. -j를 무작정 크게 주면 빨라진다는 생각입니다. 실제로는 두 가지 벽이 있습니다.

메모리 벽이 먼저 옵니다. 각 컴파일 작업이 메모리를 씁니다. 코어는 많은데 메모리가 부족한 장비에서 -j를 코어 수만큼 주면 스왑이 시작되고, 그 순간부터 오히려 느려집니다. 심하면 OOM Killer가 컴파일러를 죽입니다. 이 상황의 로그를 해석하는 법은 OOM Killer 디버깅 글에 정리해 두었습니다.

링크 단계는 병렬화되지 않습니다. 수만 개 오브젝트를 컴파일하는 구간은 코어 수만큼 빨라지지만, 마지막에 vmlinux를 만드는 링크는 사실상 단일 작업입니다. 즉 -j를 아무리 키워도 줄지 않는 하한이 있습니다.

빌드 시간을 실제 숫자로 적지 않는 이유는, 그 숫자가 설정과 장비에 따라 열 배 이상 벌어지기 때문입니다. 대신 무엇이 시간을 결정하는가는 분명하게 말할 수 있습니다.

  • 설정에서 켠 항목의 수. defconfiglocalmodconfig의 차이가 가장 큽니다.
  • 디버그 심볼. 커널 문서가 디버그 심볼을 끄는 방법을 따로 안내할 만큼 이것이 공간을 많이 잡아먹습니다. 2절의 scripts/config 예제가 그 목적입니다.
  • 두 번째 빌드부터는 훨씬 빠릅니다. 바뀐 파일만 다시 컴파일하기 때문입니다.

반복 빌드가 많다면 ccache를 붙이는 것도 방법입니다.

make CC="ccache gcc" -j $(nproc --all)

빌드가 무슨 명령을 실행하는지 보기

기본 빌드 출력은 조용합니다. 실제 컴파일 명령을 보고 싶으면 문서가 안내하는 대로 V 변수를 씁니다.

make V=1 all      # 실행되는 명령을 그대로 출력
make V=2 all      # 각 타깃이 왜 다시 빌드되는지도 함께 출력

V=2는 "왜 이게 또 컴파일되지?"라는 의문이 생겼을 때 답을 줍니다.

유용한 조회 타깃

make -s kernelrelease   # 설치될 버전 문자열 (LOCALVERSION 반영됨)
make -s kernelversion   # Makefile에 적힌 버전
make -s image_name      # 만들어질 부팅 이미지의 경로

kernelrelease는 설치 전에 반드시 한 번 확인하는 것을 권합니다. 이 문자열이 기존 커널과 같으면 다음 절에서 사고가 납니다.

5. 설치 — 지금 커널을 지우지 않고 옆에 얹기

LOCALVERSION이 안전장치입니다

가장 위험한 실수는 지금 돌아가는 커널과 같은 버전 문자열로 설치하는 것입니다. 이러면 모듈 디렉터리가 덮어써지고, 기존 커널로 되돌아가도 모듈을 못 찾아 부팅이 실패할 수 있습니다.

커널 문서 Documentation/admin-guide/README.rst도 이 상황을 직접 경고합니다. 작업 중인 커널과 같은 버전 번호로 새 커널을 설치한다면 make modules_install 전에 모듈 디렉터리를 백업하라고 안내하고, 그 대안으로 LOCALVERSION 설정으로 고유한 접미사를 붙이라고 권합니다.

# 설정 화면의 General setup 안에도 있지만 스크립트로도 됩니다
./scripts/config --file .config --set-str LOCALVERSION "-study01"
./scripts/config --file .config -d LOCALVERSION_AUTO
make olddefconfig
make -s kernelrelease    # 접미사가 붙었는지 확인

접미사 하나로 모듈 디렉터리 경로와 부트로더 항목이 전부 분리됩니다. 실습마다 숫자를 올려 가며 쓰면 어느 커널이 어느 설정이었는지도 구분됩니다.

설치 명령

# 모듈 설치 후 커널 이미지 설치
sudo make modules_install
sudo make install

커널 문서가 제시하는 방어적인 형태는 이렇습니다. 배포판에 설치 스크립트가 있는지 먼저 확인하고 진행합니다.

command -v installkernel && sudo make modules_install install

make install은 배포판의 설치 스크립트를 호출해 이미지를 /boot에 복사하고 initramfs를 만들고 부트로더 설정을 갱신합니다. 배포판마다 세부가 다릅니다.

패키지로 만들어 설치하기

배포판 패키지 관리자와 어긋나는 것이 싫다면 패키지를 만들어 설치할 수 있습니다. 최상위 Makefile이 제공하는 패키징 타깃 중 자주 쓰는 것들입니다.

make -j $(nproc --all) bindeb-pkg    # Debian/Ubuntu용 바이너리 deb
make -j $(nproc --all) binrpm-pkg    # RPM 계열용 바이너리 rpm
make -j $(nproc --all) tarbz2-pkg    # 압축 tarball

dir-pkg, pacman-pkg, tarzst-pkg 같은 것들도 있습니다. make help의 마지막 부분에 전체 목록이 나옵니다.

되돌아올 길 확보하기

같은 문서가 명확히 조언합니다. 문제가 생길 경우를 대비해 백업 커널을 손 닿는 곳에 두라는 것이고, 특히 개발 릴리스에서는 더욱 그렇다고 덧붙입니다.

실무적으로는 이렇게 합니다.

  • 부트로더 메뉴가 뜨도록 설정해 둡니다. 자동으로 넘어가면 실패했을 때 고를 기회가 없습니다.
  • 기존 커널 항목이 목록에 남아 있는지 설치 후 눈으로 확인합니다.
  • 원격 서버라면 부트로더 메뉴에 접근할 수단(콘솔, IPMI, 클라우드 콘솔)이 있는지 먼저 확인합니다. 이게 없으면 원격 서버에 커널을 설치하면 안 됩니다.

6. 왜 VM이 선택이 아닌가

이 절이 이 글의 핵심입니다.

응용 프로그램은 잘못 만들어도 그 프로그램만 죽습니다. 커널은 다릅니다. 잘못 만든 커널은 부팅하지 않는 컴퓨터가 됩니다. 화면에는 아무것도 나오지 않거나 커널 패닉 메시지가 멈춰 있고, 로그는 디스크에 기록되기 전이라 남지 않습니다. 실수의 대가가 "다시 컴파일"이 아니라 "복구 미디어로 부팅해서 부트로더 고치기"입니다.

여기에 더해, 실습 과정에서 실제로 자주 일어나는 실패는 이런 것들입니다.

  • 3절의 localmodconfig로 만든 커널에 루트 파일시스템의 스토리지 드라이버가 빠져서, 커널은 뜨는데 루트를 못 찾고 멈춥니다.
  • initramfs가 제대로 안 만들어져 같은 증상이 납니다.
  • 5편에서 만들 모듈이 널 포인터를 건드려 커널이 멈춥니다. 응용 프로그램이면 세그멘테이션 폴트로 끝날 일이 여기서는 시스템 정지입니다.

가상 머신 안에서는 이 전부가 창 하나 닫고 다시 띄우는 일이 됩니다. 그래서 VM은 초심자용 우회로가 아니라 표준 작업 환경입니다.

QEMU로 직접 부팅하기

QEMU의 좋은 점은 부트로더도 디스크 이미지도 없이 커널 이미지를 바로 부팅할 수 있다는 것입니다.

qemu-system-x86_64 \
  -kernel arch/x86/boot/bzImage \
  -initrd /path/to/initramfs.cpio.gz \
  -append "console=ttyS0 panic=1" \
  -m 2G -smp 2 -nographic

옵션을 하나씩 보면 이렇습니다.

  • -kernel — 방금 빌드한 이미지를 직접 넘깁니다.
  • -initrd — 초기 램 파일시스템입니다. 아래에서 만듭니다.
  • -append — 커널 명령줄입니다. console=ttyS0이 있어야 커널 출력이 터미널로 나옵니다. panic=1은 패닉 시 1초 뒤 재부팅하라는 뜻이라 무한 대기를 막아 줍니다.
  • -nographic — 그래픽 창 없이 현재 터미널을 콘솔로 씁니다.

-nographic으로 들어가면 QEMU를 빠져나오는 방법을 몰라 당황하기 쉽습니다. Ctrl-a를 누르고 손을 뗀 뒤 x를 누르면 종료됩니다.

최소 루트 파일시스템 만들기

가장 간단한 방법은 정적으로 링크된 BusyBox 하나만 넣은 initramfs입니다.

mkdir -p rootfs/bin
# 배포판에서 정적 빌드된 busybox를 가져오거나 직접 빌드합니다
cp $(which busybox) rootfs/bin/
cd rootfs && ./bin/busybox --install -s bin

cat > init <<'EOF'
#!/bin/sh
/bin/busybox mount -t proc none /proc
/bin/busybox mount -t sysfs none /sys
echo "부팅 완료. uname -a:"
/bin/busybox uname -a
exec /bin/sh
EOF
chmod +x init

find . | cpio -H newc -o | gzip > ../initramfs.cpio.gz

init이라는 이름이 중요합니다. 커널은 initramfs를 펼친 뒤 이 파일을 첫 프로세스로 실행합니다. 없으면 패닉이 납니다.

정적 링크가 아닌 BusyBox를 쓰면 공유 라이브러리가 없어서 실행에 실패합니다. 처음 시도에서 가장 자주 걸리는 지점이라 미리 적어 둡니다.

이 환경은 최소한이지만 5편의 모듈 실습과 6편의 GDB 연결에는 충분합니다. 더 편한 환경을 원한다면 커널 트리를 감지해 자동으로 QEMU를 띄워 주는 외부 도구들이 있습니다. 다만 이 글에서는 검증한 것만 적는다는 원칙에 따라 이름을 나열하지는 않겠습니다. 필요하면 직접 찾아보시되, 위 명령 다섯 줄이 그 도구들이 대신해 주는 일의 전부라는 점만 기억하면 됩니다.

7. 모듈 서명과 Secure Boot

UEFI Secure Boot가 켜진 장비에서는 직접 빌드한 커널이 부팅되지 않습니다. 서명이 없기 때문입니다. 관련된 설정 항목은 다음과 같습니다.

설정하는 일
CONFIG_MODULE_SIG모듈 서명 기능을 활성화합니다
CONFIG_MODULE_SIG_FORCE서명되지 않은 모듈의 로드를 거부합니다
CONFIG_SYSTEM_TRUSTED_KEYS커널에 내장할 신뢰 키 파일을 지정합니다

실습 환경에서의 현실적인 선택은 셋 중 하나입니다. QEMU 안에서 실험한다면 Secure Boot 자체가 없으니 신경 쓸 필요가 없습니다. 물리 장비에서 실습해야 한다면 펌웨어에서 Secure Boot를 끄거나, 자체 키를 등록하는 절차를 거쳐야 합니다. 후자는 이 글의 범위를 넘습니다.

CONFIG_MODULE_SIG_FORCE가 켜진 상태에서 5편의 모듈을 만들면 insmod가 거부합니다. 실습용 커널에서는 꺼 두는 편이 편합니다.

흔한 함정

make menuconfig가 실행되지 않습니다. ncurses 개발 패키지가 없는 경우입니다. 1절의 설치 명령을 확인하세요.

빌드가 인증서 관련 오류로 멈춥니다. 배포판 설정을 복사해 왔을 때 자주 납니다. 배포판이 자기 키 파일 경로를 설정에 박아 두었는데 그 파일이 없기 때문입니다. CONFIG_SYSTEM_TRUSTED_KEYS와 서명 관련 항목을 비우면 넘어갑니다.

설치 후 재부팅했더니 커널 패닉이 나며 루트를 못 찾습니다. 십중팔구 스토리지 드라이버나 파일시스템 드라이버가 커널에도 initramfs에도 없는 경우입니다. localmodconfig로 만든 커널에서 자주 납니다. 해당 드라이버를 y로 바꿔 다시 빌드하면 깔끔하게 해결됩니다.

같은 버전 문자열로 설치해 기존 모듈이 덮어써졌습니다. 5절의 LOCALVERSION을 안 붙인 경우입니다. 예방이 유일한 대책이니 습관으로 만드세요.

QEMU 화면에 아무것도 안 나옵니다. -appendconsole=ttyS0이 빠졌거나, 커널 설정에서 시리얼 콘솔이 꺼져 있는 경우입니다. defconfig 기반이면 대개 켜져 있습니다.

디스크가 가득 찼습니다. 커널 빌드는 디버그 심볼을 켜면 특히 많은 공간을 씁니다. 2절의 scripts/config 예제로 심볼을 끄거나, make mrproper로 정리한 뒤 다시 시작하세요. 다만 mrproper.config도 지우므로 미리 복사해 두어야 합니다.

마치며

빌드는 커널 학습에서 가장 지루하면서도 가장 확실한 관문입니다. 지루한 이유는 새로 배울 개념이 별로 없기 때문이고, 확실한 이유는 이 관문을 통과한 사람만 그 뒤의 모든 실습을 할 수 있기 때문입니다. 모듈도, GDB 연결도, 코드를 고쳐 보는 것도 전부 빌드가 전제입니다.

이 글에서 가장 강조하고 싶었던 것 하나만 다시 적겠습니다. 실습은 가상 머신 안에서 하세요. 커널 실수의 대가는 프로그램 종료가 아니라 시스템 정지이고, 이건 실력 문제가 아니라 커널이라는 대상의 성질입니다. 숙련된 커널 개발자일수록 가상 머신 안에서 일합니다.

다음 편은 이 시리즈의 척추입니다. 시스템 콜 하나를 사용자 공간의 호출부터 커널 안의 실제 구현까지 끝까지 따라갑니다. 커널이 읽을 수 있는 코드라는 것을 확인하게 되는 지점입니다.

이 글의 명령과 설정 이름은 2026년 8월 19일에 공식 문서와 메인라인 트리에서 확인했습니다. 배포판별 설치 절차는 차이가 있으니 사용 중인 배포판 문서를 함께 보시기 바랍니다.

직접 해보기

이전 편과 다음 편

참고 자료

모든 링크는 2026년 8월 19일에 확인했습니다.