임베디드 스터디 - 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 init | systemd | |
|---|---|---|
| 초기화 방식 | 스크립트 순차 실행 | 유닛 파일 병렬 실행 |
| 부팅 속도 | 느림 | 빠름 |
| 의존성 관리 | 수동 (스크립트 순서) | 자동 (유닛 의존성 선언) |
| 주요 사용처 | 임베디드, 경량 시스템 | 데스크톱, 서버 리눅스 |
- 임베디드에서는 시스템이 단순하고 자원이 제한적이므로 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 라이센스를 따릅니다.