+1

Một chương trình C được chạy như thế nào trên hệ điều hành Unix

Để hiểu được một chương trình C chạy như thế nào trên hệ điều hành Unix (ví dụ như Linux hoặc MacOS), chúng ta sẽ đi qua 3 chủ đề:

  • Cách compile một source code C thành file binary (có thể chạy trực tiếp được từ hệ hiều hành - giống file .exe trên Windows)
  • Cách sắp xếp các phần cứng cơ bản của 1 máy tính
  • Thực hiện việc chạy file binary

Source code sẽ là một chương trình "hello, world" đơn giản viết bằng C:

#include <stdio.h>
int main()  
{
	printf("hello, world");
	return 0;
}

Tạo file thực thi binary

Như ta thấy trên biểu đồ, source code hello khi được compile bởi GCC compiler sẽ trải qua 4 giai đoạn trước khi trở thành file thực thi binary:

  • Preprocessor phase: Thêm các header, xoá comments...
  • Compiler phase: Kiểm tra lỗi chính tả, chuyển đổi ngôn ngữ C thành ngôn ngữ assembly
  • Assembler phase: Chuyển đổi assemby code thành binary, file này được gọi là "relocatable object program". Chương trình lúc này có thể được kết nối với code của các chương trình khác để chạy, ví dụ như chương trình printf.
  • Linker phase: Khác với Preprocessor Phase là chỉ thêm header (type) để kiểm tra lỗi chính tả, ở phase này sẽ thêm trực tiếp code của các thư viện và chương trình khác (implementation) để tạo thành 1 file thực thi binary duy nhất. Lúc này chúng ta sẽ có 1 file binary duy nhất là hello, file này có thể được chạy trực tiếp từ chương trình shell (bash) của hệ điều hành như sau:
./hello

Bố cục phần cứng

Chú ý: Mình sẽ giữ các từ khoá tiếng anh để các bạn tiện tra cứu trên google

Trước khi chạy chương trình hello , chúng ta sẽ đi sơ qua về cách phần cứng được sắp xếp trong một máy tính cơ bản.

Buses

Là tập hợp các ống dây dẫn điện để truyền thông tin dưới dạng bytes giữa các phần cứng. Bytes được vận chuyển theo đơn vị là word, chứa 4 bytes (hệ điều hành 32-bit) hoặc 8 bytes (hệ điều hành 64-bit) Dữ liệu được vận chuyển giữa các phần cứng thông qua buses. Có một trạm trung tâm là I/O bridge có trách nhiệm:

  • Vận chuyển dữ liệu đến và đi giữa CPU thông qua System Bus
  • Vận chuyển dữ liệu đến và đi giữa Main Memory thông qua Memory Bus.
  • Vận chuyển dữ liệu đến và đi giữa I/O devices thông qua I/O Bus.

I/O devices

Có 4 thiết bị I/O devices chính:

  • bàn phím với nhiệm vụ đầu vào
  • chuột với nhiệm vụ đầu vào
  • màn hình với nhiệm vụ đầu ra
  • ổ đỉa để lưu trữ dài hạn các dữ liệu và chương trình Dữ liệu sẽ được chuyển từ I/O devices đến các phần cứng khác như Main Memory, CPU thông qua Buses

Main Memory

Main Memory là thiết bị lưu trữ tạm thời (tắt điện là sẽ bị xoá) lưu giữ code chạy chương trình và dữ liệu. Các dữ liệu này sẽ được Processor sử dụng và thay đổi trong quá trình chạy chương trình. Về mặt vật lý, Main Memory chứa các chips bộ nhớ truy cập ngẫu nhiên động (dynamic random access memory - DRAM). Về mặt logic, Main Memory chưa các mảng bytes liên tiếp, mỗi byte sẽ có 1 địa chỉ duy nhất. Chúc ta có thể truy xuất và cập nhật nhanh mỗi byte dựa vào các địa chỉ này.

Processor (hay còn gọi là central processing unit - CPU)

Trong CPU, chúng ta có:

  • Program Counter (là một register đặc biệt): Lưu giữ các con trỏ tới các địa chỉ của code chương trình trong Main Memory . Program Counter sẽ đảm bảo việc thực thi tuần tự các dòng code.
  • Register File: Chứa các registers thông thường, kích cỡ của register là 1 word. Mỗi register sẽ lưu giữ dữ liệu được CPU tải về từ Main Memory hoặc dữ liệu sau khi đã tính toán từ ALU.
  • Arithmetic/Logic Unit (ALU): Tính toán các dữ liệu đầu vào từ registers và trả về kết quả lưu lại vào registers.

Chạy chương trình

Chúng ta đã có file thực thi binary và góc nhìn tổng quan về cách sắp xếp các phần cứng cơ bản. Đã đến lúc chạy chương trình hello. Để chạy chương trình, chúng ta gõ tên chương trình vào shell. Sau khi chạy ta sẽ thấy dòng chữ hello, world hiển thị trên màn hình.

./hello
hello, world

Đây là các bước mà chương trình sẽ được chạy:

  1. Ban đầu, trước khi chạy chương trình, shell sẽ chờ đợi người dùng gõ tên chương trình.
  2. Người dùng gõ "./hello" và bấm enter, thông báo với hệ thống là hãy chạy file thực thi hello
  3. Phần code binary của file hello sẽ được copy từ Disk lên Main Memory
  4. Trong chương trình, chúng ta thấy chỉ có 1 dòng hướng dẫn ở trong file hello , ( printf("hello, world") ). CPU sẽ lưu lại 1 con trỏ trên Program Counter, trỏ đến địa chỉ của dòng lệnh này trên Main Memory
  5. Khác với dòng lệnh chỉ được copy địa chỉ, với data thì sẽ copy trực tiếp. Data (hello, world) được copy từ Main Memory vào Register trong Register File
  6. Trong chương trình hello, chúng ta không có dòng lệnh nào về tính toán (a +b) hoặc bitwise (a ==b), vì vậy chúng ta không cần sự tham gia của ALU. CPU sẽ copy dữ liệu chuỗi hello, world từ Register và vận chuyển đến Display.
  7. Người dùng sẽ thấy chữ hello, world được in lên màn hình
  8. Chương trình kết thúc, shell chờ đợi lệnh tiếp theo từ người dùng

Đó là cách một chương trình hello đơn giản chạy trên hệ điều hành Unix (tiền thân của Linux và MacOS)

Cảm ơn các bạn đã chịu khó đọc đến đây.

Hình ảnh và nội dung được tham khảo từ cuốn sách Computer Systems: A Programmer's Perspective

Bài viết gốc trên blog của mình: https://www.anvo.dev/articles/how-does-a-c-program-run-on-a-unix-system


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí