13P by neo 2일전 | ★ favorite | 댓글과 토론
  • 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에 연결됨
  • _sbrkheap 메모리를 _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