Hệ điều hành & Tiến trình/Tiến trình & PCB — chương trình đang chạy khác gì file đĩa
9/28
Bài 9 / 28~12 phútTiến trình — sinh ra, sống, chếtMiễn phí lượt xem

Tiến trình & PCB — chương trình đang chạy khác gì file đĩa

Tiến trình khác chương trình thế nào, OS lưu gì trong PCB (register, PC, trạng thái) để tạm dừng rồi chạy tiếp, và địa chỉ riêng của mỗi tiến trình.

TL;DR: Một chương trình là file thực thi nằm trên đĩa — dữ liệu tĩnh, chết. Một tiến trình là chương trình đó đang chạy: có bộ nhớ riêng, register, program counter chỉ tới lệnh kế tiếp, và các tài nguyên OS cấp (file đang mở, PID). Cùng một chương trình chạy ba lần tạo ra ba tiến trình độc lập. Để có thể tạm dừng một tiến trình giữa chừng (khi hết lượt CPU) rồi chạy tiếp đúng chỗ sau đó, OS lưu toàn bộ trạng thái của nó trong một cấu trúc gọi là PCB (Process Control Block): register, PC, trạng thái, con trỏ tới không gian địa chỉ, bảng file mở. Mỗi tiến trình có một PCB; đây là "hồ sơ" OS dùng để quản lý sự sống của tiến trình.

Bạn mở ba tab terminal, mỗi tab chạy python. Đó là một chương trình (/usr/bin/python3 trên đĩa) nhưng ba tiến trình — mỗi cái có biến riêng, không thấy nhau. Bạn đang chạy một vòng lặp nặng thì hệ thống vẫn mượt, chuột vẫn nhích: OS liên tục tạm dừng tiến trình của bạn, cho tiến trình khác chạy vài mili-giây, rồi quay lại — mà vòng lặp của bạn không hề "biết" mình từng bị ngắt. Làm sao OS dừng một chương trình giữa chừng rồi chạy tiếp đúng chỗ, như thể chưa từng gián đoạn?

Xong bài, bạn explain được tiến trình khác chương trình ở đâu, và OS dùng cấu trúc PCB nào để chụp lại toàn bộ trạng thái một tiến trình — nền tảng để hiểu mọi thứ còn lại của module.

1. Analogy — công thức nấu ăn và bữa đang nấu

Một công thức trong sách là các bước tĩnh: nằm im, ai đọc cũng ra cùng nội dung, không tự làm gì. Khi bạn thật sự vào bếp nấu theo nó, bạn tạo ra một bữa đang nấu: có nồi cụ thể đang sôi, đang ở bước 3, tay cầm muỗng, nguyên liệu đã dùng một phần. Hai người bạn cùng nấu một công thức tạo ra hai bữa riêng — nồi riêng, tiến độ riêng, không ai đụng nồi ai.

Chương trình là công thức; tiến trình là bữa đang nấu. Và nếu có điện thoại reo, bạn ghi lại "đang ở bước 3, nồi đang sôi lửa nhỏ" để nghe xong quay lại đúng chỗ — mẩu giấy ghi đó chính là PCB.

Nấu ănTiến trình
Công thức trong sách (tĩnh)Chương trình (file trên đĩa)
Bữa đang nấu (nồi, bước, nguyên liệu)Tiến trình (bộ nhớ, PC, tài nguyên)
Hai người nấu cùng công thức → hai bữaChạy chương trình 2 lần → 2 tiến trình độc lập
"Đang ở bước 3, nồi đang sôi"Program counter + register
Mẩu giấy ghi để quay lại đúng chỗPCB (Process Control Block)
💡 Cách nhớ

Chương trình là danh từ (một thứ trên đĩa); tiến trình là động từ hoá (một hành động đang diễn ra). PCB là mẩu giấy OS ghi lại "tiến trình này đang làm tới đâu" để dừng rồi tiếp mà không mất dấu.

2. Chương trình và tiến trình khác nhau ở đâu?

Chương trình là một file thực thi trên đĩa — chuỗi byte gồm mã máy (đoạn .text), dữ liệu khởi tạo (.data), và metadata (định dạng ELF trên Linux). Nó tĩnh: copy đi đâu vẫn thế, không có "trạng thái đang chạy".

Tiến trình là một lần thực thi của chương trình đó. Khi bạn gõ ./app, OS nạp file vào bộ nhớ, dựng không gian địa chỉ, cấp một PID (Process ID — số định danh duy nhất), rồi cho CPU bắt đầu chạy. Từ giây phút đó, nó có những thứ mà file trên đĩa không có:

  • Không gian địa chỉ riêng: vùng code, dữ liệu, heap, stack — thay đổi liên tục khi chạy.
  • Register và program counter: giá trị đang tính, lệnh kế tiếp sẽ thực thi.
  • Tài nguyên OS cấp: file descriptor (số nguyên nhỏ OS cấp làm "tay cầm" tới một file/socket/pipe đang mở) đang mở, socket, PID, thông tin user sở hữu.
  • Trạng thái: đang chạy, sẵn sàng chạy, hay đang chờ (chi tiết ở bài Trạng thái & context switch).

Điểm mấu chốt: một chương trình sinh ra nhiều tiến trình. Mỗi python trong ví dụ đầu bài là một tiến trình riêng với PID riêng và bộ nhớ riêng.

// process-identity.c -- in ra PID de thay moi lan chay la mot tien trinh khac
#include <stdio.h>
#include <unistd.h>

int main(void) {
    printf("Chuong trinh giong nhau, PID khac nhau: %d\n", getpid());
    return 0;
}
Thử đoán

Biên dịch gcc process-identity.c -o app rồi chạy ./app hai lần liên tiếp. Hai lần in ra cùng một PID hay khác nhau? Vì sao?

Chạy hai lần in ra hai PID khác nhau: cùng một file app (một chương trình) nhưng mỗi lần gõ ./app là OS tạo một tiến trình mới, cấp một PID mới. File trên đĩa không đổi; hai tiến trình là hai thực thể sống độc lập.

Một file python3 trên đĩa sinh ra ba tiến trình độc lập với PID 4021, 4022 và 4023; mỗi tiến trình có không gian địa chỉ riêng, register và program counter riêng, PCB riêng trong kernel — nên biến toàn cục trùng tên ở ba tiến trình là ba ô nhớ khác nhau

3. PCB — hồ sơ OS giữ về mỗi tiến trình

Để quản lý hàng trăm tiến trình cùng lúc — tạm dừng cái này, chạy cái kia, rồi quay lại — OS cần nhớ mọi thứ về từng tiến trình. Nó lưu trong một cấu trúc gọi là Process Control Block (PCB): một PCB cho mỗi tiến trình, sống trong bộ nhớ kernel (user code không đọc trực tiếp được).

PCB chứa (nhóm chính):

NhómNội dungVì sao cần
Định danhPID, PID của cha (PPID), user sở hữuBiết tiến trình là ai, con của ai
CPU contextGiá trị các register, program counter (PC), stack pointerChụp "đang tính tới đâu" để tạm dừng rồi tiếp
Trạng tháiRunning / Ready / Blocked, mã exit khi kết thúcScheduler biết ai được chạy
Bộ nhớCon trỏ tới page table / không gian địa chỉBiết bộ nhớ ảo của tiến trình ánh xạ vào đâu
Tài nguyênBảng file descriptor đang mở, thư mục hiện hànhKhôi phục đúng ngữ cảnh I/O
Kế toánThời gian CPU đã dùng, priorityĐiều phối công bằng

Trên Linux, cấu trúc thật đóng vai PCB là struct task_struct trong kernel (một struct lớn với hàng trăm field). Ta không cần nhớ tên từng field — điều cần hiểu là phân loại thông tin: định danh, CPU context, trạng thái, bộ nhớ, tài nguyên.

Vì sao PCB cho phép tạm dừng rồi chạy tiếp

Khi tiến trình hết lượt CPU, OS lưu toàn bộ register và program counter hiện tại vào PCB của nó, rồi nạp register+PC của tiến trình kế tiếp từ PCB của tiến trình đó. Vì PC được cất và khôi phục chính xác, khi tiến trình cũ được chạy lại, CPU tiếp tục đúng lệnh mà nó bị ngắt — không sai một byte. Đó là lý do vòng lặp của bạn "không biết" mình từng bị dừng. Chi phí của thao tác cất-nạp này (context switch) được bóc kỹ ở Module 3.

Năm nấc của một lần chuyển tiến trình: A đang chạy thì hết lượt CPU; kernel cất register và program counter của A vào PCB của A; kernel nạp register và program counter của B từ PCB của B; B chạy tiếp đúng lệnh nó từng bị ngắt; tới lượt A thì nạp lại từ PCB của A và A chạy tiếp không sai một byte

4. Vì sao mỗi tiến trình có không gian địa chỉ riêng?

Một tính chất quan trọng của tiến trình: không gian địa chỉ tách biệt. Địa chỉ 0x5000 trong tiến trình A và 0x5000 trong tiến trình B trỏ tới hai ô nhớ vật lý khác nhau. OS + MMU (Memory Management Unit) dịch địa chỉ ảo của mỗi tiến trình sang địa chỉ vật lý riêng thông qua page table riêng của nó — cơ chế bộ nhớ ảo bạn đã học ở khoá Bộ nhớ.

Hệ quả:

  • Cô lập lỗi: tiến trình A ghi bậy vào con trỏ của nó không làm hỏng bộ nhớ tiến trình B. Một tab python crash không kéo theo tab khác.
  • Bảo mật: A không đọc lén được mật khẩu B đang giữ trong RAM (trừ khi qua kênh IPC hợp lệ — xem Module 4).
  • Đơn giản hoá lập trình: mỗi chương trình viết như thể sở hữu toàn bộ bộ nhớ, không phải né địa chỉ của chương trình khác.
Tien trinh A                 Tien trinh B
+-----------------+          +-----------------+
| dia chi ao 0x5000|         | dia chi ao 0x5000|
+--------+--------+          +--------+--------+
         |  page table A              |  page table B
         v                            v
   RAM vat ly 0x9A00            RAM vat ly 0x1C00
   (hai o nho KHAC nhau -- khong dung cham)

Đây là điểm phân biệt cốt lõi giữa tiến trình (không gian địa chỉ riêng) và thread (chia sẻ không gian địa chỉ) — trục chính của bài Thread vs process.

5. Pitfall tổng hợp

Nhầm 1 — "chạy chương trình 2 lần thì chúng chia sẻ biến":

// Chay ./counter hai lan cung luc, moi ban tang bien 'count' rieng
static int count = 0;   // moi tien trinh co ban sao RIENG cua count

✅ Mỗi lần chạy là một tiến trình với không gian địa chỉ riêng. Biến count của tiến trình này và tiến trình kia là hai ô nhớ khác nhau; tăng cái này không ảnh hưởng cái kia. Muốn hai tiến trình chia sẻ dữ liệu, phải dùng IPC (shared memory, pipe, socket — Module 4), không phải biến thường.

Nhầm 2 — "PID gắn liền với chương trình":

✅ PID gắn với tiến trình, không với chương trình. Cùng file app chạy 3 lần có 3 PID khác nhau. Sau khi tiến trình chết và được cha wait (reap), PID của nó mới được OS thu hồi và có thể cấp lại cho tiến trình mới sau này — nên đừng giả định "PID 1234 luôn là app của tôi".

Nhầm 3 — "PCB nằm trong bộ nhớ của tiến trình":

✅ PCB sống trong bộ nhớ kernel, không trong không gian địa chỉ user của tiến trình. Nếu PCB nằm trong vùng nhớ user, một tiến trình lỗi có thể tự sửa trạng thái/priority của mình — phá cô lập và bảo mật. User code chỉ truy cập thông tin PCB gián tiếp qua system call (getpid, getppid, đọc /proc/<pid>/).

6. 📚 Deep Dive

📚 Đào sâu (tuỳ chọn)

Nguồn chuẩn:

  • getpid(2) — man7.org — system call trả PID của tiến trình gọi; getppid() trả PID của cha. Đây là cách chuẩn để tiến trình tự biết định danh của mình.
  • proc(5) — man7.org — filesystem ảo /proc/<pid>/ phơi bày thông tin từ PCB kernel ra không gian user: status, stat, fd/ (file đang mở), maps (không gian địa chỉ). Đây là "cửa sổ" đọc PCB mà không cần viết code kernel.
  • OSTEP — chương "The Abstraction: The Process" (file cpu-intro.pdf) — giải thích khái niệm process, machine state (memory, register, PC), và process API ở mức nền tảng.

Ghi chú: Trên Linux, cấu trúc kernel đóng vai PCB là task_struct (khai báo trong include/linux/sched.h). Bạn không cần đọc source kernel để hiểu bài này — chỉ cần nắm loại thông tin PCB giữ. Muốn "nhìn" PCB thực tế, chạy cat /proc/self/status để xem OS đang giữ gì về chính tiến trình cat đó.

7. Liên hệ các bài khác

  • fork, exec, wait — vòng đời một tiến trình: bài kế dùng chính khái niệm tiến trình + PCB ở đây để giải thích fork nhân đôi một tiến trình (chép PCB + không gian địa chỉ) như thế nào.
  • System call là gì: getpid, fork, exec đều là system call — cách duy nhất user code nhờ kernel thao tác trên tiến trình.
  • Thread vs process: thread dùng chung không gian địa chỉ của tiến trình — đối lập trực tiếp với "mỗi tiến trình một không gian riêng" ở section 4.
  • Trạng thái & context switch: đào sâu thao tác cất-nạp CPU context vào/ra PCB và chi phí thật của nó.

8. Tóm tắt

  • Mỗi tiến trình có một PID duy nhất, nhưng PID được thu hồi và tái sử dụng sau khi chết — đừng giữ PID cũ rồi giả định nó vẫn là tiến trình cũ.
  • PCB lưu sáu nhóm: định danh (PID, PPID), CPU context (register + PC), trạng thái lập lịch, bộ nhớ (page table pointer), tài nguyên (fd, signal), kế toán (thời gian CPU, số lỗi trang).
  • Không gian địa chỉ ảo riêng là ranh giới phân biệt tiến trình với thread — thread cùng process chung address space, tiến trình khác nhau thì không.
  • PCB nằm trong bộ nhớ kernel — user code chỉ đọc gián tiếp qua system call (getpid) hoặc /proc/<pid>/status.

9. Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Bạn chạy cùng một file thực thi ./server hai lần cùng lúc. Đó là một hay hai tiến trình? Chúng có chia sẻ biến toàn cục không? Giải thích bằng khái niệm không gian địa chỉ.
  2. Q2
    PCB lưu những nhóm thông tin gì, và vì sao nhờ nó mà OS tạm dừng một tiến trình rồi chạy tiếp đúng chỗ được?
  3. Q3
    Vì sao PCB được đặt trong bộ nhớ kernel thay vì trong không gian địa chỉ user của tiến trình?
  4. Q4
    Một tiến trình đang chạy vòng lặp nặng, nhưng chuột và các app khác vẫn phản hồi mượt. Điều này liên quan gì tới PCB?
  5. Q5
    Bạn thấy PID 4821 là app của mình lúc 10 giờ sáng. Chiều bạn lại thấy PID 4821 nhưng là một tiến trình khác. Có mâu thuẫn không? Giải thích.

Bài tiếp theo: fork, exec, wait — vòng đời một tiến trình

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

fork, exec, wait — trace vòng đời một tiến trình Unix