BusyBox 종합 가이드

임베디드 리눅스의 핵심 유틸리티 모음인 BusyBox를 종합적으로 다룹니다. 멀티콜 바이너리(Multicall Binary) 아키텍처, 애플릿(Applet) 디스패치 메커니즘, 심볼릭 링크(Symbolic Link) 시스템, menuconfig 설정, init 시스템, initramfs 활용, 크기 최적화까지 BusyBox의 모든 것을 설명합니다.

전제 조건: LFS (크로스 컴파일)부팅 과정, 개발 환경 설정 문서를 먼저 읽으면 BusyBox가 리눅스 시스템에서 어떤 역할을 하는지 더 잘 이해할 수 있습니다.
일상 비유: BusyBox는 스위스 아미 나이프와 같습니다. 칼, 가위, 드라이버, 병따개가 하나의 도구에 들어있듯이, ls, cp, sh, mount, init 등 수백 개의 유닉스 명령어가 하나의 실행 파일에 들어있습니다. 크기는 1MB 미만이면서 전체 리눅스 시스템을 구동할 수 있습니다.

핵심 요약

  • 멀티콜 바이너리 — 하나의 실행 파일(/bin/busybox)에 수백 개의 유닉스 유틸리티가 포함되어 있으며, argv[0](호출된 이름)으로 어떤 명령어를 실행할지 결정합니다.
  • 심볼릭 링크/bin/ls, /bin/sh 등이 모두 /bin/busybox로의 심볼릭 링크입니다. 링크 이름이 곧 실행할 애플릿을 결정합니다.
  • 애플릿(Applet) — BusyBox에 포함된 각 유틸리티를 애플릿이라 합니다. menuconfig로 필요한 애플릿만 선택하여 빌드 크기를 조절합니다.
  • init 시스템 — BusyBox는 자체 init을 제공하여 /etc/inittab 기반으로 임베디드 시스템을 부팅합니다.
  • 크기 — 기본 설정(defconfig) 약 1MB, 최소 설정 약 200KB. GNU coreutils(15MB+) 대비 1/10 이하입니다.

핵심 용어 정리

용어설명
멀티콜 바이너리(Multicall Binary)하나의 ELF 실행 파일이 호출 이름에 따라 다른 기능을 수행하는 구조
애플릿(Applet)BusyBox에 포함된 개별 유틸리티. 각 애플릿은 xxx_main() 함수로 구현됨
argv[0] 디스패치프로그램이 실행될 때 argv[0](프로그램 이름)을 검사하여 해당 애플릿을 호출하는 메커니즘
심볼릭 링크(Symlink)/bin/ls → /bin/busybox 처럼 각 명령어 이름이 busybox 바이너리를 가리키는 링크
menuconfigncurses 기반 설정 인터페이스. 애플릿 선택, 정적/동적 링크, 기능 플래그 등을 설정
ashBusyBox의 기본 셸. Almquist Shell 기반으로 POSIX 호환이며 bash보다 가볍지만 일부 bash 확장을 지원하지 않음
mdevBusyBox의 경량 디바이스 관리자. udev 대안으로 임베디드 환경에서 /dev 노드를 동적 생성
inittabBusyBox init이 읽는 설정 파일. 부팅 시 실행할 프로세스와 런레벨 동작을 정의

단계별 이해

BusyBox를 처음 접하는 개발자를 위한 5단계 가이드입니다:

1단계: BusyBox 다운로드 및 빌드

# 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 defconfig
make -j$(nproc)

# 설치 (기본: _install 디렉토리)
make install

2단계: 설치 결과 확인

# 심볼릭 링크 확인
ls -la _install/bin/
# lrwxrwxrwx 1 user user    7 ... ash -> busybox
# lrwxrwxrwx 1 user user    7 ... cat -> busybox
# lrwxrwxrwx 1 user user    7 ... ls  -> busybox
# -rwxr-xr-x 1 user user 1.1M ... busybox

# BusyBox에 포함된 애플릿 목록 확인
./busybox --list | head -20

3단계: menuconfig로 커스터마이징

# ncurses 기반 설정 인터페이스
make menuconfig

# 주요 설정 항목:
# Settings → Build static binary (no shared libs)  ← 정적 링크
# Settings → Cross compiler prefix                 ← 크로스 컴파일
# Shells → ash                                     ← 셸 선택
# Init Utilities → init                            ← init 시스템

4단계: initramfs에서 BusyBox 사용

# 최소 initramfs 디렉토리 구성
mkdir -p initramfs/{bin,sbin,etc,proc,sys,dev,tmp,usr/{bin,sbin}}

# BusyBox 정적 빌드 복사
cp busybox initramfs/bin/
cd initramfs/bin && ./busybox --install -s .
cd ../sbin && ../bin/busybox --install -s .

# init 스크립트 작성
cat > initramfs/init <<'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
echo "BusyBox initramfs booted!"
exec /bin/sh
EOF
chmod +x initramfs/init

5단계: QEMU로 테스트

# cpio 아카이브 생성
cd initramfs
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
cd ..

# QEMU 부팅 (x86_64)
qemu-system-x86_64 -kernel bzImage \
  -initrd initramfs.cpio.gz \
  -append "console=ttyS0" -nographic
QEMU 테스트 상세 가이드: QEMU 에뮬레이션 활용 문서에서 x86_64, ARM64, RISC-V 등 다양한 아키텍처에서의 BusyBox initramfs 테스트 방법을 다룹니다.

개요 및 역사

BusyBox 멀티콜 바이너리 (~1MB) 사용 환경 임베디드 시스템 initramfs 컨테이너 (Alpine 등) 복구 환경 (Recovery) 대체 대상 GNU coreutils util-linux bash net-tools module-init-tools 빌드 시스템 통합 OpenWrt Buildroot Yocto Android Recovery 사용 환경 대체 대상 빌드 시스템 BusyBox 핵심
그림: BusyBox 에코시스템 개요 — 사용 환경, 대체 대상, 빌드 시스템 통합

BusyBox란

BusyBox는 하나의 실행 파일에 수백 개의 유닉스(Unix) 유틸리티를 통합한 소프트웨어입니다. "임베디드 리눅스의 스위스 아미 나이프(The Swiss Army Knife of Embedded Linux)"라는 공식 슬로건처럼, ls, cp, sh, mount, init, ifconfig, wget 등 일반적인 리눅스 시스템에 필요한 거의 모든 기본 명령어를 단일 바이너리로 제공합니다.

멀티콜 바이너리 구조 덕분에 GNU coreutils, util-linux, bash 등을 개별 설치하는 것보다 디스크/플래시(Flash) 공간과 메모리를 극적으로 절약합니다. 전체 루트 파일시스템을 1MB 미만으로 구성할 수 있어 임베디드 시스템, initramfs, 컨테이너(Container) 베이스 이미지, 복구(Recovery) 환경에 광범위하게 사용됩니다.

주요 사용 환경:

환경BusyBox 역할대표 사례
임베디드 시스템전체 유저 공간 유틸리티 제공. 4~16MB 플래시에 리눅스 시스템 구동라우터(OpenWrt), IP 카메라, 산업용 게이트웨이, IoT 장비
initramfs커널 부팅 초기 루트 파일시스템. 하드웨어 초기화, 루트 마운트, switch_root리눅스 배포판 설치 디스크, 커널 개발/디버깅 환경
컨테이너최소 베이스 이미지(~1.2MB). 셸과 기본 유틸리티 제공Alpine Linux (Docker 사실상 표준), 사이드카 컨테이너
복구 환경시스템 복구 시 최소 유틸리티 제공. 디스크 마운트, 파일 복사, 네트워크 설정Android Recovery, 데비안 설치 디스크, 시스템 복구 USB
네트워크 부팅PXE/TFTP 부팅 시 initramfs 기반 경량 OS 제공디스크리스(Diskless) 서버, 네트워크 진단 도구

역사와 발전

연도이벤트
1996Bruce Perens가 데비안(Debian) 설치 디스크를 위해 BusyBox 최초 개발. 플로피 디스크 1장(1.44MB)에 들어가는 리눅스 시스템 목표
1999Erik Andersen이 메인테이너를 이어받아 대규모 리팩토링. uClibc 프로젝트와 함께 임베디드 리눅스 생태계 확립
2006Denys Vlasenko가 메인테이너 역할 시작. 현재까지 활발히 유지보수
2007~2011Software Freedom Conservancy가 GPL 준수를 위한 소송 다수 진행 (Verizon, Samsung, Westinghouse 등)
2015+Docker Alpine 이미지 기반으로 컨테이너 환경에서 BusyBox 활용 급증
현재1.36.x 안정 릴리스. 약 400개 애플릿 지원. OpenWrt, Buildroot, Android Recovery 등에서 기본 채택
릴리스 정보: BusyBox의 전체 릴리스 기록과 변경 사항은 공식 뉴스 페이지에서 확인할 수 있습니다. 소스 코드의 상세 변경 이력은 Git 로그를 참고하세요.

주요 버전별 변경사항:

버전릴리스주요 변경
1.002004-10첫 안정 릴리스. Erik Andersen의 대규모 리팩토링 완료
1.102008-01ash 셸 대폭 개선, IPv6 지원, SELinux 지원 추가
1.202012-08Unicode(유니코드) 지원, ntpd 애플릿 추가, 빌드 시스템 개선
1.252016-08ssl_client 추가 (HTTPS wget 지원), tc 네트워크 트래픽 제어
1.302019-01bc 임의 정밀도 계산기, 64비트 time_t, CONFIG_FEATURE_PREFER_APPLETS 개선
1.352022-01xxd 애플릿 추가, ip 명령 개선, 빌드 경고 수정
1.362023-01ash HISTFILE 지원, tsort 추가, tree 추가, musl 빌드 개선
1.372024-11최신 안정 릴리스. seedrng, nproc 추가, 다수 버그 수정

라이선스

BusyBox는 GPLv2 라이선스입니다. GPL 소송 역사에서 중요한 위치를 차지하는데, Software Freedom Conservancy(SFC)가 BusyBox를 포함한 제품의 GPL 위반을 다수 소송하여 임베디드 업계의 오픈소스(Open Source) 컴플라이언스(Compliance) 인식을 크게 높였습니다.

GPL 준수 필수: BusyBox를 제품에 포함할 경우 소스 코드를 공개해야 합니다. 정적 링크 시 결합된 전체 프로그램이 GPL 적용 대상이 됩니다. 빌드 시스템 사용 시 라이선스 매니페스트(Manifest) 자동 생성 기능을 활용하세요.

대안 프로젝트 비교

프로젝트라이선스주요 목적애플릿 수특징
BusyBoxGPLv2범용 임베디드 유틸리티~400가장 성숙하고 검증된 프로젝트. OpenWrt/Buildroot 기본 채택
toybox0BSDBSD 라이선스 대안~230Android에서 BusyBox 대체. Rob Landley(전 BusyBox 메인테이너) 주도
klibcBSD/GPLinitramfs 전용~30커널 부팅 초기에 필요한 최소 유틸리티만 제공
dashBSDPOSIX 셸 전용1 (sh)데비안/우분투 기본 /bin/sh. 셸만 필요한 경우 최적
프로젝트 비교: toybox 개발자 Rob Landley가 작성한 toybox FAQ에서 BusyBox와 toybox의 철학적/기술적 차이를 상세히 설명합니다. Android는 라이선스 이유로 2012년부터 toybox를 채택했습니다.

멀티콜 바이너리 아키텍처

멀티콜 바이너리 개념

일반적인 리눅스 시스템에서 ls, cp, cat 등은 각각 독립 실행 파일입니다. BusyBox는 이 모든 명령어를 하나의 ELF 바이너리에 통합합니다. 실행 시 argv[0](프로세스 이름)을 검사하여 어떤 애플릿을 실행할지 결정합니다:

이 구조의 핵심 이점은 코드 공유입니다. 문자열 처리, 파일 I/O, 에러 핸들링(Error Handling) 등의 공통 함수를 모든 애플릿이 공유하므로, 개별 바이너리를 합산한 크기보다 훨씬 작습니다.

/* BusyBox 멀티콜 디스패치 핵심 흐름 (단순화) */
int main(int argc, char **argv)
{
    /* argv[0]에서 경로 제거: "/bin/ls" → "ls" */
    const char *applet_name = bb_basename(argv[0]);

    /* 정렬된 애플릿 테이블에서 이진 탐색 */
    int idx = find_applet_by_name(applet_name);
    if (idx >= 0)
        return applet_main[idx](argc, argv);  /* ls_main() 호출 */

    /* "busybox" 자체로 실행된 경우 */
    if (strcmp(applet_name, "busybox") == 0)
        return busybox_main(argc, argv);

    error_msg_and_die("applet not found: %s", applet_name);
}

BusyBox의 크기 효율성은 libbb(BusyBox 내부 라이브러리)의 코드 공유에서 비롯됩니다. 모든 애플릿이 공유하는 핵심 함수들은 libbb/ 디렉토리에 있으며, 개별 바이너리에서는 각각 중복 구현해야 할 기능을 한 번만 구현합니다:

libbb 모듈제공 함수절약 효과
xfuncs.cxmalloc(), xopen(), xread() — 에러 시 자동 종료하는 래퍼모든 애플릿에서 에러 처리 코드 제거
messages.c공통 에러/경고 메시지 문자열 상수중복 문자열 리터럴 제거 (~2KB 절약)
getopt32.cgetopt32() — 옵션 파싱 통합 APIgetopt_long() 대비 훨씬 작은 옵션 파서
copyfd.cbb_copyfd_eof(), bb_copyfd_size()파일 복사 로직 공유 (cp, cat, dd 등)
recursive_action.crecursive_action() — 재귀 디렉토리 순회find, rm -r, chmod -R 등이 공유
read.cxmalloc_fgets(), bb_get_chunk_from_file()라인 단위 읽기 통합
perror_msg.cbb_perror_msg(), bb_error_msg_and_die()에러 출력 형식 통일
appletlib.c애플릿 디스패치, SUID 처리, 시그널 초기화멀티콜 바이너리 프레임워크 핵심
/* libbb/xfuncs.c — 에러 처리 래퍼 예시 */
void* FAST_FUNC xmalloc(size_t size)
{
    void *ptr = malloc(size);
    if (ptr == NULL && size != 0)
        bb_die_memory_exhausted();
    return ptr;
}

/* 모든 애플릿에서 malloc 대신 xmalloc 사용:
 * - NULL 체크 코드가 각 애플릿에서 제거됨
 * - 일관된 에러 메시지 출력
 * - 바이너리 크기 절감 */

이 설계 덕분에 개별 애플릿의 xxx_main() 함수는 핵심 로직에만 집중하며, 보일러플레이트(Boilerplate) 코드 없이 매우 짧게 구현됩니다. 예를 들어 BusyBox의 yes 애플릿은 10줄 미만, true/false는 각 1줄입니다.

애플릿 구현 분석 (cat_main 예시)

BusyBox 애플릿이 어떻게 libbb의 공유 함수를 활용하여 극도로 간결하게 구현되는지, cat 애플릿의 실제 소스 코드를 분석합니다 (소스: coreutils/cat.c):

/* coreutils/cat.c — BusyBox cat 구현 (핵심부 단순화) */
//applet:IF_CAT(APPLET(cat, BB_DIR_BIN, BB_SUID_DROP))
//usage:#define cat_trivial_usage
//usage:       "[-nbvteA] [FILE]..."

int cat_main(int argc UNUSED_PARAM, char **argv)
{
    struct stat st;
    unsigned flags;

    /* getopt32(): libbb 공유 옵션 파서
     * 한 줄로 -n, -b, -v, -t, -e, -A 옵션을 모두 파싱 */
    flags = getopt32(argv, "nbvteA");

    argv += optind;
    if (!*argv) *--argv = (char*)"-";  /* 인자 없으면 stdin */

    do {
        /* xopen_stdin(): libbb 공유 함수
         * "-"이면 stdin, 아니면 open() + 에러 시 자동 종료 */
        int fd = open_or_warn_stdin(*argv);
        if (fd < 0) { argv++; continue; }

        for (;;) {
            /* bb_copyfd_eof(): libbb 핵심 공유 함수
             * fd → stdout 전체 복사. 모든 에러 처리 내장 */
            if (bb_copyfd_eof(fd, STDOUT_FILENO) == -1)
                break;
        }
        close(fd);
    } while (*++argv);

    return EXIT_SUCCESS;
}

이 구현에서 볼 수 있는 BusyBox 설계 패턴:

BusyBox 메모리 레이아웃: 개별 바이너리 vs 멀티콜 바이너리 개별 바이너리 (GNU coreutils 방식) /bin/ls ls 로직 .text 에러 처리 (중복) 문자열 처리 (중복) 파일 I/O (중복) .rodata (중복) .data / .bss ~120 KB /bin/cat cat 로직 .text 에러 처리 (중복) 문자열 처리 (중복) 파일 I/O (중복) .rodata (중복) .data / .bss ~50 KB /bin/cp ... cp 로직 .text 에러 처리 (중복) 문자열 처리 (중복) 파일 I/O (중복) .rodata (중복) .data / .bss ~80 KB 합계: ~15 MB+ (300개 개별 바이너리) vs BusyBox 멀티콜 바이너리 /bin/busybox (단일 ELF) libbb 공유 코드 (.text) — 1회만 존재 xmalloc, getopt32, bb_error_msg, copyfd, recursive_action ... ls_main() 고유 로직만 cat_main() 고유 로직만 cp_main() ... 고유 로직만 공유 .rodata — 에러 메시지, 옵션 문자열 (1회만 존재) 공유 .data / .bss — 전역 상태, 애플릿 테이블 applet_names[] + applet_main[] 디스패치 테이블 합계: ~1 MB (300개 애플릿 포함) 공통 코드 공유로 10~15배 크기 절감: 개별 바이너리의 중복 코드가 libbb에서 1회만 링크됨
BusyBox 메모리 레이아웃: 개별 바이너리의 중복 코드(에러 처리, 문자열, I/O)가 libbb에서 1회만 존재하여 극적인 크기 절감 달성

애플릿 디스패치 메커니즘

BusyBox의 애플릿 디스패치는 빌드 시점에 생성되는 정렬된 테이블과 런타임 이진 탐색으로 구현됩니다. 빌드 시 include/applets.h의 매크로가 정렬된 애플릿 이름 테이블과 함수 포인터 배열을 생성합니다.

단계함수/구조체설명
1main()프로그램 진입점. argv[0] 추출
2lbb_prepare()시그널 핸들러, locale 등 초기 설정
3bb_basename()argv[0]에서 경로 제거 ("/bin/ls""ls")
4find_applet_by_name()정렬된 applet_names[]에서 이진 탐색. O(log n) 복잡도
5applet_main[idx]()인덱스로 함수 포인터 배열 접근 → ls_main() 등 호출
BusyBox 애플릿 디스패치 흐름 사용자 실행 $ /bin/ls -la 커널 심볼릭 링크 해석 /bin/ls → /bin/busybox busybox main() argv[0] = "/bin/ls" bb_basename → "ls" find_applet_by_name("ls") applet_names[] 이진 탐색 [ "ash", "cat", "cp", "init", "ls", "mount", "sh", ... ] → idx = 찾은 인덱스 applet_main[idx](argc, argv) → ls_main(argc, argv) 호출 ls 실행 결과 출력 애플릿 테이블 [0] "ash" → ash_main [1] "cat" → cat_main [2] "cp" → cp_main [3] "init" → init_main [4] "ls" → ls_main [5] "mount"→ mount_main [6] "sh" → ash_main [7] "wget" → wget_main ...
BusyBox 애플릿 디스패치 흐름: 심볼릭 링크를 통한 argv[0] 기반 디스패치

BusyBox 설치 시 make install은 활성화된 모든 애플릿에 대해 심볼릭 링크를 생성합니다. 링크 방식은 세 가지가 있습니다:

/bin/ ash → busybox cat → busybox cp → busybox ls → busybox mount → busybox sh → busybox vi → busybox ... 더 많은 링크 /bin/busybox ELF 실행 파일 (~1 MB) -rwxr-xr-x 1 root root /sbin/ init → ../bin/busybox halt → ../bin/busybox mdev → ../bin/busybox switch_root → ../bin/busybox ifconfig → ../bin/busybox ... 더 많은 링크 /usr/bin/ awk, diff, find, xargs → ../../bin/busybox /usr/sbin/ crond, ntpd, httpd → ../../bin/busybox 1개 실행 파일 → ~300개 심볼릭 링크 argv[0]으로 애플릿 이름 판별 후 해당 함수 실행
BusyBox 심볼릭 링크 파일시스템 구조 — 모든 링크가 단일 바이너리를 가리킴
방식명령어장점단점
심볼릭 링크busybox --install -s표준적, 디버깅 용이 (ls -la로 확인 가능)각 링크가 inode 사용
하드 링크busybox --installinode 절약, 원본 삭제 시에도 동작파일시스템 경계 불가, 디버깅 어려움
직접 호출busybox ls -la링크 불필요사용 불편, 스크립트 호환성 없음
# 심볼릭 링크 설치 예시
$ ls -la /bin/
lrwxrwxrwx  1 root root       7 Jan  1 00:00 ash -> busybox
lrwxrwxrwx  1 root root       7 Jan  1 00:00 cat -> busybox
lrwxrwxrwx  1 root root       7 Jan  1 00:00 ls  -> busybox
lrwxrwxrwx  1 root root       7 Jan  1 00:00 sh  -> busybox
-rwxr-xr-x  1 root root 1089568 Jan  1 00:00 busybox

# 직접 호출 vs 심볼릭 링크 호출
$ busybox ls /tmp        # 직접 호출: busybox 뒤에 애플릿 이름
$ ls /tmp                # 심볼릭 링크: /bin/ls → /bin/busybox

소스 코드 구조

BusyBox 소스 트리는 애플릿 카테고리별 디렉토리로 구성됩니다:

디렉토리설명주요 파일
applets/진입점, 애플릿 테이블 생성applets.c, usage.c
archival/압축/아카이브 (tar, gzip, bzip2)tar.c, gzip.c
coreutils/기본 유틸리티 (ls, cp, cat, echo)ls.c, cp.c
editors/텍스트 편집 (vi, sed, awk)vi.c, sed.c
init/init 시스템, halt, rebootinit.c, halt.c
libbb/공용 라이브러리 (모든 애플릿이 공유)xfuncs.c, messages.c
networking/네트워크 (ifconfig, wget, httpd)wget.c, httpd.c
shell/셸 (ash, hush)ash.c, hush.c
sysklogd/로깅 (syslogd, klogd)syslogd.c
util-linux/시스템 유틸리티 (mount, fdisk, dmesg)mount.c, switch_root.c
include/헤더 파일, 애플릿 매크로 정의applets.h, libbb.h
BusyBox 소스 구조 및 빌드 흐름 소스 디렉토리 구조 busybox-1.36.1/ ├── applets/ 진입점 ├── coreutils/ ls, cp, cat ... ├── shell/ ash, hush ├── init/ init, halt ├── networking/ wget, httpd ├── util-linux/ mount, fdisk ├── editors/ vi, sed, awk ├── archival/ tar, gzip ├── sysklogd/ syslogd ├── libbb/ 공용 라이브러리 ├── include/ 헤더 파일 │ ├── applets.h 애플릿 매크로 │ └── libbb.h 공용 API ├── configs/ defconfig 파일 ├── scripts/ 빌드 스크립트 ├── Makefile 최상위 Makefile ├── Config.in Kconfig 정의 └── _install/ 설치 출력 빌드 파이프라인 make defconfig 기본 .config 생성 make menuconfig 애플릿 선택, 옵션 조정 .config make -j$(nproc) 각 애플릿 .c → .o 컴파일 libbb + 선택된 애플릿 링크 busybox (ELF) make install _install/{bin,sbin,usr} 생성 심볼릭 링크: ls→busybox ... 결과물 크기 비교 defconfig (정적) ~1.1 MB 최소 설정 (정적) ~200 KB defconfig (동적) ~700 KB GNU coreutils ~15 MB+ (개별 바이너리 합산) 크로스 컴파일 변수 CROSS_COMPILE=aarch64-linux-gnu- CROSS_COMPILE=riscv64-linux-gnu- CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm / ARCH=arm64 / ARCH=riscv
BusyBox 소스 디렉토리 구조와 빌드 파이프라인

빌드와 설정

빌드 환경 준비

BusyBox 빌드에 필요한 호스트 패키지:

# Ubuntu/Debian
sudo apt-get install -y build-essential gcc make libncurses-dev \
  bzip2 wget git bc

# 크로스 컴파일용 (ARM64)
sudo apt-get install -y gcc-aarch64-linux-gnu

# 크로스 컴파일용 (RISC-V)
sudo apt-get install -y gcc-riscv64-linux-gnu

# 정적 링크용 (musl)
sudo apt-get install -y musl-tools

BusyBox는 리눅스 커널과 동일한 Kconfig 시스템을 사용합니다. make menuconfig로 ncurses 기반 설정 인터페이스에서 애플릿과 옵션을 선택합니다. 전체 설정 옵션 목록은 소스 트리의 Config.in 파일과 공식 매뉴얼에서 확인할 수 있습니다.

주요 설정 카테고리:

카테고리주요 옵션설명
SettingsCONFIG_STATIC=y정적 링크. initramfs/임베디드에 필수
CONFIG_CROSS_COMPILER_PREFIX크로스 컴파일러 접두사 설정
CONFIG_FEATURE_PREFER_APPLETS=y외부 명령 대신 내장 애플릿 우선 사용. 크기 절약
CONFIG_FEATURE_SH_STANDALONE=y셸이 외부 프로그램 대신 busybox 애플릿을 직접 호출
ShellsCONFIG_ASH=yAlmquist Shell. POSIX 호환 경량 셸
CONFIG_HUSH=yHush Shell. NOMMU 환경용 셸
Init UtilitiesCONFIG_INIT=yPID 1 init 프로세스
CONFIG_FEATURE_INIT_SCTTY=yinit이 제어 터미널을 설정
Linux SystemCONFIG_MDEV=y경량 디바이스 관리자
CONFIG_SWITCH_ROOT=yinitramfs → 실제 루트 전환
NetworkingCONFIG_UDHCPC=yDHCP 클라이언트
CONFIG_HTTPD=y경량 HTTP 서버
# menuconfig 설정 흐름
make defconfig          # 기본 설정 생성
make menuconfig         # 대화형 설정 수정

# .config에서 직접 수정도 가능
sed -i 's/# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
make oldconfig          # 의존성 자동 해결
BusyBox Kconfig 빌드 설정 흐름 설정 소스 configs/ Config.in (각 디렉토리) include/applets.src.h 설정 도구 make defconfig make menuconfig make allnoconfig make oldconfig .config CONFIG_xxx=y/n/m 헤더 생성 include/autoconf.h #define ENABLE_LS 1 #define ENABLE_CAT 1 #define ENABLE_FTP 0 컴파일 조건부 포함 #if ENABLE_LS ls.o ✓ include/applets.h (자동 생성) IF_LS(APPLET(ls, BB_DIR_BIN, BB_SUID_DROP)) IF_CAT(APPLET(cat, BB_DIR_BIN, BB_SUID_DROP)) → 정렬된 applet_names[] + applet_main[] 배열 생성 busybox (최종 ELF 바이너리)
BusyBox Kconfig 빌드 설정 흐름: Config.in → .config → autoconf.h → 조건부 컴파일 → 애플릿 테이블 → 최종 바이너리

defconfig와 최소 설정

BusyBox는 두 가지 기본 설정 프리셋을 제공합니다:

설정명령어애플릿 수결과 크기용도
defconfigmake defconfig~300~1.1 MB (정적)범용 임베디드 시스템
allnoconfigmake allnoconfig0N/A최소 설정 시작점 (수동 선택)
# 최소 initramfs용 설정 예시 (allnoconfig + 수동 선택)
make allnoconfig

# 필수 애플릿만 활성화
cat >> .config <<'EOF'
CONFIG_ASH=y
CONFIG_INIT=y
CONFIG_MOUNT=y
CONFIG_UMOUNT=y
CONFIG_MKDIR=y
CONFIG_MKNOD=y
CONFIG_CAT=y
CONFIG_ECHO=y
CONFIG_LS=y
CONFIG_SWITCH_ROOT=y
CONFIG_STATIC=y
EOF

make oldconfig    # 의존성 해결
make -j$(nproc)

정적 링크 vs 동적 링크

방식설정크기장점단점
정적 링크CONFIG_STATIC=y~1.1 MB독립 실행, initramfs에 이상적, 라이브러리 의존성 없음더 큰 바이너리, 보안 패치 시 재빌드 필요
동적 링크 (glibc)(기본값)~700 KB + libc공유 라이브러리로 전체 시스템 크기 절약glibc 의존, initramfs에 부적합
정적 링크 (musl)CC=musl-gcc CONFIG_STATIC=y~800 KBglibc보다 작은 정적 바이너리, 라이선스 깔끔 (MIT)일부 glibc 확장 미지원
# musl로 정적 빌드 (가장 작은 정적 바이너리)
make defconfig
sed -i 's/# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
make CC=musl-gcc -j$(nproc)
strip busybox
ls -lh busybox    # ~800KB

크로스 컴파일

임베디드 타겟용 BusyBox 크로스 컴파일:

# ARM64 (aarch64) 크로스 컴파일
make defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
     CONFIG_STATIC=y -j$(nproc)

# RISC-V 64bit 크로스 컴파일
make defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- \
     CONFIG_STATIC=y -j$(nproc)

# ARM 32bit (hard float) 크로스 컴파일
make defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
     CONFIG_STATIC=y -j$(nproc)

# 결과 확인
file busybox
# busybox: ELF 64-bit LSB executable, ARM aarch64, statically linked, ...
빌드 시스템 통합: Buildroot/OpenWrt/Yocto를 사용하면 BusyBox 크로스 컴파일, 설정, 루트 파일시스템 생성이 자동화됩니다. Buildroot에서는 BR2_PACKAGE_BUSYBOX=yBR2_PACKAGE_BUSYBOX_CONFIG로 설정합니다.

핵심 애플릿 카테고리

셸 (ash/hush)

BusyBox는 두 가지 셸을 제공합니다:

크기특징환경
ash~60 KBPOSIX 호환, 대부분의 sh 스크립트 실행 가능. bash 일부 확장 지원 ($(()), local)MMU 환경 (일반 임베디드)
hush~40 KB최소 POSIX 셸. 히어독(heredoc), 파이프 지원NOMMU 환경 (Cortex-M, uClinux)

ash vs bash 주요 차이점:

기능bashash (BusyBox)
배열(Array)arr=(a b c); echo ${arr[1]}미지원. set -- a b c; echo $2 로 대체
확장 테스트[[ $x =~ regex ]]미지원. echo "$x" | grep -E 'regex'
프로세스 치환diff <(cmd1) <(cmd2)미지원. 임시 파일 사용
연관 배열declare -A map미지원
문자열 조작${var^^} (대문자 변환)미지원. echo "$var" | tr a-z A-Z
산술 확장$(( ))지원
함수/locallocal var=val지원
here string<<< "text"지원 (CONFIG_ASH_BASH_COMPAT)

파일 유틸리티

BusyBox의 coreutils 애플릿은 GNU coreutils 대비 옵션이 축소되어 있습니다. 자주 사용하는 옵션은 대부분 지원하지만, 덜 사용되는 옵션은 빌드 크기를 줄이기 위해 생략됩니다:

애플릿GNU 옵션 수BusyBox 옵션 수누락된 주요 옵션
ls~60~25--color=auto (별도 CONFIG), --context
cp~30~15--reflink, --sparse
find~50~20-printf (별도 CONFIG), -regextype
sed~15~8-E/--regexp-extended (별도 CONFIG)
awkgawk 전체기본 POSIX대부분의 gawk 확장 (FPAT, @include 등)

네트워크

BusyBox는 임베디드 시스템에 필요한 기본 네트워크 도구를 제공합니다:

애플릿설명주요 옵션
ifconfig네트워크 인터페이스 설정ip 주소 설정, up/down
ipiproute2 경량 구현addr, link, route 서브커맨드
wgetHTTP/HTTPS 다운로더-O, -q, SSL 지원 (CONFIG 의존)
httpd경량 HTTP 서버CGI 지원, 디렉토리 리스팅
udhcpcDHCP 클라이언트스크립트 기반 설정 적용
udhcpdDHCP 서버임베디드 라우터용 경량 DHCP
telnetdTelnet 서버디버깅용 원격 접속
ntpdNTP 클라이언트/서버시간 동기화

init 시스템 애플릿

BusyBox init 관련 애플릿:

애플릿설명
initPID 1 프로세스. /etc/inittab을 파싱하여 시스템 초기화 수행
halt시스템 정지. init에 SIGUSR1 전송
poweroff시스템 전원 끄기. init에 SIGUSR2 전송
reboot시스템 재부팅. init에 SIGTERM 전송
getty터미널 로그인 프롬프트 제공

모듈 유틸리티

커널 모듈 관리용 애플릿:

애플릿설명
insmod커널 모듈 삽입 (.ko 파일 직접 로드)
rmmod커널 모듈 제거
modprobe의존성을 해결하며 모듈 로드/언로드
depmod모듈 의존성 파일 (modules.dep) 생성
lsmod로드된 커널 모듈 목록 출력
modinfo모듈 정보 출력

시스템 유틸리티

애플릿설명비고
mount파일시스템 마운트NFS, CIFS 등 네트워크 FS도 지원 (CONFIG 의존)
umount파일시스템 언마운트
mdev경량 디바이스 관리자udev 대안. hotplug 이벤트 처리
switch_rootinitramfs → 실제 루트 전환initramfs의 init 스크립트에서 사용
syslogd시스템 로그 데몬원격 로깅 지원
klogd커널 로그 데몬/proc/kmsg 읽어서 syslogd에 전달
dmesg커널 링 버퍼 출력
free메모리 사용량 출력

BusyBox init 시스템

inittab 구조

BusyBox init은 /etc/inittab 파일을 읽어 부팅 프로세스를 제어합니다. 형식은 sysvinit과 유사하지만 단순화되어 있습니다:

# /etc/inittab 형식:
# <id>:<runlevels>:<action>:<process>
#
# id         - 터미널 식별자 (tty 번호 등, BusyBox에서는 무시 가능)
# runlevels  - BusyBox init에서는 무시됨 (런레벨 미지원)
# action     - 실행 방식 (sysinit, wait, once, respawn, askfirst 등)
# process    - 실행할 명령어

# 시스템 초기화 (부팅 시 1회 실행, 완료까지 대기)
::sysinit:/etc/init.d/rcS

# 초기화 후 1회 실행
::wait:/bin/echo "System initialization complete"

# 콘솔 (사용자가 Enter 키를 누르면 셸 시작)
::askfirst:-/bin/sh

# 시리얼 콘솔 (재시작 반복)
ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100

# 시스템 종료 시 실행
::shutdown:/etc/init.d/rcK
::shutdown:/bin/umount -a -r

# Ctrl-Alt-Del 처리
::ctrlaltdel:/sbin/reboot

inittab 액션 유형:

액션설명사용 시점
sysinit시스템 초기화 시 실행. 완료까지 대기 후 다음 진행rcS 스크립트 실행
waitsysinit 완료 후 실행. 완료까지 대기추가 초기화 작업
once1회 실행. 완료를 기다리지 않음백그라운드 서비스 시작
respawn프로세스 종료 시 자동 재시작getty, 데몬 프로세스
askfirstrespawn과 유사하나 "Press Enter to activate" 메시지 표시 후 대기디버깅 콘솔
shutdown시스템 종료 시 실행정리(cleanup) 작업
restartSIGQUIT 수신 시 실행init 재시작
ctrlaltdelCtrl+Alt+Del 키 입력 시 실행재부팅

/etc/inittab 파일이 존재하지 않으면 BusyBox init은 하드코딩된 기본값을 사용합니다.

inittab 없을 때 기본 동작

BusyBox init은 /etc/inittab을 찾지 못하면 init/init.c에 하드코딩된 기본 설정을 사용합니다. 이는 최소 initramfs 환경에서 별도의 inittab 없이도 시스템을 부팅할 수 있게 합니다:

# BusyBox init 하드코딩 기본값 (init/init.c에서 발췌)
# inittab 파일이 없을 때 자동 적용

::sysinit:/etc/init.d/rcS          # 초기화 스크립트 실행
::askfirst:-/bin/sh                 # 콘솔 장치에서 셸 제공
tty2::askfirst:-/bin/sh             # 가상 터미널 2
tty3::askfirst:-/bin/sh             # 가상 터미널 3
tty4::askfirst:-/bin/sh             # 가상 터미널 4

콘솔 장치는 /dev/console에서 자동으로 결정됩니다. 시리얼 콘솔(Serial Console) 환경(예: ttyS0)에서는 해당 장치에 대해 하나의 askfirst 항목만 생성됩니다. 명령어 앞의 하이픈(-/bin/sh)은 로그인 셸(Login Shell)을 의미하며, argv[0]-sh로 설정되어 /etc/profile~/.profile을 읽습니다.

initramfs 팁: initramfs에서 inittab 없이 부팅할 때는 /etc/init.d/rcS 스크립트만 제공하면 기본 동작으로 충분합니다. 명시적 inittab은 getty 설정, 시리얼 콘솔 속도 지정, 또는 shutdown 처리가 필요할 때 추가합니다.

rcS 스크립트 실행 흐름

/etc/init.d/rcS는 초기화 스크립트를 순서대로 실행하는 마스터 스크립트입니다:

#!/bin/sh
# /etc/init.d/rcS — BusyBox 초기화 마스터 스크립트

# /etc/init.d/ 디렉토리의 S##name 스크립트를 번호 순서대로 실행
for script in /etc/init.d/S[0-9][0-9]*; do
    [ -x "$script" ] || continue
    case "$script" in
        *.sh)  . "$script" ;;   # .sh 확장자는 source로 실행
        *)     "$script" start ;;  # 나머지는 "start" 인자로 실행
    esac
done

일반적인 초기화 스크립트 순서:

스크립트역할
S01sysfs가상 파일시스템 마운트 (/proc, /sys, /dev)
S02hostname호스트명 설정
S05modules커널 모듈 로드
S10mdevmdev 디바이스 관리자 시작
S20network네트워크 인터페이스 설정, udhcpc 시작
S30syslogdsyslogd/klogd 시작
S40httpd웹 서버 시작 (선택)
S99local사용자 정의 초기화

init 내부 동작

BusyBox init의 핵심 코드는 init/init.c 약 900줄로 구성됩니다. init_main() 함수가 PID 1로서 다음 순서로 실행됩니다:

  1. 콘솔(Console) 설정/dev/console을 열고, setsid()로 새 세션 생성, STDIN/STDOUT/STDERR을 콘솔에 연결
  2. 시그널 핸들러(Signal Handler) 등록SIGHUP, SIGQUIT, SIGUSR1/2, SIGTERM, SIGINT를 블록하고 sigtimedwait() 방식으로 처리 (비동기 안전성(Async-Signal Safety) 확보)
  3. 환경 변수 초기화PATH=/sbin:/usr/sbin:/bin:/usr/bin 설정
  4. parse_inittab()/etc/inittab을 파싱하여 init_action 연결 리스트(Linked List) 구성. 파일이 없으면 기본값 사용
  5. run_actions(SYSINIT) — sysinit 타입 액션 실행, waitfor_pid()로 각 프로세스 완료까지 블로킹(Blocking) 대기
  6. run_actions(WAIT) — wait 타입 액션 실행, 완료까지 대기
  7. run_actions(ONCE) — once 타입 액션 실행 (대기하지 않음, 백그라운드로 진행)
  8. 메인 루프(Main Loop) 진입 — respawn/askfirst 프로세스를 관리하는 무한 루프

메인 루프는 다음 4단계를 반복합니다:

  1. run_actions(RESPAWN) — 종료된 respawn 프로세스를 재시작
  2. run_actions(ASKFIRST) — 종료된 askfirst 프로세스를 재시작 (Enter 키 대기 포함)
  3. wpid = waitpid(-1, &status, WNOHANG) — 자식 프로세스(Child Process) 종료 상태 수집 (좀비 리핑(Zombie Reaping))
  4. check_delayed_sigs() — 블록된 시그널 확인 및 처리 → sleep(1) 후 반복

init_action 구조체는 inittab의 각 항목을 메모리에 표현합니다:

/* BusyBox init/init.c — init_action 구조체 */
struct init_action {
    struct init_action *next;       /* 연결 리스트 다음 노드 */
    pid_t pid;                      /* 실행 중인 프로세스 PID (0이면 미실행) */
    uint8_t action_type;            /* SYSINIT, WAIT, ONCE, RESPAWN, ASKFIRST 등 */
    char terminal[CONSOLE_NAME_SIZE]; /* 터미널 장치명 (예: "ttyS0") */
    char command[256];              /* 실행할 명령어 */
};
waitfor_pid() vs waitpid(): waitfor_pid()는 특정 PID가 종료될 때까지 블로킹하는 래퍼(Wrapper) 함수로, SYSINIT과 WAIT 액션에서 사용됩니다. 메인 루프의 waitpid(-1, WNOHANG)는 논블로킹(Non-Blocking)으로 모든 종료된 자식을 수집합니다.
BusyBox init 내부 상태 머신 init_main() 시작 (PID 1) 콘솔/시그널 설정 setsid(), 시그널 블록, PATH 설정 parse_inittab() init_action 연결 리스트 구성 /etc/inittab 파싱 또는 하드코딩 기본값 run_actions(SYSINIT) waitfor_pid() — 완료까지 블로킹 run_actions(WAIT) waitfor_pid() — 완료까지 블로킹 run_actions(ONCE) 대기 없이 실행 후 진행 메인 루프 (무한 반복) run_actions(RESPAWN) 종료된 프로세스 재시작 run_actions(ASKFIRST) Enter 대기 후 셸 시작 waitpid(-1) 좀비 리핑 (논블로킹) check_delayed_sigs() 시그널 확인 → 처리 sleep(1) 순차 실행 (블로킹)
BusyBox init 내부 상태 머신: init_main()부터 메인 루프까지

PID 1 역할과 시그널 처리

PID 1인 BusyBox init은 두 가지 핵심 역할을 수행합니다: (1) 시그널(Signal)을 통한 시스템 상태 전환(종료, 재부팅, 설정 재로드), (2) 고아 프로세스(Orphan Process) 입양(Adoption)과 좀비 리핑(Zombie Reaping).

시그널 매핑

시그널발신 명령어init 동작
SIGHUP/etc/inittab 다시 읽기 (parse_inittab() 재호출)
SIGQUITrestart 액션 실행 (init 자체 재시작)
SIGUSR1haltshutdown 액션 → SIGTERM → SIGKILL → halt()
SIGUSR2poweroffshutdown 액션 → SIGTERM → SIGKILL → poweroff()
SIGTERMrebootshutdown 액션 → SIGTERM → SIGKILL → reboot()
SIGINTCtrl+Alt+Del (커널)ctrlaltdel 액션 실행
SIGCHLD자식 종료waitpid()로 수집, respawn 대상이면 재시작

종료 시퀀스(Shutdown Sequence)

halt, poweroff, reboot 명령은 각각 대응하는 시그널을 PID 1에 전송합니다. BusyBox init은 다음 6단계로 시스템을 정리합니다:

  1. shutdown 액션 실행/etc/init.d/rcK, umount -a -r 등 inittab의 shutdown 항목
  2. SIGTERM 전송 — 모든 프로세스에 종료 요청 (kill(-1, SIGTERM))
  3. 1초 대기 — 프로세스가 정상 종료할 시간 제공
  4. SIGKILL 전송 — 남은 프로세스 강제 종료 (kill(-1, SIGKILL))
  5. sync() 호출 — 파일시스템 버퍼를 디스크에 기록
  6. 시스템 콜 실행reboot(RB_HALT_SYSTEM), reboot(RB_POWER_OFF), 또는 reboot(RB_AUTOBOOT)

좀비 리핑(Zombie Reaping)

리눅스에서 부모 프로세스가 먼저 종료되면 자식은 PID 1에 입양됩니다. PID 1은 waitpid(-1, &status, WNOHANG)로 모든 종료된 자식의 상태를 수집해야 합니다. 이것이 PID 1만의 특수한 의무이며, 수행하지 않으면 좀비 프로세스(Zombie Process)가 프로세스 테이블에 누적됩니다. BusyBox init은 메인 루프에서 이를 자동 처리하며, 수집된 PID가 respawn/askfirst 목록에 있으면 해당 프로세스를 재시작합니다.

SIGHUP: inittab 재로드

SIGHUP 수신 시 parse_inittab()을 다시 호출하여 inittab을 재파싱합니다. 새로 추가된 항목은 다음 루프 반복에서 실행되고, 삭제된 항목은 연결 리스트에서 제거됩니다. 단, 이미 실행 중인 프로세스는 종료하지 않습니다 — 해당 프로세스가 자연스럽게 종료된 후 재시작하지 않는 방식으로 처리됩니다.

BusyBox init 시그널 처리 흐름 시그널 소스 halt → SIGUSR1 poweroff → SIGUSR2 reboot → SIGTERM Ctrl+Alt+Del → SIGINT 자식 종료 → SIGCHLD SIGHUP BusyBox init (PID 1) check_delayed_sigs() 시그널 대기열 확인 sigtimedwait() 비동기 안전 처리 delayed_sigs 비트마스크 동작 경로 종료 시퀀스 1. shutdown 액션 실행 (rcK, umount) 2. kill(-1, SIGTERM) → 모든 프로세스 3. sleep(1) — 정상 종료 대기 4. kill(-1, SIGKILL) → 강제 종료 5. sync() — 디스크 동기화 6. reboot(RB_HALT / RB_POWER_OFF / RB_AUTOBOOT) SIGUSR1→halt SIGUSR2→poweroff SIGTERM→reboot ctrlaltdel 액션 실행 일반적으로 /sbin/reboot 자식 프로세스 수집 waitpid(-1, WNOHANG) → 좀비 리핑 pid가 respawn/askfirst 목록에 있으면 → 재시작 inittab 재로드 parse_inittab() 재호출 → 연결 리스트 갱신 실행 중 프로세스는 유지, 삭제 항목은 자연 종료 후 제거 BusyBox init은 시그널을 직접 처리하지 않고 delayed_sigs 비트마스크에 기록 후, 메인 루프에서 안전하게 처리합니다
BusyBox init 시그널 처리: 명령어에서 시스템 동작까지

실전 inittab 예제

예제 1: 최소 임베디드 시스템

rcS 스크립트 없이 inittab만으로 최소 초기화를 수행합니다. initramfs 디버깅에 적합합니다:

# /etc/inittab — 최소 임베디드 (rcS 없이 직접 초기화)
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -t sysfs sys /sys
::sysinit:/bin/mount -t devtmpfs dev /dev
::sysinit:/bin/echo /sbin/mdev > /proc/sys/kernel/hotplug
::sysinit:/sbin/mdev -s
::askfirst:-/bin/sh
::shutdown:/bin/umount -a -r
::ctrlaltdel:/sbin/reboot

예제 2: 네트워크 어플라이언스

SSH 서버(dropbear)는 once로 1회 시작하고, 웹 서버는 respawn으로 크래시 시 자동 복구합니다:

# /etc/inittab — 네트워크 어플라이언스
::sysinit:/etc/init.d/rcS
::wait:/sbin/ifconfig eth0 192.168.1.1 netmask 255.255.255.0 up

# SSH: 1회 시작 (자체 데몬화)
::once:/usr/sbin/dropbear -R

# 웹 서버: 포그라운드 실행, 종료 시 자동 재시작
::respawn:/usr/sbin/lighttpd -D -f /etc/lighttpd.conf

# 시리얼 콘솔
ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100

::shutdown:/etc/init.d/rcK
::ctrlaltdel:/sbin/reboot

예제 3: 컨테이너 init

컨테이너에서 단일 애플리케이션을 PID 1로 감시합니다. respawn이 크래시 복구를 제공합니다:

# /etc/inittab — 컨테이너 init
::sysinit:/bin/mount -t proc proc /proc
::respawn:/usr/bin/my-app --foreground
::shutdown:/bin/kill -TERM $(cat /var/run/my-app.pid)

예제 4: 개발/디버깅 환경

커널 로그 레벨을 최대로 올리고, 시리얼과 콘솔 양쪽에서 접근 가능한 환경입니다:

# /etc/inittab — 개발/디버깅
::sysinit:/etc/init.d/rcS
::wait:/bin/echo "=== Debug Console Ready ==="
::wait:/bin/dmesg -n 8

# 시리얼 콘솔 (respawn으로 세션 종료 시 재시작)
ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100

# 로컬 콘솔 (askfirst로 Enter 대기)
::askfirst:-/bin/sh

::shutdown:/bin/sync
::ctrlaltdel:/sbin/reboot
respawn 폭주 방지: respawn 프로세스가 즉시 종료되는 경우(예: 실행 파일 누락, 설정 오류), BusyBox init은 5초 간격으로 재시작을 시도합니다. 이는 무한 재시작 폭주(Respawn Storm)를 방지하는 내장 보호 메커니즘입니다. 로그에 "process 'xxx' (pid NNN) exited. Scheduling for restart." 메시지가 반복되면 해당 프로세스의 설정을 점검하세요.

init 비교

항목BusyBox initsysvinitsystemd
크기~20 KB (BusyBox 내장)~500 KB~10 MB+
설정/etc/inittab/etc/inittab + /etc/rc.d//etc/systemd/ (유닛 파일)
런레벨미지원0~6 런레벨타겟(target) 기반
병렬 시작미지원 (순차 실행)미지원지원 (의존성 그래프)
서비스 관리수동 (kill, 스크립트)service 명령systemctl
로깅syslogd (선택)syslogjournald (바이너리 로그)
소켓 활성화미지원미지원지원
cgroup 관리미지원미지원지원
대상 환경임베디드, initramfs레거시 서버데스크톱, 서버

각 init 시스템의 상세한 설명은 systemd 종합 가이드SysVinit 종합 가이드를 참고하세요.

경량 init 대안: BusyBox init 외에도 runits6가 임베디드/컨테이너 환경에서 사용됩니다. runit은 3단계(1:초기화, 2:서비스 감시, 3:종료) 구조로, Void Linux의 기본 init이며 s6는 더 엄격한 프로세스 감시와 의존성 관리를 제공합니다. Alpine Linux는 BusyBox init 대신 OpenRC를 기본 채택합니다.

부팅 흐름

BusyBox init 부팅 흐름 Linux Kernel 부팅 완료, PID 1 실행 /sbin/init BusyBox init (PID 1) /etc/inittab 파싱 액션별 프로세스 목록 구성 sysinit 실행 /etc/init.d/rcS (완료까지 대기) rcS 스크립트 순차 실행 S01sysfs → /proc, /sys 마운트 S02hostname → 호스트명 설정 S05modules → 커널 모듈 로드 S10mdev → 디바이스 노드 생성 S20network → 네트워크 설정 S30syslogd → 로그 데몬 시작 S99local → 사용자 정의 초기화 ↓ 완료 wait 액션 실행 (완료까지 대기) respawn getty -L ttyS0 종료 시 자동 재시작 askfirst -/bin/sh Enter 키 대기 → 셸 종료/재부팅 흐름 halt → SIGUSR1 → init poweroff → SIGUSR2 → init reboot → SIGTERM → init 1. shutdown 액션 실행 (rcK) 2. SIGTERM → 모든 프로세스 3. SIGKILL → 남은 프로세스 → sync → halt/reboot inittab 없을 때 기본 동작 ::sysinit:/etc/init.d/rcS tty1::askfirst:-/bin/sh tty2::askfirst:-/bin/sh (tty2~tty4 반복) (콘솔 장치에 셸 제공)
BusyBox init 부팅 흐름: 커널에서 사용자 공간까지

initramfs와 임베디드 리눅스 활용

initramfs 생성 절차

BusyBox 기반 initramfs는 임베디드 리눅스와 커널 디버깅에서 가장 많이 사용되는 루트 파일시스템 형태입니다. initramfs의 동작 원리는 커널 공식 문서에서 설명합니다:

# 1. 디렉토리 구조 생성
mkdir -p initramfs/{bin,sbin,etc,proc,sys,dev,tmp,mnt,root}
mkdir -p initramfs/usr/{bin,sbin}
mkdir -p initramfs/etc/init.d
mkdir -p initramfs/var/{log,run}

# 2. BusyBox 정적 빌드 복사 및 설치
cp busybox initramfs/bin/
chmod +x initramfs/bin/busybox
cd initramfs
bin/busybox --install -s bin/
bin/busybox --install -s sbin/
bin/busybox --install -s usr/bin/
bin/busybox --install -s usr/sbin/

# 3. 필수 디바이스 노드 생성 (또는 devtmpfs 사용)
sudo mknod -m 622 dev/console c 5 1
sudo mknod -m 666 dev/null    c 1 3
sudo mknod -m 666 dev/zero    c 1 5
sudo mknod -m 444 dev/random  c 1 8
sudo mknod -m 444 dev/urandom c 1 9

# 4. cpio 아카이브 생성
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
cd ..

init 스크립트 패턴

initramfs의 /init 스크립트는 커널이 가장 먼저 실행하는 사용자 공간 프로그램입니다. 용도에 따라 다양한 패턴이 있으며, 모든 패턴은 POSIX sh 호환으로 작성해야 합니다:

패턴 1: 단순 디버깅용 (셸로 진입)

커널 개발과 디버깅 시 가장 자주 사용하는 패턴입니다. 최소한의 가상 파일시스템만 마운트하고 셸 프롬프트를 제공합니다:

#!/bin/sh
# /init — 단순 initramfs init (커널 디버깅용)

mount -t proc     none /proc
mount -t sysfs    none /sys
mount -t devtmpfs none /dev

# 커널 정보 출력
echo "================================================="
echo "  BusyBox initramfs booted successfully!"
echo "  Kernel: $(uname -r)"
echo "  cmdline: $(cat /proc/cmdline)"
echo "================================================="

exec /bin/sh

패턴 2: 실제 루트로 전환 (switch_root)

프로덕션 임베디드 시스템에서 사용하는 패턴입니다. initramfs에서 하드웨어를 초기화한 뒤 실제 루트 파일시스템으로 전환합니다:

#!/bin/sh
# /init — switch_root 패턴 (프로덕션용)

mount -t proc     none /proc
mount -t sysfs    none /sys
mount -t devtmpfs none /dev

# mdev로 디바이스 노드 동적 생성
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

# 커널 파라미터에서 루트 장치 추출
ROOT=$(cat /proc/cmdline | sed 's/.*root=\([^ ]*\).*/\1/')

# 루트 장치가 나타날 때까지 대기 (USB 등 느린 장치)
RETRY=0
while [ ! -b "$ROOT" ] && [ $RETRY -lt 30 ]; do
    sleep 1
    RETRY=$((RETRY + 1))
    mdev -s
done

if [ ! -b "$ROOT" ]; then
    echo "FATAL: Root device $ROOT not found!"
    exec /bin/sh    # 복구 셸 제공
fi

# 실제 루트 파일시스템 마운트
mount -o ro "$ROOT" /mnt

# 정리 후 switch_root
umount /proc /sys /dev 2>/dev/null
exec switch_root /mnt /sbin/init

패턴 3: 완전한 임베디드 루트 (switch_root 없이)

initramfs 자체가 최종 루트 파일시스템인 경우입니다. 플래시 없는 네트워크 부팅이나 read-only 장비에서 사용합니다:

#!/bin/sh
# /init — initramfs가 최종 루트 (inittab 기반)

# 가상 파일시스템 마운트
mount -t proc     none /proc
mount -t sysfs    none /sys
mount -t devtmpfs none /dev
mkdir -p /dev/pts /dev/shm
mount -t devpts   none /dev/pts
mount -t tmpfs    none /dev/shm
mount -t tmpfs    none /tmp
mount -t tmpfs    none /var

# 필요한 디렉토리 생성 (tmpfs 위)
mkdir -p /var/log /var/run /var/lock

# mdev 시작
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

# 호스트명 설정
hostname "busybox-system"

# syslogd/klogd 시작
syslogd -C256    # 256KB 원형 버퍼
klogd

# BusyBox init으로 전환 (inittab 사용)
exec /sbin/init

완전한 루트 파일시스템 구축

BusyBox 기반으로 inittab, rcS, 네트워크, 로깅까지 갖춘 완전한 임베디드 루트 파일시스템을 단일 스크립트로 구축하는 레시피입니다:

#!/bin/sh
# build-rootfs.sh — BusyBox 기반 완전한 루트 파일시스템 구축 스크립트
set -e

ROOTFS="rootfs"
BUSYBOX="./busybox"    # 정적 빌드된 BusyBox 바이너리

echo "=== 1. 디렉토리 구조 생성 ==="
rm -rf "$ROOTFS"
mkdir -p "$ROOTFS"/{bin,sbin,etc/init.d,proc,sys,dev,tmp,mnt,root}
mkdir -p "$ROOTFS"/usr/{bin,sbin}
mkdir -p "$ROOTFS"/var/{log,run,lock,spool}
mkdir -p "$ROOTFS"/usr/share/udhcpc

echo "=== 2. BusyBox 설치 ==="
cp "$BUSYBOX" "$ROOTFS/bin/busybox"
chmod 755 "$ROOTFS/bin/busybox"
chroot "$ROOTFS" /bin/busybox --install -s

echo "=== 3. 기본 설정 파일 생성 ==="

# /etc/inittab
cat > "$ROOTFS/etc/inittab" <<'EOF'
::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L ttyS0 115200 vt100
tty1::askfirst:-/bin/sh
::shutdown:/etc/init.d/rcK
::ctrlaltdel:/sbin/reboot
EOF

# /etc/init.d/rcS (마스터 초기화)
cat > "$ROOTFS/etc/init.d/rcS" <<'SCRIPT'
#!/bin/sh
for s in /etc/init.d/S[0-9][0-9]*; do
    [ -x "$s" ] && "$s" start
done
SCRIPT
chmod +x "$ROOTFS/etc/init.d/rcS"

# /etc/init.d/rcK (종료)
cat > "$ROOTFS/etc/init.d/rcK" <<'SCRIPT'
#!/bin/sh
for s in $(ls -r /etc/init.d/S[0-9][0-9]*); do
    [ -x "$s" ] && "$s" stop
done
SCRIPT
chmod +x "$ROOTFS/etc/init.d/rcK"

# S01sysfs — 가상 파일시스템 마운트
cat > "$ROOTFS/etc/init.d/S01sysfs" <<'SCRIPT'
#!/bin/sh
case "$1" in
  start)
    mount -t proc     none /proc
    mount -t sysfs    none /sys
    mount -t devtmpfs none /dev
    mkdir -p /dev/pts /dev/shm
    mount -t devpts   none /dev/pts
    mount -t tmpfs    none /dev/shm
    mount -t tmpfs    none /tmp
    mount -t tmpfs    none /var
    mkdir -p /var/log /var/run /var/lock
    echo /sbin/mdev > /proc/sys/kernel/hotplug
    mdev -s
    ;;
esac
SCRIPT
chmod +x "$ROOTFS/etc/init.d/S01sysfs"

# S20network — 네트워크 설정
cat > "$ROOTFS/etc/init.d/S20network" <<'SCRIPT'
#!/bin/sh
case "$1" in
  start) ifconfig lo 127.0.0.1 up
         ifconfig eth0 up
         udhcpc -i eth0 -s /usr/share/udhcpc/default.script -b ;;
  stop)  ifconfig eth0 down ;;
esac
SCRIPT
chmod +x "$ROOTFS/etc/init.d/S20network"

# udhcpc default.script
cat > "$ROOTFS/usr/share/udhcpc/default.script" <<'SCRIPT'
#!/bin/sh
case "$1" in
  bound|renew)
    ifconfig "$interface" "$ip" netmask "$subnet"
    [ -n "$router" ] && route add default gw "$router"
    [ -n "$dns" ] && echo "nameserver $dns" > /etc/resolv.conf ;;
  deconfig) ifconfig "$interface" 0.0.0.0 ;;
esac
SCRIPT
chmod +x "$ROOTFS/usr/share/udhcpc/default.script"

# /etc/passwd, /etc/group, /etc/shadow
echo "root:x:0:0:root:/root:/bin/sh" > "$ROOTFS/etc/passwd"
echo "root:x:0:" > "$ROOTFS/etc/group"
echo "root::0:0:99999:7:::" > "$ROOTFS/etc/shadow"
chmod 600 "$ROOTFS/etc/shadow"

# /etc/hostname, /etc/hosts
echo "busybox-dev" > "$ROOTFS/etc/hostname"
echo "127.0.0.1 localhost busybox-dev" > "$ROOTFS/etc/hosts"

# /etc/profile
cat > "$ROOTFS/etc/profile" <<'EOF'
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
export PS1='[\u@\h \W]\$ '
alias ll='ls -la'
EOF

echo "=== 4. cpio 아카이브 생성 ==="
cd "$ROOTFS"
find . | cpio -o -H newc | gzip > ../rootfs.cpio.gz
cd ..
echo "완료: rootfs.cpio.gz ($(du -h rootfs.cpio.gz | cut -f1))"
이 스크립트 실행 후: qemu-system-x86_64 -kernel bzImage -initrd rootfs.cpio.gz -append "console=ttyS0" -nographic로 네트워크, 로깅, 로그인 프롬프트가 있는 완전한 BusyBox 시스템을 테스트할 수 있습니다.

QEMU 테스트

# x86_64 테스트
qemu-system-x86_64 -kernel arch/x86/boot/bzImage \
  -initrd initramfs.cpio.gz \
  -append "console=ttyS0 nokaslr" -nographic -m 256M

# ARM64 (aarch64) 테스트
qemu-system-aarch64 -M virt -cpu cortex-a57 \
  -kernel Image -initrd initramfs.cpio.gz \
  -append "console=ttyAMA0" -nographic -m 512M

# RISC-V 64bit 테스트
qemu-system-riscv64 -M virt \
  -kernel Image -initrd initramfs.cpio.gz \
  -append "console=ttyS0" -nographic -m 256M
상세 QEMU 설정: QEMU 에뮬레이션 활용 문서에서 GDB 연결, 네트워크 설정, 공유 디렉토리 마운트 등 고급 테스트 환경 구성을 다룹니다.

커널 내장 initramfs (CONFIG_INITRAMFS_SOURCE)

외부 -initrd 파일 대신 커널 이미지 안에 BusyBox initramfs를 직접 내장할 수 있습니다. 이 방식은 부트로더가 initrd 로딩을 지원하지 않는 환경이나, 단일 커널 이미지로 부팅해야 하는 경우에 유용합니다. 커널의 CONFIG_INITRAMFS_SOURCE 옵션을 사용합니다:

# 방법 1: 디렉토리 경로 지정 — 커널 빌드 시 자동 cpio 생성
# 커널 .config 에 설정:
CONFIG_INITRAMFS_SOURCE="/path/to/busybox-rootfs"
CONFIG_INITRAMFS_COMPRESSION_GZIP=y

# 방법 2: cpio 아카이브 직접 지정
CONFIG_INITRAMFS_SOURCE="/path/to/initramfs.cpio"

# 방법 3: gen_init_cpio 스펙 파일 사용 (가장 정밀한 제어)
# initramfs_spec.txt:
dir  /bin       0755 0 0
dir  /sbin      0755 0 0
dir  /etc       0755 0 0
dir  /proc      0755 0 0
dir  /sys       0755 0 0
dir  /dev       0755 0 0
file /bin/busybox  /path/to/busybox  0755 0 0
slink /bin/sh      busybox           0777 0 0
slink /sbin/init   ../bin/busybox    0777 0 0
file /init         /path/to/init.sh  0755 0 0
nod  /dev/console  0600 0 0 c 5 1
nod  /dev/null     0666 0 0 c 1 3

# 커널 .config 에 설정:
CONFIG_INITRAMFS_SOURCE="initramfs_spec.txt"
방법CONFIG_INITRAMFS_SOURCE 값장점단점
디렉토리/path/to/rootfs/직관적, 파일 추가/수정 용이파일 소유권/권한이 호스트에 의존 (root 필요할 수 있음)
cpio 아카이브/path/to/initramfs.cpio미리 생성된 아카이브 재사용수정 시 아카이브 재생성 필요
스펙 파일initramfs_spec.txtuid/gid/권한 정밀 제어, root 불필요파일 추가 시 스펙 수동 편집
주의: 커널 내장 initramfs는 커널 이미지 크기를 증가시킵니다. BusyBox rootfs가 1MB라면 커널 이미지가 ~1MB 커집니다. 또한 rootfs를 변경할 때마다 커널을 다시 빌드해야 합니다. 개발 중에는 외부 -initrd 방식이 더 편리하고, 최종 프로덕션에서 내장 방식을 사용하는 것이 일반적입니다.

빌드 시스템 통합

실제 임베디드 프로젝트에서는 BusyBox를 직접 빌드하기보다 빌드 시스템을 통해 통합합니다:

빌드 시스템BusyBox 설정 방법설정 파일
Buildrootmake busybox-menuconfigpackage/busybox/busybox.config
OpenWrt기본 포함, make menuconfig → Utilitiespackage/utils/busybox/config/
YoctoCORE_IMAGE_EXTRA_INSTALL += "busybox"meta/recipes-core/busybox/
# Buildroot에서 BusyBox 설정 변경
cd buildroot-2024.02
make busybox-menuconfig    # BusyBox 전용 menuconfig
make                       # 전체 시스템 빌드 (BusyBox 포함)
빌드 시스템 상세 가이드: 임베디드 빌드 시스템 (OpenWrt/Buildroot/Yocto) 문서에서 각 빌드 시스템의 전체 워크플로와 BusyBox 통합 방법을 상세히 다룹니다.

컨테이너 환경 활용

BusyBox는 컨테이너 베이스 이미지로도 널리 사용됩니다. Docker Hub의 공식 BusyBox 이미지는 약 1.2MB로, 최소한의 셸 환경이 필요한 유틸리티 컨테이너에 적합합니다:

베이스 이미지크기패키지 관리자용도
busybox:latest~1.2 MBash없음최소 유틸리티 컨테이너
busybox:musl~1.4 MBash없음musl 기반 (Alpine 호환)
alpine:latest~7 MBash (BusyBox)apk경량 리눅스 배포판
ubuntu:latest~78 MBbashapt범용 (비교 기준)
scratch0 MB없음없음정적 바이너리 전용

Alpine Linux는 BusyBox + musl libc + apk 패키지 관리자로 구성된 경량 리눅스 배포판입니다. Docker 컨테이너 베이스로 사실상 표준이 되었으며, BusyBox의 가장 성공적인 활용 사례입니다. Alpine의 BusyBox 패키징은 Alpine 패키지 저장소에서 확인할 수 있습니다:

# BusyBox 기반 최소 컨테이너
docker run -it busybox:latest sh
/ # busybox --list | wc -l    # 포함된 애플릿 수 확인
396

# Alpine Linux (BusyBox + musl + apk)
docker run -it alpine:latest sh
/ # cat /etc/alpine-release
3.20.0
/ # which ls
/bin/ls
/ # ls -la /bin/ls
lrwxrwxrwx    1 root     root          12 ... /bin/ls -> /bin/busybox

# Dockerfile: BusyBox 기반 경량 이미지
# FROM busybox:musl
# COPY my-static-binary /usr/local/bin/app
# CMD ["/usr/local/bin/app"]

크기 최적화와 성능

크기 비교

BusyBox의 핵심 장점은 극적인 크기 절감입니다. 동일한 기능을 제공하는 GNU 유틸리티 대비 10~75배 작은 바이너리를 생성합니다. 아래 다이어그램은 각 구성별 크기를 시각적으로 비교합니다:

BusyBox vs GNU 유틸리티 크기 비교 0 2 MB 4 MB 8 MB 12 MB 16 MB 최소 (musl, 정적) ~200 KB defconfig (동적) ~700 KB defconfig (musl, 정적) ~800 KB defconfig (glibc, 정적) ~1.1 MB toybox (defconfig) ~800 KB Alpine (BusyBox+musl+apk) ~7 MB GNU coreutils+bash+util-linux ~15 MB+ 바이너리 크기 (로그 스케일 아님, 대략적 비율)
BusyBox 구성별 크기 비교: 최소 200KB부터 GNU 유틸리티 합산 15MB+까지
구성크기애플릿 수비고
BusyBox defconfig (정적, glibc)~1.1 MB~300범용 임베디드
BusyBox defconfig (정적, musl)~800 KB~300musl로 크기 절감
BusyBox defconfig (동적, glibc)~700 KB + libc~300libc 공유 시
BusyBox 최소 설정 (정적, musl)~200 KB~20initramfs 최소
BusyBox + strip + UPX~300 KB~300압축된 defconfig
GNU coreutils + bash + util-linux~15 MB+~200개별 바이너리 합산
toybox (defconfig)~800 KB~230BSD 라이선스 대안

크기 줄이기 기법

기법절감 효과방법
불필요 애플릿 제거최대 80%make allnoconfig 후 필요한 것만 활성화
musl libc 사용~30%make CC=musl-gcc CONFIG_STATIC=y
FEATURE_PREFER_APPLETS~5~10%외부 프로그램 대신 내장 애플릿 우선. fork/exec 오버헤드 제거
FEATURE_SH_STANDALONE~5%셸이 PATH 탐색 대신 내장 애플릿 직접 호출
strip~20%strip busybox — 디버그 심볼 제거
UPX 압축~60%upx --best busybox — 런타임 압축 해제 (부팅 시간 증가)
-Os 최적화~10%기본 적용됨. -O2보다 크기 우선
# 최소 크기 빌드 예시
make allnoconfig
# 필수 애플릿만 선택 (.config 편집 또는 menuconfig)
make CC=musl-gcc CONFIG_STATIC=y -j$(nproc)
strip busybox
ls -lh busybox    # ~200KB

# UPX 추가 압축 (선택)
upx --best busybox
ls -lh busybox    # ~80KB (UPX 압축 후)

NOMMU 지원

MMU(Memory Management Unit)가 없는 프로세서(ARM Cortex-M, 일부 MIPS, Blackfin, ColdFire 등)에서는 fork() 시스템 콜을 사용할 수 없습니다. fork()는 프로세스 주소 공간을 복제하는데, MMU 없이는 가상 메모리 매핑이 불가능하기 때문입니다. BusyBox는 이러한 NOMMU 환경을 위해 vfork() 기반 동작을 지원합니다:

항목MMU 환경 (일반)NOMMU 환경
프로세스 생성fork() + exec()vfork() + exec()
ash (권장)hush (필수, ash는 fork 의존)
파이프fork()로 자식 생성vfork() — 부모 일시 중단, 변수 공유 주의
바이너리 형식ELF (위치 독립 코드)FLAT (bFLT), XIP (Execute In Place)
CONFIG 옵션(기본값)CONFIG_NOMMU=y
대상 프로세서Cortex-A, x86, RISC-V (S/U mode)Cortex-M, Blackfin, ColdFire, RISC-V (M mode)
메모리 보호프로세스 간 격리격리 없음 — 버그 시 시스템 전체 영향
/* MMU vs NOMMU 프로세스 생성 차이 */

/* MMU 환경: fork()가 주소 공간 복제 (COW) */
pid_t pid = fork();
if (pid == 0) {
    /* 자식: 독립된 메모리 공간 */
    exec("/bin/ls", ...);
}

/* NOMMU 환경: vfork()는 부모 스택 공유 */
pid_t pid = vfork();
if (pid == 0) {
    /* 자식: 부모와 같은 메모리!
     * exec() 또는 _exit()만 호출 가능
     * 변수 수정, 함수 호출 금지 */
    exec("/bin/ls", ...);
    _exit(1);  /* exec 실패 시 _exit 사용 (return 금지) */
}
/* 부모: vfork()는 자식이 exec/_exit할 때까지 블록됨 */
NOMMU 제약사항:
  • vfork() 후 자식에서 exec() 또는 _exit()만 호출 가능. 변수 수정, 함수 호출, return 금지
  • hush 셸의 파이프 구현은 MMU 환경 대비 제한적 (중첩 파이프 제약)
  • 프로세스 간 메모리 보호가 없으므로, 하나의 버그가 전체 시스템에 영향
  • 동적 라이브러리 사용 불가 — 반드시 정적 링크 또는 XIP 사용

보안 고려사항

항목설명설정
SUID일부 애플릿(passwd, su, ping)은 SUID 비트 필요CONFIG_FEATURE_SUID — SUID 실행 시 최소 권한으로 전환
SELinuxSELinux 보안 컨텍스트 지원CONFIG_SELINUX=y — libselinux 의존
권한 분리애플릿별 SUID 필요 여부 지정CONFIG_FEATURE_INDIVIDUAL=y — 애플릿별 개별 바이너리 생성
비밀번호 해싱SHA-256/SHA-512 해시 지원CONFIG_SHA256_HWACCEL
보안 주의: 임베디드 제품에서 BusyBox를 사용할 때:
  • 불필요한 애플릿(telnetd, ftpd 등)은 반드시 비활성화
  • 기본 비밀번호를 설정하지 않은 채 getty/login을 활성화하지 말 것
  • CONFIG_FEATURE_SUID_CONFIG=y로 SUID 애플릿을 명시적으로 제한
  • 프로덕션에서는 telnetd 대신 dropbear(SSH) 사용 권장

고급 활용

mdev 경량 디바이스 관리자

mdev는 udev의 경량 대안으로, 임베디드 환경에서 /dev 디바이스 노드를 동적으로 관리합니다. mdev의 설정 문법은 공식 mdev 문서에서 상세히 설명합니다:

항목mdevudev (systemd)
크기~15 KB (BusyBox 내장)~2 MB (libsystemd 포함)
설정/etc/mdev.conf/etc/udev/rules.d/
규칙 문법단순 정규식 + 소유권/권한복잡한 키-값 매칭
핫플러그/proc/sys/kernel/hotplug 경유netlink 소켓 직접 수신
펌웨어 로드지원 (CONFIG_FEATURE_MDEV_LOAD_FIRMWARE)기본 지원
핫플러그 이벤트 흐름 부팅 시 스캔 (mdev -s) 커널: 하드웨어 이벤트 감지 (USB 장치 삽입) 커널이 /proc/sys/kernel/hotplug 읽음 → /sbin/mdev 호출 (환경 변수 전달) mdev: /sys 디바이스 속성 읽기 $SUBSYSTEM, $DEVNAME, $MAJOR, $MINOR, $ACTION /etc/mdev.conf 규칙 매칭 sd[a-z] 0:6 0660 @/etc/mdev/storage.sh /dev/sda 노드 생성 (mknod) uid=0, gid=6(disk), mode=0660 @스크립트 실행 (삽입 이벤트) /etc/mdev/storage.sh — 자동 마운트 등 부팅 init 스크립트에서 mdev -s 실행 /sys/class/* 스캔 tty, net, input, misc ... /sys/block/* 스캔 sda, mmcblk0 ... 각 장치의 dev 파일 읽기 MAJOR:MINOR 번호 추출 모든 /dev 노드 일괄 생성 /dev/tty0, /dev/sda, /dev/null ... 핫플러그: 런타임 장치 삽입/제거 처리 (이벤트 기반) mdev -s: 부팅 시 기존 장치 일괄 스캔 (/sys 순회)
mdev 핫플러그 이벤트 흐름 및 부팅 시 디바이스 스캔 과정
# mdev 설정 및 실행
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s    # 기존 디바이스 스캔하여 /dev 채우기

# /etc/mdev.conf 예시
# 형식: <regex> <uid>:<gid> <permissions> [=path] [@|$|*cmd]
console  0:0  0600
null     0:0  0666
zero     0:0  0666
random   0:0  0444
urandom  0:0  0444
tty[0-9] 0:5  0660
sd[a-z]  0:6  0660  @/etc/mdev/storage.sh    # 삽입 시 스크립트 실행
sd[a-z]  0:6  0660  $/etc/mdev/storage.sh    # 제거 시 스크립트 실행

httpd 경량 웹 서버

BusyBox httpd는 임베디드 장비의 관리 웹 인터페이스나 펌웨어 업데이트 서버로 사용됩니다. 전체 크기가 약 20KB이면서 CGI 스크립팅, 기본 인증(Basic Auth), IP 기반 접근 제어를 지원합니다:

기능지원 여부비고
정적 파일 서빙지원MIME 타입 자동/수동 매핑
CGI (Common Gateway Interface)지원/cgi-bin/ 경로 기본
기본 인증 (Basic Auth)지원httpd.conf에서 경로별 설정
IP 접근 제어지원A:(허용)/D:(차단) 규칙
SSL/TLS미지원stunnel 또는 리버스 프록시 필요
디렉토리 리스팅지원D:*로 비활성화 가능
가상 호스트미지원단일 호스트만 지원
Keep-Alive미지원연결당 하나의 요청
# 기본 실행 (포트 8080, /var/www 루트)
httpd -p 8080 -h /var/www -f    # -f: 포그라운드 실행

# CGI 스크립트 예시 (/var/www/cgi-bin/status.sh)
#!/bin/sh
echo "Content-type: text/html"
echo ""
echo "<h1>System Status</h1>"
echo "<pre>"
echo "=== Uptime ===" && uptime
echo "=== Memory ===" && free
echo "=== Disk ===" && df -h
echo "=== Network ===" && ifconfig eth0
echo "</pre>"

# httpd.conf 설정 (/var/www/httpd.conf)
# 디렉토리별 인증 (사용자:비밀번호)
/cgi-bin:admin:s3cretPass
/admin:admin:s3cretPass
# IP 접근 제어 (A=허용, D=차단)
A:192.168.1.0/24     # 로컬 네트워크 허용
D:*                   # 나머지 차단
# MIME 타입 추가
.json:application/json
.svg:image/svg+xml

# 펌웨어 업데이트 CGI 예시 (/var/www/cgi-bin/upload.sh)
#!/bin/sh
echo "Content-type: text/plain"
echo ""
# POST 데이터를 파일로 저장
cat > /tmp/firmware.bin
if [ -s /tmp/firmware.bin ]; then
    echo "Firmware received, flashing..."
    mtd write /tmp/firmware.bin firmware
    reboot
else
    echo "Error: No firmware data received"
fi

udhcpc/udhcpd DHCP

udhcpc는 BusyBox의 DHCP 클라이언트로, 스크립트 기반으로 네트워크를 설정합니다:

# DHCP 클라이언트 실행
udhcpc -i eth0 -s /usr/share/udhcpc/default.script

# default.script 예시 (/usr/share/udhcpc/default.script)
#!/bin/sh
case "$1" in
  bound|renew)
    ifconfig "$interface" "$ip" netmask "$subnet"
    if [ -n "$router" ]; then
      route del default 2>/dev/null
      route add default gw "$router"
    fi
    echo "nameserver $dns" > /etc/resolv.conf
    ;;
  deconfig)
    ifconfig "$interface" 0.0.0.0
    ;;
esac

ash 셸 스크립팅 주의사항

BusyBox ash에서 스크립트를 작성할 때 bash 비호환 사항에 주의해야 합니다. ash는 POSIX Shell Command Language를 기반으로 하며, ShellCheck 도구를 사용하면 스크립트의 이식성 문제를 사전에 검출할 수 있습니다:

# ✗ bash 전용 — ash에서 오류 발생
array=(one two three)         # 배열 미지원
[[ $x == "test" ]]            # [[ ]] 미지원
echo ${var^^}                 # 대소문자 변환 미지원
diff <(ls dir1) <(ls dir2)   # 프로세스 치환 미지원

# ✓ ash 호환 대안
set -- one two three; echo "$2"     # 위치 매개변수 사용
[ "$x" = "test" ]                   # 단일 [ ] 사용
echo "$var" | tr 'a-z' 'A-Z'       # 외부 명령 사용
ls dir1 > /tmp/a; ls dir2 > /tmp/b; diff /tmp/a /tmp/b

# 셸 스크립트 이식성 확인 팁
# 1. 스크립트 첫 줄: #!/bin/sh (#!/bin/bash 아님)
# 2. ShellCheck 도구로 POSIX 호환성 검사
# 3. FEATURE_SH_STANDALONE 활성화 시 PATH에 없는 외부 명령 동작 주의

실전 init.d 서비스 스크립트 패턴:

BusyBox 임베디드 시스템의 init.d 서비스 스크립트는 다음 패턴을 따릅니다. bash 의존성 없이 순수 POSIX sh로 작성해야 합니다:

#!/bin/sh
# /etc/init.d/S20network — 네트워크 초기화 스크립트 예시

DAEMON="/sbin/udhcpc"
IFACE="eth0"
PIDFILE="/var/run/udhcpc.${IFACE}.pid"
SCRIPT="/usr/share/udhcpc/default.script"

start() {
    printf "Starting network on %s: " "$IFACE"
    # 인터페이스 활성화
    ifconfig "$IFACE" up
    # DHCP 클라이언트 시작 (백그라운드, PID 파일 생성)
    start-stop-daemon -S -b -m -p "$PIDFILE" \
        -x "$DAEMON" -- -i "$IFACE" -s "$SCRIPT" -p "$PIDFILE"
    if [ $? -eq 0 ]; then
        echo "OK"
    else
        echo "FAIL"
        return 1
    fi
}

stop() {
    printf "Stopping network on %s: " "$IFACE"
    start-stop-daemon -K -p "$PIDFILE" -x "$DAEMON"
    ifconfig "$IFACE" down
    rm -f "$PIDFILE"
    echo "OK"
}

case "$1" in
    start)   start ;;
    stop)    stop ;;
    restart) stop; start ;;
    *)       echo "Usage: $0 {start|stop|restart}"; exit 1 ;;
esac

ash에서의 고급 패턴:

# 설정 파일 파싱 (연관 배열 대신 eval 사용)
while IFS='=' read -r key value; do
    case "$key" in
        \#*|"") continue ;;   # 주석, 빈 줄 무시
    esac
    eval "cfg_${key}='${value}'"
done < /etc/myapp.conf
echo "Server: $cfg_server, Port: $cfg_port"

# 함수에서 값 반환 (nameref 미지원이므로 서브셸 stdout 사용)
get_ip() {
    ifconfig "$1" 2>/dev/null | grep 'inet addr' | \
        sed 's/.*addr:\([0-9.]*\).*/\1/'
}
MY_IP=$(get_ip eth0)

# 간단한 로깅 함수
log() {
    local level="$1"; shift
    logger -t "myapp" -p "daemon.${level}" "$*"
    echo "[$(date '+%H:%M:%S')] ${level}: $*"
}
log info "Service started"
log err "Connection failed"

트러블슈팅

자주 발생하는 문제

증상원인해결
applet not found해당 애플릿이 빌드에 포함되지 않음make menuconfig에서 애플릿 활성화 후 재빌드
/bin/sh: can't execute동적 링크 빌드인데 공유 라이브러리 누락CONFIG_STATIC=y로 정적 빌드 또는 ldd busybox로 의존성 확인
initramfs 부팅 시 not syncing: No init found/init 스크립트 없거나 실행 권한 없음chmod +x init, #!/bin/sh 첫 줄 확인
크로스 컴파일 빌드 실패CROSS_COMPILE 접두사 오류which ${CROSS_COMPILE}gcc로 경로 확인
mdev -s 실행 시 디바이스 누락/sys 마운트 안 됨mount -t sysfs none /sys 먼저 실행
getty 반복 재시작터미널 장치 미존재 또는 권한 문제/dev/ttyS0 존재 확인, console=ttyS0 커널 파라미터 확인
정적 빌드 시 링크 에러 (glibc)glibc 정적 빌드 라이브러리 누락apt install libc6-dev 또는 musl 사용
ash: syntax error 스크립트 오류bash 전용 문법 사용위 ash 스크립팅 주의사항 참고, POSIX 호환 문법으로 수정

디버깅 기법

BusyBox 자체와 BusyBox 기반 시스템을 디버깅하는 다양한 방법입니다:

1. 빌드 시점 진단

# 디버그 정보 포함 빌드
make menuconfig
# Settings → Build with debug information (CONFIG_DEBUG=y)
# Settings → Build with extra compiler warnings (CONFIG_WERROR=y)
make V=1 -j$(nproc)    # V=1: 상세 빌드 출력

# 빌드에 포함된 애플릿 확인
./busybox --list           # 활성화된 모든 애플릿
./busybox --list-full      # 설치 경로 포함 목록

# 바이너리 정보 확인
file busybox               # ELF 타입, 아키텍처, 링크 방식
size busybox               # text/data/bss 세그먼트 크기
nm busybox | grep _main    # 포함된 xxx_main 심볼 확인
readelf -d busybox         # 동적 링크 의존성 (정적이면 빈 출력)

2. 런타임 디버깅

# strace로 시스템 콜 추적
strace -f busybox ls /tmp                    # 단일 애플릿 추적
strace -f -e trace=open,read,write busybox cat /etc/hosts  # 특정 syscall만

# ltrace로 라이브러리 함수 추적 (동적 링크 빌드만)
ltrace busybox ls /tmp

# BusyBox 자체 디버그 출력
# CONFIG_FEATURE_VERBOSE_USAGE=y — 상세 도움말
busybox ls --help    # 상세 옵션 설명

3. QEMU + GDB 원격 디버깅

# 터미널 1: QEMU 실행 (GDB 서버 활성화)
qemu-system-x86_64 -kernel bzImage \
  -initrd initramfs.cpio.gz \
  -append "console=ttyS0 nokaslr" \
  -nographic -s -S    # -s: GDB 서버 :1234, -S: CPU 정지

# 터미널 2: GDB 연결
gdb busybox_unstripped     # strip 전 바이너리 사용
(gdb) target remote :1234
(gdb) break ls_main        # 특정 애플릿 브레이크포인트
(gdb) break find_applet_by_name  # 디스패치 과정 추적
(gdb) continue
(gdb) bt                   # 백트레이스로 호출 경로 확인

4. initramfs 부팅 문제 진단

# initramfs 내용물 검증
mkdir /tmp/check && cd /tmp/check
zcat initramfs.cpio.gz | cpio -idmv    # 풀어서 확인
ls -la init                             # 실행 권한 확인
head -1 init                            # #!/bin/sh 확인
file bin/busybox                        # 아키텍처 확인
bin/busybox --list | wc -l              # 애플릿 수 확인

# 커널 부팅 파라미터로 디버깅
# rdinit=/bin/sh  — init 대신 직접 셸 실행
# init=/bin/sh    — initramfs의 /init 대신 /bin/sh
qemu-system-x86_64 -kernel bzImage \
  -initrd initramfs.cpio.gz \
  -append "console=ttyS0 rdinit=/bin/sh" -nographic
커널 디버깅 가이드: 커널 디버깅GDB 디버깅 문서에서 QEMU+GDB 환경 설정을 상세히 다룹니다.

참고 자료

공식 자료

자료URL설명
공식 사이트busybox.net다운로드, 뉴스, 릴리스 정보
FAQbusybox.net/FAQ.html자주 묻는 질문과 답변
소스 저장소 (Git)git.busybox.net/busybox공식 Git 저장소, 커밋 로그, 브랜치
소스 미러 (GitHub)github.com/mirror/busyboxGitHub 읽기 전용 미러 — 코드 탐색에 편리
다운로드busybox.net/downloads안정 릴리스 tarball, 패치, 스냅샷
버그 트래커bugs.busybox.netBugzilla 기반 버그 리포트, 기능 요청
메일링 리스트busybox ML개발 토론, 패치 리뷰, 릴리스 공지
애플릿 목록BusyBox.html전체 애플릿 매뉴얼 (명령어별 옵션 설명)
라이선스busybox.net/license.htmlGPLv2 라이선스 전문 및 라이선스 준수 안내
BusyBox 소개busybox.net/about.htmlBusyBox 프로젝트 개요, 지원 아키텍처, 설계 철학
링크 목록 (심볼릭 링크)BusyBox 명령어 레퍼런스 (busybox.net)각 애플릿의 사용법, 옵션, GNU 대비 차이점
프로젝트URL관계
toyboxToybox — BSD 라이선스 BusyBox 대안 (landley.net)BSD 라이선스 대안. Android에서 BusyBox 대체
toybox vs BusyBoxToybox FAQ — BusyBox와의 차이점 (landley.net)Toybox 설계 철학, BusyBox 대비 장단점 비교
Alpine LinuxAlpine Linux — BusyBox 기반 배포판 (alpinelinux.org)BusyBox + musl 기반 경량 리눅스 배포판
Alpine WikiAlpine Wiki — BusyBox 설정 가이드 (wiki.alpinelinux.org)Alpine에서의 BusyBox 구성, 대체 패키지 설치 방법
musl libcmusl libc — 정적 링킹에 최적화된 C 라이브러리 (musl.libc.org)BusyBox 정적 빌드에 자주 사용되는 경량 C 라이브러리
musl FAQmusl과 glibc 기능 차이 (wiki.musl-libc.org)musl 정적 빌드 시 주의할 glibc 호환성 차이
BuildrootBuildroot — 임베디드 빌드 시스템 (buildroot.org)BusyBox 기본 채택 임베디드 빌드 시스템
Buildroot BusyBox 설정Buildroot 매뉴얼 — BusyBox 커스터마이징 (buildroot.org)Buildroot에서 BusyBox 설정 파일 커스터마이징 방법
OpenWrtOpenWrt — BusyBox 기반 라우터 펌웨어 (openwrt.org)BusyBox 기반 라우터 펌웨어
uClibc-nguClibc-ng — 임베디드 C 라이브러리 (uclibc-ng.org)임베디드용 경량 C 라이브러리 (BusyBox와 자주 조합)
dropbearDropbear — 경량 SSH 서버 (matt.ucc.asn.au)BusyBox 환경에서 자주 사용되는 경량 SSH 서버
sbase/ubasesbase — 최소주의 유닉스 유틸리티 (suckless.org)suckless 프로젝트의 최소주의 유닉스 유틸리티
runitrunit — 경량 init/서비스 관리자 (smarden.org)BusyBox init 대안으로 사용되는 경량 서비스 감독 시스템
GNU CoreutilsGNU Coreutils — BusyBox 비교 대상 (gnu.org)BusyBox가 경량 대체하는 GNU 핵심 유틸리티 전체 목록
Yocto ProjectYocto Project — 임베디드 리눅스 빌드 (yoctoproject.org)BusyBox를 rootfs에 포함하는 임베디드 리눅스 빌드 프레임워크

커널 관련 문서

문서URL설명
initramfs 가이드ramfs/rootfs/initramfs 공식 문서 (kernel.org)커널 공식 initramfs/initrd 문서
early userspace초기 사용자 공간 지원 (kernel.org)초기 사용자 공간 지원 (gen_init_cpio, CONFIG_INITRAMFS_SOURCE)
devtmpfsdevtmpfs 커널 문서 (kernel.org)mdev 대안인 커널 기반 /dev 자동 생성
Kconfig 언어Kconfig 언어 명세 (kernel.org)BusyBox가 사용하는 동일한 Kconfig 시스템 문서
Kbuild 시스템Kbuild 빌드 시스템 (kernel.org)BusyBox Makefile 구조가 차용한 커널 빌드 시스템
switch_rootinitramfs에서 실제 rootfs 전환 (kernel.org)BusyBox switch_root 명령이 수행하는 루트 전환 메커니즘

학습 자료

자료URL/출처설명
BusyBox 소스 해부BusyBox 아키텍처 분석 (IBM DeveloperWorks)M. Tim Jones의 BusyBox 아키텍처 분석 기사
Embedded Linux PrimerChristopher Hallinan 저 (Prentice Hall)BusyBox 챕터 포함 임베디드 리눅스 입문서
Mastering Embedded Linux ProgrammingFrank Vasquez, Chris Simmonds 저 (Packt)BusyBox init, rootfs 구축 실습 포함
Linux From Scratch (LFS)Linux From Scratch — 리눅스 직접 구축 (linuxfromscratch.org)리눅스 시스템 직접 구축 — BusyBox 대신 GNU 유틸리티 사용하지만 구조 이해에 도움
POSIX Shell ReferencePOSIX 셸 명세 (pubs.opengroup.org)ash 스크립팅의 기준이 되는 POSIX 셸 명세
POSIX UtilitiesPOSIX 유틸리티 목록 (pubs.opengroup.org)BusyBox 애플릿이 구현하는 POSIX 표준 유틸리티 전체 목록
ShellCheckShellCheck — 셸 스크립트 정적 분석 (shellcheck.net)셸 스크립트 정적 분석 도구 — bash/POSIX 호환성 검사
Minimal Linux LiveMinimal Linux Live — BusyBox 기반 최소 리눅스 (github.com)커널 + BusyBox만으로 부팅하는 최소 리눅스 교육 프로젝트
Arch Wiki — BusyBoxBusyBox 설정 및 활용 (wiki.archlinux.org)BusyBox 설치, initramfs 구성, mkinitcpio 통합 가이드
Gentoo Wiki — BusyBoxBusyBox 구성 가이드 (wiki.gentoo.org)Gentoo에서의 BusyBox 구성, initramfs 생성, 복구 셸 활용
문서관련 내용
부팅 과정커널 부팅에서 init 실행까지의 전체 흐름
개발 환경 설정BusyBox 기반 initramfs 생성 및 커널 개발 환경
QEMU 에뮬레이션BusyBox initramfs를 QEMU에서 테스트하는 방법
임베디드 빌드 시스템Buildroot/OpenWrt/Yocto에서 BusyBox 통합
RAM 디스크initramfs와 initrd의 차이, 루트 파일시스템 구성
Linux From Scratch크로스 컴파일 환경 구축
커널 디버깅QEMU+GDB 디버깅 환경 설정
GDB 디버깅GDB를 이용한 BusyBox/커널 디버깅