컨테이너나 저사양 임베디드 보드처럼 메모리가 1GB 이하로 제한된 환경에서는 배포판 defconfig 커널조차 여유 공간을 상당히 잡아먹는다. 배포판 커널은 다양한 하드웨어를 지원하기 위해 수많은 드라이버와 서브시스템을 켜둔 상태라, 실제 타깃 환경에서는 전혀 쓰이지 않는 코드가 이미지 크기와 초기 메모리 사용량을 늘린다. 무작정 Kconfig 옵션을 끄기 전에 먼저 “지금 얼마나 쓰고 있는지”를 정확히 재는 기준이 있어야 이후 튜닝의 효과를 비교할 수 있다. 이 글에서는 QEMU 환경에서 defconfig 커널을 빌드하고, initrd 없이 부팅해 이미지 크기와 런타임 메모리 사용량의 베이스라인을 잡는 방법을 정리한다.
이 시리즈의 다른 글
- 메모리가 작은 시스템을 위한 커널 경량화 (2) — initrd 사용에 따른 영향
- 메모리가 작은 시스템을 위한 커널 경량화 (3) — Kconfig 옵션으로 커널 이미지 줄이기
- 메모리가 작은 시스템을 위한 커널 경량화 (4) — 빌드 최적화와 initramfs 축소
- 메모리가 작은 시스템을 위한 커널 경량화 (5) — 커널 메모리 관리 옵션 검토
- 메모리가 작은 시스템을 위한 커널 경량화 (6) — 종합 비교와 메모리 한계 실증
무엇을, 어떻게 측정할까
커널 경량화는 “이미지 크기”와 “런타임 메모리 사용량”이라는 서로 다른 두 축을 함께 다룬다. 이 시리즈에서는 아래 네 가지 수치를 매 편마다 반복 측정해 비교 기준으로 삼는다.
| 측정 대상 | 도구 | 의미 |
|---|---|---|
| vmlinux 크기 | size vmlinux | 심볼 포함 커널 바이너리의 text/data/bss |
| bzImage 크기 | ls -la | 부트로더가 실제로 로드하는 압축 이미지 |
| 부팅 후 RAM | free -m, /proc/meminfo | 커널 + 최소 유저스페이스가 쓰는 실제 메모리 |
| Slab 사용량 | /proc/meminfo의 Slab/SReclaimable/SUnreclaim | 커널 내부 오브젝트 캐시가 먹는 메모리 |
테스트 환경
실기기 없이도 재현 가능하도록 QEMU 게스트를 타깃으로 잡았다. rootfs는 initrd/initramfs를 쓰지 않고 커널이 부팅 시점에 블록 디바이스를 직접 마운트하도록 구성했다 — 실제 임베디드 보드에서 부트로더가 커널만 넘겨주고 별도 initrd 단계를 두지 않는 구성과 동일하다.
| 항목 | 값 |
|---|---|
| 커널 소스 | Linux 7.2.0-rc3, make defconfig |
| 아키텍처 | x86_64 |
| QEMU 메모리 | -m 1024M |
| rootfs | ext4 64MB 이미지, busybox 단독 (initrd 미사용, root=/dev/vda 직접 마운트) |
커널 빌드와 vmlinux 크기
make defconfig
time make -j6 bzImageKernel: arch/x86/boot/bzImage is ready (#1)
real 5m45.792s
user 24m6.092s
sys 6m14.873s
6코어 기준 5분 45초가 걸렸다. defconfig가 켜둔 드라이버(i915 등 그래픽 드라이버 포함)가 많아 이후 Kconfig 옵션을 정리하는 편에서 빌드 시간 자체도 눈에 띄게 줄어들 것으로 예상된다.
size vmlinux text data bss dec hex filename
31123268 8858326 1114804 41096398 27314ce vmlinux
initrd 없이 부팅하기
initrd 없이 root=/dev/vda로 바로 마운트하려면 루트 파일시스템을 인식할 블록 드라이버와 파일시스템이 모듈(=m)이 아니라 커널에 내장(=y)돼 있어야 한다. defconfig에서는 이미 아래처럼 built-in 상태였다.
grep -E "CONFIG_VIRTIO_BLK=|CONFIG_EXT4_FS=" .configCONFIG_VIRTIO_BLK=y
CONFIG_EXT4_FS=y
rootfs 이미지의 /sbin/init은 busybox sh 스크립트 하나로, proc/sysfs를 마운트하고 측정값을 출력한 뒤 곧바로 종료한다.
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "===== free -m ====="
free -m
echo "===== /proc/meminfo (head) ====="
head -5 /proc/meminfo
echo "===== slab total ====="
grep -E "^Slab:|SReclaimable:|SUnreclaim:" /proc/meminfo
echo "===== boot done (no initrd, root=/dev/vda) ====="
poweroff -fqemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-drive file=rootfs.img,format=raw,if=virtio \
-append "root=/dev/vda rw console=ttyS0 quiet" \
-m 1024M \
-nographic \
-no-reboot===== free -m =====
total used free shared buff/cache available
Mem: 969 13 953 0 3 945
Swap: 0 0 0
===== /proc/meminfo (head) =====
MemTotal: 992228 kB
MemFree: 976036 kB
MemAvailable: 967808 kB
Buffers: 108 kB
Cached: 2068 kB
===== slab total =====
Slab: 6712 kB
SReclaimable: 844 kB
SUnreclaim: 5868 kB
===== boot done (no initrd, root=/dev/vda) =====
[ 5.373195] reboot: Power down
측정 결과 요약
| 항목 | 값 |
|---|---|
| vmlinux (심볼 포함) | 53.7 MB (text 29.7MB + data 8.4MB + bss 1.1MB) |
| bzImage (압축, 실제 부팅용) | 14.8 MB |
| 부팅 후 MemTotal | 969 MB (QEMU -m 1024M 기준, 일부는 펌웨어/커널 예약) |
| 부팅 직후 used | 13 MB |
| Slab 총합 | 6.7 MB (SUnreclaim 5.7MB + SReclaimable 0.8MB) |
| 커널 자체 타임스탬프 (userspace 도달) | 5.37초 |
주의사항
- initrd를 쓰지 않는 구성에서는 rootfs를 마운트할 블록 드라이버(
virtio-blk등)와 파일시스템(ext4등)이 반드시 built-in(=y)이어야 한다. 모듈(=m)로 빠지면 root 마운트 시점에 모듈을 로드할 방법이 없어 커널 패닉(VFS: Unable to mount root fs)이 난다. (initrd를 쓰면 이 제약이 사라지는데, 그 트레이드오프는 (2)편에서 다룬다.) 이후 Kconfig 옵션을 하나씩 끌 때도 이 두 옵션은 건드리지 않는다. - 부팅 직후 used 13MB는 busybox 하나만 올라온 극단적으로 단순한 유저스페이스 기준치다. 실제 애플리케이션을 얹으면 당연히 늘어나므로, 이 시리즈에서는 “커널 자체가 강제로 먹는 하한선”을 비교 기준으로 삼는다.
- vmlinux(53.7MB)와 bzImage(14.8MB)의 차이는 대부분 디버그 심볼과 gzip 압축 때문이며, 실제 부팅에 쓰이는 건 bzImage 쪽이다. 이후 편에서 디버그 심볼을 끄면 vmlinux 크기가 먼저 줄어드는 걸 확인할 수 있다.
마무리
defconfig 기준 베이스라인(bzImage 14.8MB, 부팅 직후 used 13MB, Slab 6.7MB)을 확보했다. (2)편에서는 같은 커널을 initrd 구성으로 다시 부팅해 이 베이스라인과 비교하고, (3)편부터는 menuconfig/localmodconfig로 불필요한 드라이버·파일시스템·네트워크 프로토콜과 디버그 옵션을 하나씩 끄면서 이 수치가 어떻게 줄어드는지 같은 방식으로 재측정한다.