- vừa được xem lúc

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

0 0 17

Người đăng: An Vo

Theo Viblo Asia

Để 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

Bình luận

Bài viết tương tự

- vừa được xem lúc

TẬP TRUNG VÀO MỘT VIỆC DUY NHẤT VÀ LÀM THẬT TỐT - TRIẾT LÝ HỆ ĐIỀU HÀNH UNIX

. Hệ điều hành Unix đã tồn tại trong nhiều thập kỷ và nó cũng như các hệ điều hành Linux là một phần quan trọng của thế giới máy tính. Hệ điều hành Unix được viết vào năm 1970 và là một trong những hệ điều hành đầu tiên viết bằng ngôn ngữ C.

0 0 20

- vừa được xem lúc

Tại sao Rails lại dùng cả Webpack lẫn Sprocket?

Khi Rails 6 được ra mắt, có thể bạn đã từng tự hỏi. WTF, sao Webpack đã được add vào rồi, mà Sprocket vẫn tồn tại thế kia . Chẳng phải Webpack và Sprocket được dùng để giải quyết chung một công việc hay sao. Hoặc cả đây:.

0 0 59

- vừa được xem lúc

Tìm hiểu chung về LLVM

Không hề khó khăn khi nhận thấy rằng các ngôn ngữ lập trình được tạo ra cũng như cải tiến với tốc độ ngày một cao. Rust của Mozilla, Swift của Apple hay Kotlin của Jetbrain và nhiều ngôn ngữ khác cung cấp cho các nhà phát triển một loạt các lựa chọn mới về tốc độ, độ an toàn, sự tiện lợi, tính di độ

0 0 30

- vừa được xem lúc

Sự khác nhau giữa trình biên dịch và trình thông dịch

Trình biên dịch là trình dịch chuyển đổi từ ngôn ngữ nguồn (các ngôn ngữ lập trình bậc cao) thành ngôn ngữ đối tượng (ví dự như ngôn ngữ máy). Ngược lại với trình biên dịch, trình thông dịch là một ch

0 0 46

- vừa được xem lúc

002: JVM Compiler với C1 Compiler và C2 Compiler

Bài viết nằm trong series Java memory management & performance. Một ví dụ dễ hiểu như sau, ta có một văn bản bằng tiếng Anh, và muốn nó được dịch sang ngôn ngữ tiếng Nhật để được thực thi.

0 0 46

- vừa được xem lúc

003: JVM Code cache và Ahead of Time Compiler

Bài viết nằm trong series Java memory management & performance. Bài viết hôm nay sẽ tìm hiểu kĩ hơn về JVM Code cache, tuning JVM Code cache size và AoT Compiler.

0 0 40