Compiler, Cross Compiler và Ứng Dụng trong Hệ Thống Nhúng

Compiler và Cross Compiler là những công cụ không thể thiếu trong quá trình phát triển phần mềm, đặc biệt là đối với các hệ thống nhúng. Bài viết này sẽ giúp bạn hiểu rõ hơn về hai khái niệm này, cũng như vai trò quan trọng của chúng trong việc tạo ra các ứng dụng cho các thiết bị phần cứng khác nhau.

Compiler (Trình Biên Dịch)

Compiler, hay trình biên dịch, là một chương trình máy tính có khả năng chuyển đổi mã nguồn được viết bằng một ngôn ngữ lập trình (như C, C++, Java) sang một ngôn ngữ khác, thường là ngôn ngữ máy (machine code) mà máy tính có thể trực tiếp hiểu và thực thi. Nói một cách đơn giản, compiler giúp “dịch” ngôn ngữ lập trình mà con người dễ hiểu thành ngôn ngữ mà máy tính có thể thực hiện. Nhờ có compiler, chúng ta có thể viết code bằng ngôn ngữ cấp cao và sau đó chuyển đổi nó thành các lệnh mà vi xử lý có thể thực thi. Trong trường hợp lập trình vi điều khiển (MCU), compiler sẽ dịch file .c thành file .hex hoặc .bin để nạp vào MCU.

Quá trình biên dịch diễn ra như thế nào?

Sơ đồ quá trình biên dịchSơ đồ quá trình biên dịch

Quá trình biên dịch bao gồm nhiều bước phức tạp, nhưng có thể tóm gọn như sau:

  1. Tiền xử lý (Preprocessing): Xử lý các chỉ thị tiền xử lý như #include#define.
  2. Biên dịch (Compilation): Chuyển đổi mã nguồn thành mã assembly.
  3. Hợp ngữ (Assembly): Chuyển đổi mã assembly thành mã đối tượng (object code).
  4. Liên kết (Linking): Kết hợp các mã đối tượng và thư viện để tạo thành một file thực thi hoàn chỉnh.

Các bước thực thi chương trìnhCác bước thực thi chương trình

Trong các IDE (Integrated Development Environment) như Keil C, quá trình biên dịch thường được thực hiện một cách tự động khi bạn nhấn nút “Build” hoặc “Run”. Tuy nhiên, phía sau những nút bấm đơn giản này là cả một chuỗi các hoạt động phức tạp diễn ra theo các bước đã nêu trên.

Cross Compiler (Trình Biên Dịch Chéo) và Toolchain

Cross Compiler, hay còn gọi là Toolchain, là một loại compiler đặc biệt cho phép biên dịch mã nguồn trên một nền tảng (ví dụ: máy tính chạy chip Intel) để tạo ra file thực thi có thể chạy trên một nền tảng khác (ví dụ: chip ARM). Điều này rất quan trọng trong lĩnh vực phát triển hệ thống nhúng, nơi mà việc phát triển phần mềm thường được thực hiện trên máy tính mạnh mẽ, sau đó triển khai trên các thiết bị phần cứng có tài nguyên hạn chế. Ví dụ, bạn có thể sử dụng một máy tính chạy Ubuntu để biên dịch ra một file image có thể chạy trên Raspberry Pi.

Ví dụ về Cross CompilerVí dụ về Cross Compiler

Các đối tượng liên quan đến Cross Compiler/Toolchain

Quá trình tạo và sử dụng Cross Compiler/Toolchain liên quan đến ba đối tượng chính:

  • Build: Hệ thống tạo ra toolchain. Thường là các máy tính sử dụng chip Intel và hệ điều hành Linux hoặc Windows.
  • Host: Hệ thống chạy toolchain để biên dịch mã nguồn. Host thường giống với Build, là các máy tính dùng chip Intel và Windows hoặc Linux.
  • Target: Hệ thống chạy chương trình được tạo ra bởi Host. Target thường là các máy tính nhúng dùng chip ARM, nhưng cũng có thể là một máy tính bình thường dùng chip Intel.

Mô hình GCC Cross CompilerMô hình GCC Cross Compiler

Các thành phần của Cross Compiler

Cross Compiler bao gồm nhiều thành phần khác nhau, phối hợp với nhau để thực hiện quá trình biên dịch chéo:

Các thành phần của ToolchainCác thành phần của Toolchain

  • Binutils: Tập hợp các công cụ để tạo và quản lý file nhị phân (.bin) cho target CPU.
    • as: Assembler, tạo ra mã nhị phân từ assembler source code.
    • ld: Trình liên kết (linker).
    • ar, ranlib: Tạo ra file nén .a, được sử dụng như thư viện.
    • objdump, readelf, size, nm, strings: Phân tích file nhị phân.
    • strip: Loại bỏ các phần thừa trong file nhị phân để giảm kích thước.
  • C/C++ Library: Thư viện được dùng làm interface giữa applications và kernel, cung cấp các C API chuẩn để dễ dàng phát triển ứng dụng. Một số thư viện phổ biến bao gồm: glibc, uClibc, eglibc, dietlibc, newlib,…

C LibraryC Library

  • Kernel header: Cung cấp các API cần thiết cho Applications và C Library giao tiếp với Kernel.

Kernel HeaderKernel Header

  • GCC compiler: Trình biên dịch trong hệ thống Linux, hỗ trợ nhiều ngôn ngữ và kiến trúc CPU khác nhau như ARM, MIPS, PowerPC, SuperH, x86.
    • gcc, c++, g++: Compiler cho các ngôn ngữ C và C++.
  • GDB Debugger: Trình gỡ rối, hỗ trợ quá trình phát hiện lỗi khi phát triển ứng dụng.

Để sử dụng cross-compiler, bạn cần cài đặt các biến môi trường để compiler có thể hoạt động chính xác. Ví dụ:

$ export PATH=/path/to/compiler/bin:$PATH
$ export CROSS_COMPILE=arm-none-linux-gnueabi-
$ export CC=${CROSS_COMPILE}gcc
$ export CXX=${CROSS_COMPILE}g++
$ export CPP=${CROSS_COMPILE}cpp
$ export AR=${CROSS_COMPILE}ar
$ export AS=${CROSS_COMPILE}as
$ export LD=${CROSS_COMPILE}ld
$ export RANLIB=${CROSS_COMPILE}ranlib
$ export STRIP=${CROSS_COMPILE}strip

Ví dụ với GCC Compiler

Cài đặt GCC trên Ubuntu

Để bắt đầu, hãy cài đặt GCC trên máy tính Ubuntu của bạn.

Kiểm tra phiên bản GCC/G++ hiện có và cài đặt nếu chưa có:

$ gcc -version
$ g++ -version
$ sudo apt-get install gcc g++

Ví dụ

Xét một chương trình C đơn giản tính căn bậc hai của 4:

#include <stdio.h>
#include <math.h>

int main(int argc, char **argv){
  double x;
  x = sqrt(4);
  printf("x = %f n", x);
  return 1;
}

Lưu đoạn code trên vào file main.c và thực hiện các lệnh sau trên Ubuntu:

$ export CFLAGS="-I./include -DDEBUG -Wall -g"
$ export LDFLAGS+=" -L./lib -lm"
$ gcc -c main.c ${CFLAGS} #tạo file object từ source
$ gcc -o prog main.o ${LDFLAGS} #tạo file chương trình nhị phân từ file object
$ ./prog #chạy chương trình

Kết quả

Kết quả chạy chương trình GCCKết quả chạy chương trình GCC

Giải thích

Đây là một ví dụ cơ bản về cách sử dụng GCC.

  • CFLAGS: C compiler flags, đưa các options vào trong compiler để thực hiện quá trình compile source code thành object. Bao gồm các thông tin:

    • Đường dẫn các header bắt đầu với -I, ví dụ -I./include.
    • Các define được bắt đầu với -D, ví dụ -DDEBUG để define DEBUG.
    • Các option đặc biệt khác của compiler như -g để bật chức năng debug gdb của gcc compiler, -Wall để trace các cảnh báo (warning) trong quá trình compile.
    $ export CFLAGS="-I./include -DDEBUG -Wall -g"
  • LDFLAGS: Linker flags, dùng trong quá trình linking các thư viện. Bao gồm các thông tin:

    • Đường dẫn tới thư viện, được bắt đầu bằng -L, ví dụ -L./lib.
    • Các thư viện bắt đầu với -l (viết tắt của lib), ví dụ: -lm tương ứng với libm, thư viện math có sẵn trong hệ thống.
    $ export LDFLAGS+=" -L./lib -lm"
  • gcc: Compiler cho C source và g++ là compiler cho C++ source.

Một số thông số quan trọng khác:

  • sysroot: Đường dẫn đến thư mục root (/). Khi tìm file header lúc compile, gcc sẽ dùng đường dẫn này làm base để tìm các file .h.
  • Tham số -print-sysroot để hiển thị sysroot.
  • Thêm -v vào câu lệnh biên dịch để thấy chính xác các tham số của gcc. Ví dụ: $ gcc -c main.c ${CFLAGS} -v

Kết luận

Bài viết đã cung cấp những kiến thức cơ bản về Compiler và Cross Compiler, cũng như vai trò của chúng trong quá trình phát triển phần mềm, đặc biệt là trong lĩnh vực hệ thống nhúng. Việc nắm vững các khái niệm này sẽ giúp bạn hiểu rõ hơn về quy trình biên dịch và cách thức tạo ra các ứng dụng cho các nền tảng phần cứng khác nhau.