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.
Mục Lục
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ị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:
- Tiền xử lý (Preprocessing): Xử lý các chỉ thị tiền xử lý như
#includevà#define. - Biên dịch (Compilation): Chuyển đổi mã nguồn thành mã assembly.
- Hợp ngữ (Assembly): Chuyển đổi mã assembly thành mã đối tượng (object code).
- 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ì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 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 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 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 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 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 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" - Đường dẫn các header bắt đầu với
-
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ụ:-lmtương ứng vớilibm, thư viện math có sẵn trong hệ thống.
$ export LDFLAGS+=" -L./lib -lm" - Đường dẫn tới thư viện, được bắt đầu bằng
-
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
-vvà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.
