포스트

임베디드 스터디 - init 스크립트 / BusyBox / 공유라이브러리

임베디드 스터디 - init 스크립트 / BusyBox / 공유라이브러리

사용자 공간 초기화 개요

  • kernel_init()에서 /sbin/init(PID 1)이 실행되는 것까지 확인했음
  • /sbin/init 이후 시스템의 나머지 서비스(네트워크, 로그, 쉘 등)를 초기화하는 과정이 필요함
  • init이 모든 서비스를 직접 코딩하면 서비스 추가 시마다 init 코드를 수정해야 하므로 확장성이 나쁨
    • 이를 해결하기 위해 스크립트 기반으로 서비스를 관리함

SysV init

  • 전통적인 Unix 계열 init 시스템
  • /etc/inittab 파일을 읽어 실행할 스크립트 목록을 파악하고, 순서대로 실행함
1
2
3
4
5
6
/sbin/init
    ↓
/etc/inittab 읽기
    ↓
/etc/init.d/rcS 실행   ← 임베디드에서 주로 사용하는 초기화 스크립트
(파일시스템 마운트, 네트워크 설정, 서비스 시작 등)
  • /etc/inittab 예시
1
2
3
4
5
6
7
8
# 부팅 시 rcS 실행
::sysinit:/etc/init.d/rcS

# 콘솔에 쉘 실행
::respawn:/bin/sh

# 리부트 시 처리
::restart:/sbin/init
  • SysV init vs systemd 비교
 SysV initsystemd
초기화 방식스크립트 순차 실행유닛 파일 병렬 실행
부팅 속도느림빠름
의존성 관리수동 (스크립트 순서)자동 (유닛 의존성 선언)
주요 사용처임베디드, 경량 시스템데스크톱, 서버 리눅스
  • 임베디드에서는 시스템이 단순하고 자원이 제한적이므로 SysV init이 적합한 경우가 많음

BusyBox

  • 임베디드 리눅스의 표준 툴킷
  • ls, cp, sh, mount, ifconfig 등 수백 개의 유틸리티를 단일 바이너리 하나에 통합
1
2
3
4
5
6
일반 리눅스                         BusyBox
/bin/ls      (~100KB)
/bin/cp      (~80KB)          →     /bin/busybox  (단일 바이너리, ~1MB)
/bin/sh      (~900KB)
/bin/mount   (~60KB)
... 수백 개 ...
  • 개별 바이너리로 관리할 때보다 전체 용량이 크게 줄어듦

Applet과 심볼릭 링크

  • BusyBox의 각 기능 단위를 applet이라고 함
  • busybox ls, busybox cp 처럼 매번 앞에 붙이지 않고, 심볼릭 링크로 일반 명령어처럼 사용 가능
1
2
3
4
/bin/ls    →  /bin/busybox  (심볼릭 링크)
/bin/cp    →  /bin/busybox  (심볼릭 링크)
/bin/sh    →  /bin/busybox  (심볼릭 링크)
/bin/mount →  /bin/busybox  (심볼릭 링크)
  • BusyBox는 실행 시 argv[0](호출된 이름)을 확인해 어떤 applet을 실행할지 결정함
    • ls로 호출되면 ls 동작, sh로 호출되면 쉘 동작
1
2
3
4
5
6
7
// BusyBox 내부 동작 (개념적 표현)
int main(int argc, char *argv[]) {
    if (strcmp(argv[0], "ls") == 0)   return ls_main(argc, argv);
    if (strcmp(argv[0], "cp") == 0)   return cp_main(argc, argv);
    if (strcmp(argv[0], "sh") == 0)   return sh_main(argc, argv);
    // ...
}

공유라이브러리와 ld.so

정적 링킹 vs 동적 링킹

  • printf, malloc 등 libc 함수를 프로그램마다 복사해 넣으면(정적 링킹) 용량·메모리 낭비
1
2
3
4
5
정적 링킹                           동적 링킹
프로그램A + libc 복사본
프로그램B + libc 복사본    →        프로그램A ──┐
프로그램C + libc 복사본             프로그램B ──┼── libc.so (메모리에 1개만 로드)
                                 프로그램C ──┘
  • 공유라이브러리(.so) 방식은 메모리에 .so를 하나만 올리고 여러 프로세스가 공유함

ld.so — 동적 링커/로더

  • 프로그램 실행 시 필요한 .so를 찾아 메모리에 로드하고 심볼 주소를 연결하는 역할
  • 커널이 프로그램을 실행할 때 main() 보다 ld.so먼저 실행함
1
2
3
4
5
6
7
8
9
10
11
12
프로그램 실행 (./myapp)
        ↓
커널이 ld.so를 먼저 실행
        ↓
myapp ELF 헤더의 NEEDED 섹션 확인
→ 필요한 .so 목록 파악 (libc.so, libm.so 등)
        ↓
각 .so를 메모리에 로드
        ↓
심볼 주소 연결 (printf → libc.so의 실제 주소)
        ↓
myapp의 main() 실행
  • 임베디드에서는 용량 이슈로 glibc 대신 경량 libc를 사용하는 경우가 많음
라이브러리특징주요 사용처
glibc기능 완전, 용량 큼데스크톱/서버 리눅스
musl libc경량, POSIX 준수임베디드, Alpine Linux
uClibc초경량, 구형 임베디드저사양 MCU 기반 시스템

전체 흐름 요약

1
2
3
4
5
6
7
8
9
10
11
12
/sbin/init 실행 (PID 1)
        ↓
/etc/inittab 읽기 → /etc/init.d/rcS 스크립트 실행
(파일시스템 마운트, 네트워크 설정, 서비스 시작)
        ↓
rcS 안에서 BusyBox applet 호출
(mount, ifconfig, sh 등 → 실제로는 전부 /bin/busybox)
        ↓
각 프로그램 실행 시 ld.so가 먼저 동작
→ 필요한 .so 로드 및 심볼 주소 연결
        ↓
쉘 프롬프트 (BusyBox sh)
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.