Bài time cho thấy khi real cao nhưng user+sys thấp, chương trình đang chờ — và một thủ phạm phổ biến là đĩa. Máy chậm rề, CPU vẫn rảnh, vì một tiến trình nào đó đang "quật" đĩa liên tục. Nhưng tiến trình nào, và nó thực sự chạm đĩa bao nhiêu? Linux cho câu trả lời qua /proc/<pid>/io. Có điều, cách đọc nó tiềm ẩn một cái bẫy: một tiến trình có thể có vẻ đọc rất nhiều mà thực ra không chạm đĩa chút nào — nhờ page cache. Bài này (phần 9 loạt Debug) chạy thật để đọc đúng I/O của tiến trình.
/proc//io: các cột và nghĩa
File /proc/<pid>/io cho vài bộ đếm I/O của tiến trình:
rchar/wchar: số byte đọc/ghi qua syscall (read/write) — kể cả dữ liệu lấy từ hoặc ghi vào RAM cache, chưa chắc chạm đĩa.read_bytes/write_bytes: số byte thật đọc từ / ghi xuống đĩa (tầng block) — đây là I/O đĩa thật.syscr/syscw: số lần gọiread()/write().
cat /proc/<pid>/io

Hình 1: /proc/<pid>/io cho rchar/wchar (qua syscall, gồm cả cache) và read_bytes/write_bytes (đĩa thật); bẫy là rchar cao không có nghĩa đĩa bận — có thể từ page cache; đo delta bằng cách đọc hai lần.
Đo thật: ghi 200MB, và bẫy page cache

Hình 2: Chạy thật — tiến trình ghi 200MB: wchar=209715200 (200MB qua syscall), syscw=200, write_bytes=209715200 (200MB xuống đĩa); đọc file trong cache: rchar=104885123 (~100MB) nhưng read_bytes=0 (từ page cache, không chạm đĩa); delta write_bytes 0→157286400 (~150MB).
- Ghi thật: tiến trình ghi 200MB cho
wchar=write_bytes= 209715200 (200MB), vàsyscw=200(200 lầnwrite1MB). Cả byte-qua-syscall lẫn byte-xuống-đĩa đều 200MB — đây là ghi thật ra đĩa. - Bẫy page cache: đọc lại một file 100MB vừa được dùng cho
rchar=104885123(~100MB đọc qua syscall) nhưngread_bytes=0— không một byte nào từ đĩa! Dữ liệu được kernel giữ trong page cache (RAM) từ lần dùng trước, nênread()lấy từ RAM. Nếu chỉ nhìnrchar, bạn tưởng đĩa đang bận đọc 100MB — sai.read_bytes=0mới là sự thật: đĩa không bận. - Đo delta: đọc
write_bytestrước (0) và sau khi ghi 150MB (157286400) — chênh ~150MB. Đọc hai lần cho biết tiến trình ghi bao nhiêu trong khoảng đó; theo dõi con số tăng dần = tiến trình đang ghi liên tục (thủ phạm quật đĩa).
Vì sao phân biệt này quan trọng
Đây là mấu chốt chẩn đoán I/O: rchar/wchar cao không có nghĩa đĩa bận. Một app đọc đi đọc lại cùng dữ liệu (từ cache) có rchar khổng lồ mà read_bytes bằng 0 — hoàn toàn lành mạnh, không chạm đĩa. Ngược lại, khi hệ thống chậm vì đĩa, cái bạn cần nhìn là read_bytes/write_bytes (I/O thật) — chúng cho biết tiến trình nào đang thực sự đọc/ghi đĩa. Nhầm hai loại dẫn tới quy tội sai và tối ưu nhầm chỗ.
Đánh đổi cần cân nhắc
write_bytes bị nhiễu bởi write-back trễ. Kernel không ghi ngay mọi thứ xuống đĩa — nó gom vào cache rồi ghi lùi (write-back) sau. Nên write_bytes của một tiến trình có thể thấp hơn wchar tại một thời điểm (dữ liệu còn trong cache, chưa xuống đĩa), rồi tăng lên sau khi kernel flush. fsync() ép ghi ngay. Khi đo, hiểu rằng ghi đĩa thật có độ trễ so với lời gọi write.
iotop cho cái nhìn realtime nhưng cần quyền. iotop (như top cho I/O) hiển thị I/O đĩa của từng tiến trình theo thời gian thực — tiện hơn đọc /proc thủ công. Nhưng nó cần CAP_NET_ADMIN/root và kernel bật accounting phù hợp, nên không phải môi trường nào cũng chạy được. Khi không có iotop, đọc /proc/<pid>/io lặp lại (hoặc pidstat -d) là phương án thủ công tương đương.
Trong container/máy ảo, I/O đĩa có thêm lớp. Như bind mount trên Docker Desktop, I/O có thể đi qua ranh giới VM hoặc lớp overlayfs, khiến read_bytes/write_bytes phản ánh khác với đĩa vật lý thật. Khi chẩn đoán I/O trong container, kết hợp /proc/<pid>/io với công cụ ở tầng host (hoặc docker stats) để có bức tranh đầy đủ.
Ba ý mang về
/proc/<pid>/iocho I/O của tiến trình: đo thật, tiến trình ghi 200MB hiệnwchar=write_bytes=200MB vàsyscw=200— biết chính xác nó ghi bao nhiêu và bằng bao nhiêu lời gọi.- Bẫy page cache —
rcharcao ≠ đĩa bận: đo thật, đọc file trong cache chorchar=100MBnhưngread_bytes=0(từ RAM, không chạm đĩa); muốn biết đĩa có bận thật, nhìnread_bytes/write_bytes. - Đo delta để tìm thủ phạm quật đĩa: đọc
/proc/<pid>/iohai lần thấywrite_bytestăng (0→150MB) — tiến trình đang ghi liên tục; nhớ write-back gây trễ, và dùngiotop/pidstat -dcho cái nhìn realtime.
Nguồn
- Kernel docs — proc//io: https://docs.kernel.org/filesystems/proc.html
- man7.org — proc_pid_io(5): https://man7.org/linux/man-pages/man5/proc_pid_io.5.html
- man7.org — iotop(8): https://man7.org/linux/man-pages/man8/iotop.8.html
Phần sau ta giải mã một con số ai cũng thấy mà ít người hiểu: load average — ba số 1/5/15 phút thực sự đo gì, vì sao nó khác %CPU, và vì sao load cao không phải lúc nào cũng là CPU quá tải.