OS 없이 printf 구현하기 - Bare Metal 환경에서 C 표준 라이브러리 활용
(popovicu.com)- Newlib 라이브러리를 활용해 운영체제 없이도 printf를 포함한 C 표준 함수를 사용할 수 있도록 구현한 방법 소개
- RISC-V 아키텍처 기반 Bare Metal 환경에서 UART 드라이버 및 메모리 할당 함수를 직접 구현하여 Newlib에 연결
- _write, _sbrk, _close 등 최소한의 시스템 호출 함수 구현만으로도 printf 등 고급 기능 사용 가능
- Newlib 기반 툴체인을 만들기 위해 RISC-V GCC 툴체인과 함께 자동 빌드 및 링크 스크립트 작성법 안내
- 결과적으로 UART 출력, scanf 입력, 동적 메모리 할당까지 작동하는 printf 환경 구축에 성공함
Software abstractions and C standard library
- 일반 OS에서는
printf
호출 시 커널 시스템 콜, 터미널 계층, 폰트 렌더링 등 다양한 추상화 계층이 동작함 - Bare Metal 환경에서는 운영체제 없이 직접 입출력 제어가 필요하며, 이를 위해 직접 드라이버 구현이 요구됨
- Newlib는 전체 C 표준 라이브러리 대신 최소 기능만 구현하여 확장 가능한 구성을 제공함
Newlib concept
-
printf
는 내부적으로_write
같은 간단한 primitive 함수를 기반으로 구현됨 - Newlib는 초기에는 모든 함수가 더미 형태로 정의되어 있으며, 필요한 부분만 구현하면 나머지는 기본값 사용 가능
- 개발자가 필요한 함수만 구현하면 유연하게 C 라이브러리 기능 사용 가능
Cross-compilation toolchain
- x86_64/Linux → RISC-V로 크로스 컴파일을 위해 GCC 소스에서 직접 빌드 필요
- Newlib이 기본 C 라이브러리로 설정된 툴체인을 구축하여 RISC-V용 바이너리를 빌드할 수 있도록 설정
Toolchain details
- 툴체인 빌드 시
--prefix
,--enable-multilib
,--disable-gdb
,--with-cmodel=medany
옵션 사용 -
medany
는 RISC-V에서 고주소 메모리 영역 접근 가능하게 해주는 설정임 - 빌드 완료 후에는
/opt/riscv-newlib
경로에서 cross-compiler 및 Newlib 라이브러리 활용 가능
Implementing the memory and UART building blocks
- QEMU 환경의 16550A UART 하드웨어 주소를 직접 접근하여 문자 송수신 구현
-
_write
,_sbrk
,_close
등의 시스템 콜 대체 함수 구현으로 Newlib에 연결됨 -
_sbrk
는 heap 메모리를_end
지점부터_stack_bottom
까지 확장하는 방식으로 동작
Application example: input and output
-
main
함수에서 printf, scanf 사용 가능, 입력값도 정상 처리됨 - echo는 지원하지 않지만
scanf
를 통해 문자열 입력을 받고 출력 가능 - 별도의 런타임을 구현하여 스택 초기화 및 BSS 섹션 zero-fill 수행 후
main
호출
Linker script
- 실행 시작 주소는
0x80000000
, 해당 위치에 런타임 코드 배치 -
.text
,.rodata
,.data
,.bss
순으로 메모리 배치하며, heap은_end
부터 stack 전까지로 설정 - stack은 64KB 고정 크기, 최상단 주소는
0x80000000 + 64MB
-
ASSERT
구문을 통해 heap과 stack 충돌 방지
The ‘gotcha’ moment
- 툴체인 설정 시
--with-cmodel=medany
를 사용해야 0x80000000 이상의 주소를 처리 가능한 기계어 명령 생성 가능 - C 라이브러리와 애플리케이션 코드가 주소 모델이 다르면 링크 에러 발생
Running the app
- Makefile을 통해 cross-compile 및 QEMU 실행 자동화 가능
-
-specs=nosys.specs
,-nostartfiles
,-T link.ld
옵션으로 Newlib 최소 설정 및 사용자 정의 런타임 사용 -
make debug
실행 시 QEMU 콘솔에서 UART로 입력과 출력이 정상적으로 작동됨 -
qemu_debug.log
를 통해 실제 명령어 트레이스를 확인할 수 있음
Conclusion
- 운영체제 없이도 printf, scanf, malloc 등 사용 가능한 구조를 Newlib으로 구현함
- Newlib의 빌딩 블록 기반 구조를 활용하여, 필요한 기능만 최소한으로 구현하는 것이 핵심 전략임
- 추후 파일 시스템, 메모리 관리 등 추가 기능 구현도 가능하며, 라이브러리 호환성을 유지하면서 Bare Metal에서도 재사용 가능
- 전체 프로젝트 결과물은 약 220KB로 비교적 작고 효율적인 수준임
GitHub 소스: popovicu/bare-metal-cstdlib