Skip to main content

Command Palette

Search for a command to run...

Tiến trình (Process) - Tổ chức bộ nhớ

Updated
5 min read

Tổ chức bộ nhớ của một tiến trình bao gồm các thành phần sau: Code/Text segment, Data Segment (.data và .rodata), bss, Heap và Stack và được sắp xếp trong không gian địa chỉ ảo của tiến trình theo thứ tự từ địa chỉ thấp lên địa chỉ cao.

  • Lưu ý: đây là tổ chức bộ nhớ của tiến trình bên trong bộ nhớ ảo, không phản ánh thực tế bên trong bộ nhớ chính (RAM), các địa chỉ này sẽ được đơn vị quản lý bộ nhớ (MMU - Memory Management Unit) ánh xạ lên địa chỉ thật ở trên RAM.
  1. Code segment (Text segment)

Đây là phần mã lệnh (Code/Text) sau khi chương trình được biên dịch sang mã máy. Hay chính là các câu lệnh chỉ thị cho CPU thực hiện.

Cùng xem câu lệnh ở bên dưới:

int x = 10;
x = x + 1;

“x = x + 1“ khi được biên dịch sẽ sinh ra mã máy, và mã máy đó được lưu trong Code/Text segment.

Đặc điểm quan trọng của phần Code/Text:

  • Đây là phần chỉ đọc (Read only) tránh cho chương trình tự sửa code

  • Nạp từ file thực thi vào RAM khi tạo process

  1. Data Segment

Data Segment là nơi lưu trữ các biến toàn cục (global) và biến tĩnh đã được khởi tạo khác 0.

Data bao gồm 2 vùng là .data.rodata

.data là vùng chứa biến có thể thay đổi (Read/Write)

.rodata là vùng chứa các hằng số (Read only)

Chúng ta cùng xem xét một chương trình đơn giản như sau:

int a = 10;
int b;
int c = 0;
const char* d = "abc";

int main() {
    int e = 100;
    const char* f = "abc";
    static int g = 20;
    static int h;
    static int i = 0;
    static const char* j = "abc";
    return 0;
}

a là biến toàn cục có khởi tạo khác 0 → a nằm trong Data Segment (vùng .data).

b là biến toàn cục không có khởi tạo và c là biến toàn cục khởi tạo bằng 0 → b và c không nằm trong Data Segment. (b và c nằm trong vùng .bss)

d là biến toàn cục có khởi tạo khác 0 → d nằm trong Data Segment (vùng .data), “abc“ là hằng số nằm trong vùng .rodata của Data Segment.

các biến e và f là biến cục bộ (local) của hàm main() → e và f không nằm trong Data Segment. (Nằm trong stack của hàm main() bên trong vùng Stack).

g là biến static có khởi tạo khác 0 → g nằm trong vùng .data của Data Segment.

h và i là biến static chưa được khởi tạo hoặc khởi tạo bằng 0 → h và i không nằm trong Data Segment (h và i nằm trong vùng .bss)

j là biến static có khởi tạo khác 0 → j nằm trong vùng .data của Data Segment, “abc“ sẽ nằm trong vùng .rodata của Data Segment.

  1. bss Segment

Bss là vùng bộ nhớ được hệ điều hành cấp phát khi khởi tạo process để lưu trữ các biến toàn cục (global) hoặc biến static chưa được khởi tạo hoặc khởi tạo bằng 0, mục đích là để giảm kích thước file thực thi.

  1. Heap

Heap là vùng bộ nhớ được cấp phát động trong quá trình thực thi tiến trình, ví dụ thực hiện hàm malloc() trong ngôn ngữ c hoặc toán tử new trong các ngôn ngữ bậc cao.

Chúng ta cùng xem xét ví dụ sau:

#include <stdlib.h>

int main() {
    // Cấp phát một mảng có kích thước là 400 bytes
    int* p = malloc(100 * sizeof(int));
    if(!p) return 1;

    // Giải phóng vùng nhớ trên Heap sau khi sử dụng
    free(p);
    p = NULL;

    return 0;
}

p la biến cục bộ của hàm main() → p nằm ở trong stack của hàm main() trong vùng Stack.

Vùng nhớ có kích thước 100 * sizeof(int) (400 bytes) nằm trong vùng Heap.

Thông thường các biến cục bộ của hàm nằm trên stack của hàm sẽ được giải phóng theo phạm vi { } của hàm, nhưng khi cấp phát vùng nhớ trong vùng Heap để sử dụng, cần được giải phóng sau khi sử dụng, vì các vùng nhớ này không được tự giải phóng theo phạm vi của hàm.

  1. Stack

Stack là vùng nhớ lưu các biến cục bộ, các tham số hàm và địa chỉ trả về của hàm.

Địa chỉ trả về của hàm là là địa chỉ của lệnh tiếp theo ngay sau lời gọi hàm.

Stack hoạt động theo nguyên lý LIFO (last in first out - vào sau ra trước)

Chúng ta cùng xem xét ví dụ sau:

int total(int a, int b) {
    return a + b;
}

int main() {
    int a = 2;
    int b = 3;
    int c = total(a, b);

    return 0;
}

Khi tiến trình được tạo, hàm main sẽ được cấp phát một vùng nhớ trong Stack (main stack frame), trong đó lưu các biến a, b, c và địa chỉ trả về của hàm main.

Khi hàm total được gọi, một stack frame mới cho total được tạo trong Stack (total stack frame), copy của các giá trị a và b và địa chỉ trả về của hàm total (địa chỉ của câu lệnh gán giá trị của total cho biến c trong hàm main) được đẩy vào total stack frame.

Sau khi hàm total thực hiện xong total stack frame sẽ được giải phóng khỏi Stack.

Sau khi gán giá trị của total cho biến c, hàm tiếp tục câu lệnh return 0, main stack frame sẽ được giải phóng khỏi Stack để chuẩn bị các câu lệnh kết thúc tiến trình.

Như vậy Stack hoạt động theo nguyên lý LIFO. Mỗi hàm được gọi sẽ tạo ra một stack frame mới và bị hủy khi hàm kết thúc.

Hiểu rõ tổ chức bộ nhớ của tiến trình giúp tránh lỗi memory, debug chính xác và viết chương trình hiệu quả hơn.

The End.

More from this blog

U40 Học Code - Lập trình & Hệ điều hành

11 posts