Hướng dẫn chi tiết về cách chạy một chương trình C trên hệ điều hành Unix
Để hiểu rõ cách mà một chương trình C được thực thi trên hệ điều hành Unix (bao gồm cả Linux và MacOS), bài viết này sẽ trình bày ba chủ đề quan trọng:
- Cách biên dịch mã nguồn C thành tệp nhị phân thực thi (tương tự như tệp .exe trên Windows).
- Cấu trúc phần cứng cơ bản của máy tính.
- Quá trình thực thi tệp nhị phân.
Chúng ta sẽ thử nghiệm với mã nguồn đơn giản cho chương trình "hello, world" như sau:
c
#include <stdio.h>
int main()
{
printf("hello, world");
return 0;
}
Tạo tệp thực thi nhị phân
Như đã đề cập, mã nguồn hello
khi được biên dịch bằng trình biên dịch GCC
trải qua bốn giai đoạn trước khi trở thành tệp nhị phân thực thi:
- Giai đoạn tiền xử lý (Preprocessor): Thêm các header và loại bỏ các comment.
- Giai đoạn biên dịch (Compiler): Kiểm tra lỗi cú pháp và chuyển đổi mã C thành mã assembly.
- Giai đoạn lắp ráp (Assembler): Chuyển mã assembly thành mã nhị phân; tệp này gọi là "chương trình đối tượng di chuyển được" (relocatable object program). Chương trình có thể kết nối với mã của các chương trình khác, chẳng hạn như
printf
. - Giai đoạn liên kết (Linker): Bổ sung mã từ các thư viện và chương trình khác để tạo thành một tệp nhị phân duy nhất. Cuối cùng, chúng ta có tệp nhị phân duy nhất là
hello
, có thể chạy trực tiếp từ shell (bash
) của hệ điều hành:
./hello
Cấu trúc phần cứng cơ bản
Trước khi chạy chương trình hello
, chúng ta hãy xem xét cấu trúc bên trong của máy tính:
Các bus
Các bus là tập hợp các dây dẫn điện dùng để truyền dữ liệu dưới dạng bytes
giữa các thành phần phần cứng trong máy tính. Bytes
sẽ được vận chuyển theo đơn vị là word
, với 4 bytes trên hệ điều hành 32-bit hoặc 8 bytes trên hệ điều hành 64-bit.
Có ba loại bus chính trong máy tính:
- System Bus: Vận chuyển dữ liệu giữa
CPU
và các tài nguyên hệ thống khác. - Memory Bus: Chuyển dữ liệu giữa
Main Memory
vàCPU
. - I/O Bus: Vận chuyển dữ liệu đến và từ các thiết bị I/O.
Thiết bị I/O
Có bốn thiết bị I/O chính:
- Bàn phím (đầu vào)
- Chuột (đầu vào)
- Màn hình (đầu ra)
- Ổ đĩa (lưu trữ dài hạn)
Bộ nhớ chính (Main Memory)
Main Memory
là nơi lưu trữ tạm thời (dữ liệu sẽ bị mất khi tắt nguồn) mã chương trình và dữ liệu. Các chip bộ nhớ truy cập ngẫu nhiên động (DRAM) tạo nên bộ nhớ chính, cho phép truy xuất và sửa đổi dữ liệu nhanh chóng thông qua các địa chỉ byt.
Bộ xử lý (CPU)
Trong CPU
, các thành phần chính bao gồm:
- Program Counter: Lưu giữ các địa chỉ của mã chương trình trong
Main Memory
, đảm bảo thực thi tuần tự các dòng lệnh. - Register File: Chứa các thanh ghi thông thường; kích thước mỗi thanh ghi bằng 1
word
. - Arithmetic/Logic Unit (ALU): Thực hiện các phép toán trên dữ liệu từ các thanh ghi và lưu kết quả lại vào thanh ghi.
Quá trình chạy chương trình
Bây giờ, chúng ta đã có tệp nhị phân thực thi và hiểu về cấu trúc phần cứng cơ bản, đã đến lúc chạy chương trình hello
. Để thực hiện, người dùng chỉ cần nhập tên chương trình vào shell. Sau khi thực hiện, dòng chữ hello, world
sẽ được hiển thị trên màn hình:
./hello
hello, world
Dưới đây là quy trình cụ thể mà chương trình thực hiện:
- Khi bắt đầu, shell sẽ chờ đợi người dùng nhập tên chương trình.
- Người dùng gõ "./hello" và nhấn Enter, thông báo cho hệ thống chạy tệp nhị phân
hello
. - Mã nhị phân của tệp
hello
sẽ được sao chép từ ổ đĩa vàoMain Memory
. - CPU lưu địa chỉ dòng lệnh
printf
trongProgram Counter
. - Dữ liệu
hello, world
sẽ được chuyển từMain Memory
vàoRegister
. - Vì không có phép toán yêu cầu ALU, CPU sẽ truyền dữ liệu từ
Register
đến màn hình hiển thị. - Người dùng nhìn thấy dòng chữ
hello, world
trên màn hình. - Chương trình kết thúc và shell tiếp tục chờ lệnh tiếp theo từ người dùng.
Đó chính là quy trình chạy một chương trình đơn giản "hello" trên hệ điều hành Unix, tiền thân của Linux và MacOS. Cảm ơn bạn đã theo dõi bài viết này!
Nguồn tham khảo: Computer Systems: A Programmer's Perspective.
Bài viết gốc trên blog của mình.
source: viblo