(3)편에서 Kconfig 옵션만으로 bzImage를 31% 줄였다. 여기서 한 걸음 더 들어가면 두 갈래 길이 남는다 — 컴파일러가 코드를 어떻게 만들어내는지(최적화 옵션, LTO)와, 애초에 커널 이미지에 무엇을 실어 보낼지(built-in vs module, initramfs 자체의 크기)다. 이 글에서는 네 가지를 실제로 켜고 꺼보고 재빌드해서, 이론상 좋다고 알려진 선택이 실제로도 그런지 하나씩 확인한다.
이 시리즈의 다른 글
- 메모리가 작은 시스템을 위한 커널 경량화 (1) — 측정 방법론과 베이스라인
- 메모리가 작은 시스템을 위한 커널 경량화 (2) — initrd 사용에 따른 영향
- 메모리가 작은 시스템을 위한 커널 경량화 (3) — Kconfig 옵션으로 커널 이미지 줄이기
- 메모리가 작은 시스템을 위한 커널 경량화 (5) — 커널 메모리 관리 옵션 검토
- 메모리가 작은 시스템을 위한 커널 경량화 (6) — 종합 비교와 메모리 한계 실증
-Os: 크기 우선 최적화
defconfig는 기본적으로 CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE(-O2 계열)를 쓴다. (3)편의 트림된 설정 위에 CONFIG_CC_OPTIMIZE_FOR_SIZE(-Os)로 바꾸고 재빌드했다.
./scripts/config --disable CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE
./scripts/config --enable CONFIG_CC_OPTIMIZE_FOR_SIZE
make olddefconfig
time make -j6 bzImage| 항목 | (3)편 트림 (-O2) | -Os | 변화 |
|---|---|---|---|
| bzImage | 10.27 MB | 8.99 MB | -12.4% |
| vmlinux (text+data+bss) | 29.16 MB | 25.57 MB | -12.3% |
이미지는 확실히 작아졌다. 부팅 시간은 세 번 반복 측정한 결과 2.6~6.8초로 편차가 커서, -Os 자체의 효과라고 단정하긴 어려웠다 — 이 편차의 원인은 아래 주의사항에서 다룬다.
LTO: 항상 작아지지는 않는다
ThinLTO는 컴파일 단위를 넘나들며 전역 최적화를 하기 때문에 이론적으로는 더 작고 빠른 코드를 만들어야 한다. 이 호스트엔 LLVM 18(clang/lld)이 있어 -Os 설정 위에 clang 툴체인 전체로 전환하고 CONFIG_LTO_CLANG_THIN을 켜봤다.
./scripts/config --disable CONFIG_LTO_NONE
./scripts/config --enable CONFIG_LTO_CLANG_THIN
make LLVM=1 olddefconfig
time make LLVM=1 -j6 bzImage| 항목 | -Os (gcc) | -Os + ThinLTO (clang) | 변화 |
|---|---|---|---|
| bzImage | 8.99 MB | 9.89 MB | +10.1% |
| vmlinux (text+data+bss) | 25.57 MB | 28.21 MB | +10.3% |
오히려 커졌다. gcc에서 clang으로 툴체인 자체가 바뀐 게 변수라 “LTO 때문에 커졌다”고 단정할 순 없지만, 적어도 이 조합에서는 ThinLTO가 크기 절감을 보장하지 않는다는 것만은 실측으로 확인됐다. 크기가 중요한 타깃이라면 LTO를 켜기 전에 반드시 이렇게 직접 재봐야 한다.
module vs builtin: VIRTIO_NET으로 실측
이 QEMU 테스트는 네트워크를 쓰지 않는다. -Os 설정에서 CONFIG_VIRTIO_NET을 built-in(y)에서 module(m)로 바꿔 얼마나 빠지는지 봤다.
./scripts/config --module CONFIG_VIRTIO_NET
make olddefconfig
make -j6 bzImage modules
ls -la drivers/net/virtio_net.ko| 항목 | VIRTIO_NET=y | VIRTIO_NET=m | 변화 |
|---|---|---|---|
| bzImage | 8.99 MB | 8.95 MB | -41 KB |
| vmlinux (text+data+bss) | 25.57 MB | 25.50 MB | -73 KB |
| virtio_net.ko | – | 168 KB | +168 KB (필요 시) |
부팅 이미지만 보면 41KB 줄었다. 하지만 그 기능이 실제로 필요해서 모듈을 로드해야 하는 상황이라면 168KB짜리 .ko를 더해야 하니 합계는 오히려 127KB 늘어난다. module化가 이득인 경우는 “이 배포본에서는 아예 안 쓴다”고 확신할 때뿐이다.
initramfs 자체 축소: busybox 최소 재빌드
(2)편에서 쓴 initramfs는 272개 애플릿을 전부 담은 배포판 busybox-static이었다. 우리 init 스크립트가 실제로 쓰는 애플릿은 sh, mount, echo, free, head, grep, poweroff 7개뿐이라, busybox를 직접 그 구성으로만 재빌드했다.
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar xjf busybox-1.36.1.tar.bz2 && cd busybox-1.36.1
make allnoconfig
for c in STATIC FREE GREP ECHO HEAD SH_IS_ASH ASH MOUNT POWEROFF; do
sed -i "s/# CONFIG_${c} is not set/CONFIG_${c}=y/" .config
done
yes "" | make oldconfig
make -j6| 항목 | 배포판 busybox-static (272 애플릿) | 커스텀 (9 애플릿) | 변화 |
|---|---|---|---|
| busybox 바이너리 | 2.02 MB | 1.30 MB | -35.7% |
| initramfs (압축) | 1.06 MB | 0.64 MB | -42.5% |
| 부팅 직후 used | 19 MB | 13.75 MB | -27.6% |
| Slab 총합 | 6.75 MB | 5.33 MB | -21% |
애플릿을 272개에서 9개로 97% 줄였는데 바이너리는 36%밖에 안 줄었다. ash 셸 인터프리터 자체가 busybox에서 가장 큰 단일 구성요소라 애플릿 개수보다 셸 포함 여부가 크기를 더 많이 좌우한다.
주의사항
- 이 테스트 호스트에는
/dev/kvm이 없다(중첩 가상화 미지원) — 즉 시리즈 내내 QEMU가 KVM 가속 없이 순수 소프트웨어 에뮬레이션(TCG)으로 돌아갔다. 그래서 매 부팅 로그에MP-BIOS bug: 8254 timer not connected to IO-APIC경고가 반복됐고, 이번 편에서는 같은 바이너리로 5번 중 1~2번꼴로Kernel panic - IO-APIC + timer doesn't work!가 실제로 재현됐다. 이미지 크기·RAM/Slab 수치는 빌드가 결정하는 값이라 신뢰할 수 있지만, 부팅 시간과 부팅 성공 자체는 이 환경에서 노이즈가 크다 — 값 하나만 보고 판단하지 말고 여러 번 돌려봐야 한다. - 최소 busybox에서
free -m은 옵션이 무시되고 항상 KB 단위로 나온다 —-b/-k/-m/-g/-h플래그는CONFIG_DESKTOP에 종속돼 있는데allnoconfig기준 최소 구성엔 빠져 있기 때문이다.head -5같은 GNU 스타일 축약 옵션도CONFIG_FEATURE_FANCY_HEAD가 없으면invalid option에러가 난다(head -n 5로는 정상 동작). 실제로 이 글의 init 스크립트를 최소 busybox에 그대로 옮겼다가 두 에러를 그대로 만났다 — 애플릿만 켠다고 끝이 아니라 그 안의 서브 기능(feature) 의존성도 확인해야 한다. - LTO/컴파일러 비교는 gcc→clang 툴체인 전환이 함께 섞여 있어 “LTO 자체의 효과”만 순수하게 분리한 결과는 아니다. 정확히 분리하려면 clang으로 LTO 없이 빌드한 값도 따로 재야 하는데, 이 글에서는 거기까지는 하지 않았다.
마무리
(1)편 defconfig 베이스라인(bzImage 14.8MB) 대비 (3)편 Kconfig 정리와 이 글의 -Os를 더하면 8.99MB로 39% 줄었다. LTO는 이번 조합에서는 역효과였고, module化는 그 기능을 안 쓸 때만 이득이었다. initramfs도 애플릿을 실제 쓰는 것만 남기면 42% 더 줄어든다. (5)편에서는 Page Cache/Swap/Reclaim/Watermark/MGLRU 같은 커널 메모리 관리 옵션을 검토하고, (6)편에서 지금까지의 모든 조합을 한 자리에 모아 최종 비교표로 정리하며 시리즈를 마무리한다.