Interrupt, trap & exception — ba con đường vào kernel
Phân biệt interrupt (phần cứng, bất đồng bộ), trap (chủ động — syscall), exception (lỗi — divide by zero, page fault) và cách kernel xử lý từng loại.
TL;DR: CPU rời user mode để vào kernel qua đúng ba con đường, phân biệt theo nguồn phát sinh. Interrupt đến từ phần cứng bên ngoài (đồng hồ, bàn phím, card mạng), bất đồng bộ — không liên quan lệnh đang chạy. Trap là chương trình chủ động vào kernel, đồng bộ — system call chính là một trap. Exception do chính lệnh đang chạy gây ra: chia cho 0, opcode sai, hay page fault. Interrupt và exception đi qua bảng IDT để tìm handler; syscall hiện đại dùng lệnh syscall riêng. Điểm tinh tế: nhiều exception (như page fault khi demand paging) là bình thường — kernel xử lý xong rồi chạy lại đúng lệnh đó, chương trình không hề biết.
Ở bài 02, bạn thấy syscall là cách chủ động chuyển từ user sang kernel mode. Nhưng đó chưa phải con đường duy nhất. Khi bạn gõ phím giữa lúc chương trình đang tính toán, CPU vẫn phải vào kernel để xử lý phím — dù chương trình chẳng gọi syscall nào. Khi code chia một số cho 0, CPU cũng vào kernel — dù bạn không hề muốn. Ba tình huống này — chủ động gọi, phần cứng báo, và lệnh gây lỗi — là ba lối vào kernel khác nhau về bản chất.
Bài này phân loại chúng theo ba trục (nguồn, đồng bộ hay không, kernel làm gì), giải thích vì sao page fault không phải lúc nào cũng là lỗi, và cho bạn tự tay phân loại vài sự kiện.
1. Analogy — ba kiểu "có việc" gọi quản lý toà nhà
Quay lại toà nhà ở bài 01, nơi ban quản lý (kernel) xử lý các sự cố. Có ba kiểu tình huống khiến một nhân viên (chương trình) phải nhờ tới ban quản lý:
- Chuông báo cháy reo (interrupt): tín hiệu đến từ bên ngoài, bất ngờ, không liên quan việc nhân viên đang làm. Ai đó ở tầng khác bấm chuông. Nhân viên phải tạm dừng, ban quản lý xử lý, rồi mọi người quay lại việc dở.
- Nhân viên chủ động gọi tổng đài (trap): nhân viên cố ý nhấc máy xin cấp lại thẻ. Đây là điều đã hẹn trước, có quy trình — chính là system call.
- Nhân viên làm hỏng thứ gì đó (exception): nhân viên vô tình làm kẹt máy photo, hoặc mở nhầm cửa khoá. Hành động của chính họ gây ra sự cố, buộc ban quản lý can thiệp.
| Toà nhà | Máy tính | Bản chất |
|---|---|---|
| Chuông cháy reo (từ bên ngoài) | Interrupt (timer, bàn phím, mạng) | Bất đồng bộ, từ phần cứng |
| Chủ động gọi tổng đài | Trap (system call) | Đồng bộ, cố ý |
| Tự làm kẹt máy / mở nhầm cửa | Exception (chia 0, page fault) | Đồng bộ, do lệnh gây ra |
Ba lối vào kernel khác nhau ở nguồn: interrupt đến từ bên ngoài (phần cứng), trap do chương trình chủ động gọi, exception do chính lệnh đang chạy gây ra. Trục thứ hai: chỉ interrupt là bất đồng bộ; trap và exception luôn gắn với một lệnh cụ thể.
2. Ba con đường vào kernel
Định nghĩa gọn từng loại:
- Interrupt (ngắt phần cứng): tín hiệu điện từ một thiết bị bên ngoài CPU — đồng hồ (timer), bàn phím, chuột, ổ đĩa báo "đọc xong", card mạng báo "có gói tới". Nó bất đồng bộ: đến vào bất kỳ thời điểm nào, không liên quan tới lệnh CPU đang chạy. CPU chạy nốt lệnh hiện tại rồi mới xử lý.
- Trap: một cú chuyển vào kernel do chương trình chủ động phát ra bằng một lệnh đặc biệt. System call là trap phổ biến nhất: bạn cố ý chạy
syscallđể nhờ kernel. Trap đồng bộ — xảy ra đúng tại lệnh phát ra nó, có thể đoán trước. - Exception (ngoại lệ): CPU phát hiện chính lệnh đang thực thi không hợp lệ hoặc cần kernel can thiệp — chia cho 0, opcode không tồn tại, truy cập trang nhớ chưa có mặt. Cũng đồng bộ (gắn với một lệnh cụ thể) nhưng không chủ ý — nó là hệ quả ngoài mong muốn của lệnh.
Cảnh báo về từ ngữ: Intel gọi một lớp con của exception là "trap" (loại báo cáo sau khi lệnh chạy xong, như breakpoint). Nhiều sách OS lại dùng "trap" cho mọi lối vào kernel đồng bộ (gồm cả syscall lẫn exception). Trong module này, để rõ ràng, ta dùng: trap = cú vào kernel chủ động do syscall, tách riêng khỏi exception = do lệnh gây lỗi. Khi đọc tài liệu khác, luôn kiểm nghĩa tác giả dùng.
3. Cơ chế — ba trục phân loại
Dựng bảng so sánh theo ba trục độc lập: nguồn, đồng bộ hay bất đồng bộ, và kernel làm gì sau đó.
| Trục | Interrupt | Trap (syscall) | Exception |
|---|---|---|---|
| Nguồn | Thiết bị phần cứng bên ngoài | Chương trình chủ động | Chính lệnh đang chạy |
| Đồng bộ? | Bất đồng bộ (bất cứ lúc nào) | Đồng bộ (tại lệnh syscall) | Đồng bộ (tại lệnh gây lỗi) |
| Cố ý? | Không liên quan chương trình | Có, cố ý | Không, ngoài mong muốn |
| Ví dụ | Timer, bàn phím, gói mạng | read, write, open | Chia 0, page fault, opcode sai |
| Kernel làm gì | Phục vụ thiết bị, rồi trả về lệnh đang dở | Chạy handler syscall, trả kết quả | Sửa được → chạy lại lệnh; không → gửi signal (giết) |
| Lệnh sau khi về | Lệnh kế tiếp | Lệnh kế tiếp sau syscall | Thường chạy lại chính lệnh đó (nếu sửa được) |
Cả interrupt và exception đều dùng chung một cơ chế tra bảng: CPU có IDT (Interrupt Descriptor Table) — mảng ánh xạ mỗi số vector (0 = chia 0, 14 = page fault, 32+ = các IRQ phần cứng...) tới địa chỉ handler. Khi sự kiện xảy ra, CPU tra IDT theo vector, chuyển kernel mode, nhảy vào handler.
System call x86-64 hiện đại thì không đi qua IDT: lệnh syscall nhảy thẳng tới một địa chỉ kernel đã ghi sẵn trong một thanh ghi đặc biệt gọi là MSR (Model-Specific Register) — đây là "đường nhanh" thay cho cách cũ int 0x80 (vốn có đi qua IDT). Đó là lý do bài 02 nói syscall tới "một điểm vào cố định".
flowchart TD
A["CPU dang chay user code"] --> B{"Su kien gi?"}
B -->|"Tin hieu tu thiet bi ngoai"| C["INTERRUPT<br/>tra IDT theo vector IRQ"]
B -->|"Lenh syscall (chu dong)"| D["TRAP<br/>nhay diem vao syscall"]
B -->|"Lenh gay loi (chia 0, page fault)"| E["EXCEPTION<br/>tra IDT theo vector loi"]
C --> K["Kernel mode: chay handler"]
D --> K
E --> K
K --> R{"Xong -> tra ve dau?"}
R -->|"Interrupt / Trap"| N["Lenh KE TIEP"]
R -->|"Exception sua duoc"| S["Chay LAI chinh lenh do"]
R -->|"Exception khong sua duoc"| X["Gui signal -> thuong giet tien trinh"]4. Interrupt — bất đồng bộ, từ phần cứng
Đặc trưng khiến interrupt khác hẳn hai loại kia: nó không liên quan lệnh nào đang chạy. Card mạng nhận một gói tin lúc 3 giờ 07 phút 22.000481 giây — đúng lúc CPU của bạn đang cộng hai số trong vòng lặp. Card phát tín hiệu interrupt; CPU chạy nốt lệnh cộng, lưu chỗ đang dở, rồi vào kernel chạy handler để nhận gói. Xong, nó quay lại lệnh kế tiếp trong vòng lặp như chưa có gì xảy ra.
Hai interrupt bạn đã gặp gián tiếp:
- Timer interrupt: đồng hồ phần cứng phát ngắt đều đặn (thường khoảng 100 tới 1000 lần mỗi giây, tuỳ cấu hình
CONFIG_HZ). Đây chính là thứ ở bài 01 cho phép kernel giành lại CPU từ một vòng lặp vô tận. Không có timer interrupt thì không có điều phối preemptive. - I/O completion: khi bạn
read()từ đĩa và tiến trình bị cho "ngủ" chờ dữ liệu, chính một interrupt từ ổ đĩa ("đọc xong rồi") sẽ đánh thức tiến trình dậy.
Vì interrupt bất đồng bộ, kernel phải xử lý cực nhanh và cẩn thận (không được giả định gì về trạng thái chương trình bị ngắt). Đây là lý do handler interrupt thường rất ngắn, đẩy phần việc nặng cho sau.
5. Page fault có phải luôn là lỗi?
Câu trả lời ngắn: không. Đây là hiểu nhầm phổ biến nhất về exception.
Page fault là exception vector 14: CPU cố truy cập một địa chỉ ảo mà MMU chưa ánh xạ tới trang vật lý. Có hai khả năng hoàn toàn khác nhau:
- Page fault "bình thường" (major/minor fault): trang hợp lệ nhưng chưa nạp — ví dụ demand paging (trang code chưa đọc từ đĩa), hay copy-on-write (trang dùng chung, giờ mới ghi lần đầu). Kernel lặng lẽ nạp/tạo trang, cập nhật page table, rồi chạy lại đúng lệnh vừa fault. Chương trình không hề biết mình vừa fault — chỉ thấy hơi chậm một chút. Bạn đã học cơ chế này ở Course 2 — Page fault & swap.
- Page fault "lỗi thật" (segfault): truy cập địa chỉ không hợp lệ — con trỏ NULL, con trỏ hỏng, ghi vào vùng chỉ đọc. Kernel không có cách sửa, nên gửi
SIGSEGV; mặc định giết tiến trình với dòngSegmentation fault.
Cùng một cơ chế phần cứng (exception vector 14), hai kết cục trái ngược — điều quyết định là kernel xử lý được hay không. Đây là minh hoạ đẹp cho dòng "Exception sửa được → chạy lại lệnh" trong bảng section 3.
Với exception không sửa được, ví dụ chia số nguyên cho 0:
// divzero.c
#include <stdio.h>
int main(void) {
int z = 0;
int x = 10 / z; // chia nguyen cho 0 -> exception #DE
printf("%d\n", x);
return 0;
}
Trước khi đọc output: chương trình này in ra gì? Tiến trình có chạy tới printf không, hay chết trước? Nếu chết, kernel gửi tín hiệu nào?
$ gcc divzero.c -o divzero && ./divzero
Floating point exception (core dumped)
Lệnh chia gây exception #DE (Divide Error), kernel gửi SIGFPE, tiến trình chết (phần core dumped chỉ hiện nếu hệ thống bật core dump qua ulimit -c). (Tên thông báo nhắc "floating point" nhưng đây là phép chia số nguyên — một cái tên lịch sử gây nhầm.)
6. Thử sức — tự phân loại
Trước khi đọc đáp án, hãy tự xếp mỗi sự kiện sau vào interrupt / trap / exception, và đoán CPU sẽ quay về lệnh kế tiếp hay chạy lại lệnh cũ:
- Chương trình gọi
open("data.txt", O_RDONLY). - Bạn di chuột trong lúc video đang phát.
- Code dereference một con trỏ NULL.
- Tiến trình chạm trang stack chưa được cấp, nhưng địa chỉ hợp lệ (stack đang lớn ra).
- Đồng hồ hệ thống phát ngắt định kỳ để scheduler chạy.
Đáp án:
| # | Sự kiện | Loại | Quay về đâu |
|---|---|---|---|
| 1 | open(...) | Trap (syscall) | Lệnh kế tiếp |
| 2 | Di chuột | Interrupt (chuột) | Lệnh kế tiếp (chương trình chạy tiếp) |
| 3 | Dereference con trỏ NULL | Exception (page fault, không sửa được) | Không về — SIGSEGV giết tiến trình |
| 4 | Trang stack hợp lệ chưa cấp | Exception (page fault, sửa được) | Chạy lại chính lệnh đó sau khi cấp trang |
| 5 | Timer định kỳ | Interrupt (timer) | Lệnh kế tiếp (hoặc context switch sang tiến trình khác) |
Chú ý cặp 3 và 4: cùng là page fault nhưng một cái giết tiến trình, một cái trong suốt — đúng như section 5.
7. Pitfall — những hiểu nhầm thường gặp
❌ Nhầm 1 — "Exception luôn nghĩa là chương trình có bug."
✅ Không. Page fault do demand paging hay copy-on-write là exception xảy ra liên tục trong mọi chương trình khoẻ mạnh; kernel xử lý và chạy lại lệnh, không ai chết. Chỉ exception không sửa được mới thành crash.
❌ Nhầm 2 — "System call và interrupt là một, đều 'ngắt' CPU vào kernel."
✅ Khác nguồn và khác tính đồng bộ. System call (trap) do chương trình chủ động phát, đồng bộ, đoán trước được. Interrupt do phần cứng bên ngoài phát, bất đồng bộ, tới bất kỳ lúc nào không liên quan chương trình. Gộp chúng làm một là mất đi điều quan trọng nhất: ai gây ra và khi nào.
❌ Nhầm 3 — "Sau khi kernel xử lý xong, CPU luôn chạy lệnh kế tiếp."
✅ Với interrupt và trap thì đúng — về lệnh kế tiếp. Nhưng exception sửa được (page fault bình thường) thì kernel chạy lại chính lệnh vừa fault, vì lệnh đó chưa hoàn thành. Nếu chạy lệnh kế tiếp, thao tác bộ nhớ ban đầu sẽ bị bỏ lỡ.
8. 📚 Deep Dive
Spec / tài liệu tham khảo:
- OSTEP — Chapter 6: Limited Direct Execution — mục "Handling Interrupts" và bàn về timer interrupt cho preemption; nền tảng cho module scheduler sau.
- signal(7) — Linux man page — bảng tín hiệu:
SIGSEGV(truy cập nhớ sai),SIGFPE(lỗi số học),SIGILL(opcode sai) — chính là các signal kernel gửi khi exception không sửa được. - Intel SDM, Vol. 3, Chapter 6: Interrupt and Exception Handling — bảng vector đầy đủ (0 =
#DE, 14 =#PF...) và phân loại fault/trap/abort của Intel. Tra khi cần chi tiết x86.
Ghi chú: OSTEP đủ cho ý tưởng OS-level; man page signal(7) cho ánh xạ exception → signal trên Linux; Intel SDM cho phân loại phần cứng chính xác.
9. Liên hệ các bài khác
- Bài 02 — System call là gì: system call là con đường trap — một trong ba lối vào; bài này đặt nó cạnh interrupt và exception.
- Bài 01 — Kernel mode vs user mode: timer interrupt là thứ cho kernel giành lại CPU; cái
#GPkhi chạy lệnh đặc quyền ở bài 01 chính là một exception. - Bài 04 — Đọc syscall bằng strace:
stracecũng hiện các signal (nhưSIGSEGV) mà exception sinh ra, không chỉ syscall. - Course 2 — Page fault & swap: cơ chế đầy đủ của page fault "bình thường" — vì sao nó không phải lỗi.
10. Tóm tắt
- CPU vào kernel qua ba con đường, phân biệt theo nguồn: interrupt (phần cứng ngoài), trap (syscall — chủ động), exception (lệnh gây lỗi).
- Trục đồng bộ: chỉ interrupt là bất đồng bộ (tới bất kỳ lúc nào); trap và exception luôn gắn với một lệnh cụ thể.
- Interrupt và exception tra bảng IDT (vector → handler); syscall x86-64 hiện đại dùng lệnh
syscallriêng, không qua IDT. - Sau xử lý: interrupt và trap về lệnh kế tiếp; exception sửa được thì chạy lại chính lệnh vừa fault.
- Page fault không phải luôn là lỗi: demand paging / copy-on-write là page fault bình thường, kernel xử lý trong suốt; chỉ truy cập không hợp lệ mới thành
SIGSEGV. - Exception không sửa được → kernel gửi signal:
SIGSEGV(nhớ sai),SIGFPE(chia 0),SIGILL(opcode sai) — thường giết tiến trình.
11. Tự kiểm tra
Q1Vì sao interrupt được gọi là 'bất đồng bộ' còn exception và trap là 'đồng bộ'? Cho một ví dụ cho thấy khác biệt này quan trọng thế nào.▸
Đồng bộ nghĩa là sự kiện gắn chặt với một lệnh cụ thể và có thể tái lập: chạy lại lệnh đó sẽ lại gây ra nó. Trap (lệnh syscall) và exception (lệnh chia cho 0) đều vậy — chúng xảy ra tại một lệnh xác định. Bất đồng bộ nghĩa là sự kiện tới từ bên ngoài, không liên quan lệnh nào đang chạy và có thể đến vào bất kỳ thời điểm nào — như timer, bàn phím, gói mạng.
Khác biệt này quan trọng vì nó quyết định kernel giả định được gì. Với exception, kernel biết chính xác lệnh nào gây ra và có thể sửa rồi chạy lại. Với interrupt, kernel không được giả định gì về trạng thái chương trình bị ngắt — nó có thể đang ở giữa bất kỳ tính toán nào — nên handler interrupt phải cực kỳ cẩn thận và ngắn gọn.
Q2Hai lần dereference con trỏ đều gây page fault, nhưng một lần chương trình chạy tiếp bình thường còn một lần bị 'Segmentation fault'. Giải thích vì sao cùng cơ chế mà hai kết cục trái ngược.▸
SIGSEGV, mặc định giết tiến trình. Cùng cơ chế phần cứng, khác nhau ở chỗ kernel có xử lý được hay không.Q3Vì sao sau khi xử lý một interrupt hoặc trap, CPU quay về LỆNH KẾ TIẾP, còn sau một page fault sửa được thì CPU CHẠY LẠI chính lệnh cũ?▸
syscall đã hoàn thành nhiệm vụ của nó (nhờ kernel làm việc); quay về lệnh kế tiếp là đúng. Với interrupt, lệnh đang chạy cũng đã chạy xong trước khi CPU xử lý ngắt, nên tiếp tục từ lệnh kế tiếp. Nhưng với page fault sửa được, lệnh gây fault chưa hoàn thành — nó đang cố đọc/ghi một địa chỉ mà lúc đó chưa ánh xạ, nên thao tác bộ nhớ chưa thực sự xảy ra. Kernel chỉ làm cho địa chỉ đó dùng được (nạp/tạo trang), rồi phải để CPU thử lại chính lệnh đó để nó hoàn thành thao tác bộ nhớ. Nếu bỏ qua sang lệnh kế tiếp, thao tác đọc/ghi ban đầu sẽ bị mất.Q4Timer interrupt liên quan thế nào tới khả năng kernel dừng một vòng lặp vô tận (nhắc lại từ bài 01)?▸
cli) là lệnh đặc quyền, user code không chặn được timer. Khi vào kernel qua timer interrupt, scheduler có cơ hội quyết định cho tiến trình khác chạy (context switch). Nói cách khác, timer interrupt là cơ chế phần cứng đảm bảo kernel luôn giành lại được CPU — nền tảng của điều phối preemptive.Q5Khi đọc tài liệu, bạn thấy từ 'trap' được dùng với nghĩa khác nhau. Vì sao cần cẩn thận, và trong module này 'trap' nghĩa là gì?▸
Bài tiếp theo: Đọc syscall bằng strace — nhìn chương trình nói chuyện với OS
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
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