# OS 없이 printf 구현하기 - Bare Metal 환경에서 C 표준 라이브러리 활용

> Clean Markdown view of GeekNews topic #20714. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=20714](https://news.hada.io/topic?id=20714)
- GeekNews Markdown: [https://news.hada.io/topic/20714.md](https://news.hada.io/topic/20714.md)
- Type: news
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-05-06T09:46:02+09:00
- Updated: 2025-05-06T09:46:02+09:00
- Original source: [popovicu.com](https://popovicu.com/posts/bare-metal-printf/)
- Points: 14
- Comments: 0

## Summary

Newlib 라이브러리를 이용하여 **RISC-V 기반 Bare Metal 환경**에서 운영체제 없이 **printf, scanf, malloc 등 C 표준 함수**를 구현하였습니다. **UART 드라이버, 메모리 할당 함수와 최소 시스템 콜**을 작성하고 이들을 Newlib에 연결하여 표준 입출력과 동적 메모리 할당을 지원하였습니다. 크로스 컴파일을 위해 **RISC-V GCC 툴체인, 전용 링크 스크립트, QEMU 실행 환경**을 직접 구성하였습니다. 전체 프로젝트 결과물은 약 **220KB의 효율적인 크기**를 보이며, Bare Metal에서도 C 표준 라이브러리 호환성을 유지합니다.

## Topic Body

- **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](https://github.com/popovicu/bare-metal-cstdlib)

## Comments



_No public comments on this page._
