14 điểm bởi GN⁺ 2025-05-06 | Chưa có bình luận nào. | Chia sẻ qua WhatsApp
  • Giới thiệu cách triển khai để có thể sử dụng các hàm chuẩn C bao gồm printf ngay cả khi không có hệ điều hành bằng cách tận dụng thư viện Newlib
  • Trong môi trường Bare Metal dựa trên kiến trúc RISC-V, tự triển khai driver UART và các hàm cấp phát bộ nhớ rồi kết nối chúng với Newlib
  • Chỉ với việc triển khai tối thiểu các hàm system call như _write, _sbrk, _close cũng có thể sử dụng các tính năng nâng cao như printf
  • Hướng dẫn cách tạo toolchain dựa trên Newlib cùng với RISC-V GCC toolchain, tự động build và viết linker script
  • Kết quả là đã xây dựng thành công môi trường printf với xuất UART, nhập scanf, và cả cấp phát bộ nhớ động hoạt động đầy đủ

Software abstractions and C standard library

  • Trong OS thông thường, khi gọi printf sẽ có nhiều tầng trừu tượng như system call của kernel, tầng terminal, render font... cùng hoạt động
  • Trong môi trường Bare Metal, cần điều khiển vào/ra trực tiếp mà không có hệ điều hành, vì vậy phải tự triển khai driver
  • Newlib cung cấp cấu hình có thể mở rộng bằng cách chỉ triển khai chức năng tối thiểu thay vì toàn bộ thư viện chuẩn C

Newlib concept

  • printf về mặt nội bộ được xây dựng dựa trên các hàm primitive đơn giản như _write
  • Ban đầu Newlib định nghĩa mọi hàm ở dạng dummy, và nếu chỉ triển khai phần cần thiết thì phần còn lại có thể dùng giá trị mặc định
  • Chỉ cần lập trình viên triển khai các hàm cần thiết là có thể linh hoạt sử dụng tính năng của thư viện C

Cross-compilation toolchain

  • Để cross-compile từ x86_64/Linux → RISC-V, cần build trực tiếp từ mã nguồn GCC
  • Thiết lập toolchain với Newlib làm thư viện C mặc định để có thể build binary cho RISC-V

Toolchain details

  • Khi build toolchain sử dụng các tùy chọn --prefix, --enable-multilib, --disable-gdb, --with-cmodel=medany
  • medany là thiết lập cho phép truy cập vùng nhớ địa chỉ cao trên RISC-V
  • Sau khi build xong, có thể sử dụng cross-compiler và thư viện Newlib tại đường dẫn /opt/riscv-newlib

Implementing the memory and UART building blocks

  • Tự truy cập trực tiếp địa chỉ phần cứng 16550A UART trong môi trường QEMU để triển khai truyền và nhận ký tự
  • Kết nối với Newlib bằng cách triển khai các hàm thay thế system call như _write, _sbrk, _close
  • _sbrk hoạt động theo cách mở rộng bộ nhớ heap từ vị trí _end đến _stack_bottom

Application example: input and output

  • Trong hàm main có thể sử dụng printf, scanf, và giá trị nhập vào cũng được xử lý bình thường
  • Không hỗ trợ echo, nhưng vẫn có thể nhận chuỗi bằng scanf rồi in ra
  • Tự triển khai runtime riêng để khởi tạo stack, zero-fill vùng BSS rồi sau đó gọi main

Linker script

  • Địa chỉ bắt đầu thực thi là 0x80000000, và mã runtime được đặt tại vị trí này
  • Bố trí bộ nhớ theo thứ tự .text, .rodata, .data, .bss, và heap được đặt từ _end đến trước stack
  • Stack có kích thước cố định 64KB, địa chỉ đỉnh là 0x80000000 + 64MB
  • Dùng câu lệnh ASSERT để ngăn heap và stack va chạm nhau

The ‘gotcha’ moment

  • Khi cấu hình toolchain, cần dùng --with-cmodel=medany để tạo ra mã máy có thể xử lý các địa chỉ từ 0x80000000 trở lên
  • Nếu mô hình địa chỉ của thư viện C và mã ứng dụng khác nhau thì sẽ phát sinh lỗi link

Running the app

  • Có thể tự động hóa cross-compile và chạy QEMU thông qua Makefile
  • Dùng các tùy chọn -specs=nosys.specs, -nostartfiles, -T link.ld để áp dụng cấu hình Newlib tối thiểu và runtime tự định nghĩa
  • Khi chạy make debug, nhập và xuất qua UART hoạt động bình thường trên console QEMU
  • Có thể kiểm tra trace lệnh thực tế thông qua qemu_debug.log

Conclusion

  • Đã triển khai bằng Newlib một cấu trúc có thể dùng printf, scanf, malloc... mà không cần hệ điều hành
  • Điểm mấu chốt là tận dụng cấu trúc kiểu building block của Newlib để chỉ triển khai tối thiểu những chức năng cần thiết
  • Về sau cũng có thể bổ sung thêm các chức năng như hệ thống file, quản lý bộ nhớ..., đồng thời vẫn giữ được tính tương thích thư viện để tái sử dụng trong môi trường Bare Metal
  • Toàn bộ kết quả dự án có dung lượng khoảng 220KB, tương đối nhỏ gọn và hiệu quả

Mã nguồn GitHub: popovicu/bare-metal-cstdlib

Chưa có bình luận nào.

Chưa có bình luận nào.