Hệ điều hành & Tiến trình/Mini-challenge — mổ xẻ hai lệnh (cat vs curl) bằng strace
6/28
Bài 6 / 28~15 phútKernel & System CallMiễn phí lượt xem

Mini-challenge — mổ xẻ hai lệnh (cat vs curl) bằng strace

Diagnose và so sánh hai lệnh thực tế (cat một file lớn vs curl một URL) bằng strace: đếm, phân loại syscall theo nhóm và giải thích vì sao mỗi nhóm xuất hiện.

TL;DR: Bạn sẽ tự cầm strace -c mổ xẻ hai lệnh thật — cat một file lớn (nặng về I/O file) và curl một URL (nặng về mạng) — rồi trả lời ba câu: (1) mỗi lệnh gọi nhiều nhất những syscall nào và thuộc nhóm gì; (2) nhóm syscall nào đặc trưng cho từng lệnh; (3) syscall nào ngốn thời gian nhất và vì sao. Đây là bài tổng hợp cả module: bạn dùng kiến thức về ranh giới user/kernel, cơ chế syscall, và cách đọc strace để tự chẩn đoán một chương trình chỉ từ dấu vết syscall của nó — kỹ năng lõi khi debug service thật.

🎯 Đề bài

Bạn được giao hai lệnh và phải hiểu chúng "nói chuyện" với kernel thế nào, chỉ bằng strace — không đọc source của cat hay curl.

Chuẩn bị dữ liệu:

# Tao mot file ~50MB de co du lieu that
head -c 50000000 /dev/urandom > big.bin

Lệnh A — đọc file lớn:

cat big.bin > /dev/null

Lệnh B — tải một URL:

curl -s https://example.com -o /dev/null

Nhiệm vụ của bạn (làm trước khi đọc phần Lời giải):

  1. Chạy strace -c cho từng lệnh, thu bảng tổng hợp.
  2. Với mỗi lệnh: liệt kê 3–5 syscall bị gọi nhiều nhất và phân nhóm (file / memory / process / network).
  3. Chỉ ra nhóm đặc trưng phân biệt hai lệnh — vì sao cat khác curl về hình dạng syscall.
  4. Tìm syscall ngốn thời gian nhất trong mỗi lệnh và giải thích vì sao nó đắt (nhớ khái niệm mode switch, chờ I/O, chờ mạng).

🔍 Phân tích I-P-O

Mô tả
InputHai lệnh: cat big.bin (đọc 50 MB) và curl (tải một trang web)
ProcessChạy mỗi lệnh dưới strace -c, đọc bảng, phân nhóm, xếp hạng
OutputKết luận: nhóm syscall đặc trưng mỗi lệnh + syscall đắt nhất + lý do cơ chế

Điều kiện để phân tích đúng:

  • Chạy nhiều lần cho ổn định (lần đầu có thể chậm do cache lạnh, file chưa trong page cache).
  • Nhớ strace phóng đại thời gian (bài 04) — dùng để so tỉ lệ, không phải tốc độ tuyệt đối.
  • Với curl, kết quả phụ thuộc mạng; nếu offline, thay bằng một URL nội bộ hoặc file local.

📦 Concept mapping

Bài này tổng hợp trực tiếp cả module:

Kiến thức cầnBài gốcDùng ở đây thế nào
User code phải xin kernel làm I/OBài 01 — Kernel vs user modeMọi dòng trace là một lần cat/curl vượt biên sang kernel
Cơ chế syscall + chi phí mode switchBài 02 — System call là gìGiải thích vì sao nhiều read/write nhỏ thì đắt
Con đường vào kernel + chờ I/OBài 03 — Interrupt, trap & exceptionrecvfrom/poll block chờ interrupt "dữ liệu tới" từ card mạng
Đọc và phân nhóm strace -cBài 04 — Đọc syscall bằng straceCông cụ chính của bài

▶️ Bắt đầu

Chạy và tự quan sát trước. Với lệnh A:

strace -c cat big.bin > /dev/null

Với lệnh B:

strace -c curl -s https://example.com -o /dev/null

Ghi lại bảng của mỗi lệnh. Tự trả lời bốn câu hỏi trong Đề bài trước khi mở Lời giải. Nếu curl không có sẵn, thử wget -q -O /dev/null https://example.com hoặc dùng getent hosts example.com để có một lệnh chạm mạng nhẹ hơn.

💡 Gợi ý

Gợi ý 1 — cách phân nhóm nhanh: đừng nhìn từng syscall lạ. Gộp theo bảng bốn nhóm ở bài 04: thấy read/write/openat/close → nhóm file; mmap/mprotect/brkmemory; socket/connect/recvfrom/sendto/pollnetwork; execve/clone/wait4process.

Gợi ý 2 — vì sao cat đầy readwrite: cat đọc file theo từng khối rồi ghi ra. Kích thước khối quyết định số syscall: 50 MB đọc theo khối 128 KB cho khoảng vài trăm cặp read+write. Nếu bạn thấy con số lớn hơn nhiều, khối nhỏ hơn — mỗi khối là một mode switch.

Gợi ý 3 — vì sao curl có nhiều openat/mmap bất ngờ: phần lớn syscall của curl không phải mạng — mà là nạp thư viện (libcurl, libssl, libc) và đọc chứng chỉ TLS (openat hàng loạt file trong /etc/ssl/certs). Phần mạng thật (socket/connect/recvfrom) ít dòng hơn nhưng thường đắt thời gian nhất vì phải chờ.

Gợi ý 4 — syscall đắt nhất: nhìn cột % time, không phải cột calls. Syscall gọi nhiều chưa chắc tốn thời gian; syscall chờ (network, disk) tốn thời gian dù gọi ít.

✅ Lời giải

Dưới là bảng tiêu biểu (số cụ thể sẽ khác trên máy bạn — quan trọng là hình dạng).

Lệnh A — strace -c cat big.bin

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 52.10    0.014820          38       384           read
 41.30    0.011750          30       383           write
  2.80    0.000800          80        10           mmap
  1.50    0.000420          42        10           openat
  1.10    0.000310          31        10           close
  0.60    0.000170          28         6           fstat
  ...
------ ----------- ----------- --------- --------- ----------------
100.00    0.028440                   820         2 total

Phân tích:

  • Nhóm đặc trưng: file / I/O. read (384) và write (383) áp đảo cả về số lần lẫn thời gian. Đây đúng bản chất cat: đọc một khối rồi ghi một khối, lặp tới hết file. Số cặp ~384 với file 50 MB cho biết mỗi khối cỡ 128 KB (50MB / 384 ≈ 130KB).
  • Nhóm memory (mmap, openat, close, fstat) chỉ vài lần: đây là phần khởi động — nạp libc, mở file, kiểm tra metadata. Không đáng kể.
  • Syscall đắt nhất: read (52% thời gian). Nó đắt vì mỗi lần đọc một khối 128 KB phải chờ dữ liệu từ đĩa (nếu chưa trong page cache) — tiến trình bị cho ngủ, đánh thức bởi interrupt "đọc xong" từ ổ đĩa (nhớ bài 03). write gần ngang vì cũng chuyển 50 MB ra đích.

Nếu big.bin đã nằm sẵn trong page cache (chạy lần hai), read sẽ nhanh hơn nhiều vì không chạm đĩa — chỉ copy từ cache. Đây là bằng chứng trực tiếp rằng "chờ I/O" là phần đắt.

Lệnh B — strace -c curl -s https://example.com -o /dev/null

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 43.20    0.006100         610        10           recvfrom
 21.50    0.003030        1010         3           connect
 12.80    0.001800         180        10         2 poll
  8.10    0.001140          14        79        18 openat
  5.40    0.000760           9        82           mmap
  3.20    0.000450          10        45           read
  2.10    0.000300          12        25           close
  ...
------ ----------- ----------- --------- --------- ----------------
100.00    0.014100                   410        21 total

Phân tích:

  • Nhóm đặc trưng: network. recvfrom (nhận dữ liệu từ server), connect (mở kết nối TCP), poll (chờ socket sẵn sàng) — bộ ba này không xuất hiện ở cat. Đây là dấu vân tay của một chương trình mạng.
  • Nhưng openat bị gọi nhiều nhất về số lần (79), phần lớn là nạp hàng loạt shared library (libssl, libcrypto, libz...) và đọc CA bundle chứng chỉ. Nhiều dòng ENOENT (18 errors) ở đây là bình thường — search path tìm cert/lib (nhớ pitfall bài 04), không phải lỗi.
  • Syscall đắt nhất: recvfromconnect (chiếm gần 65% thời gian dù chỉ vài lần gọi). Chúng đắt vì phải chờ mạng: connect chờ bắt tay TCP (một vòng đi-về tới server), recvfrom chờ gói dữ liệu tới. Trong lúc chờ, tiến trình ngủ và được đánh thức bởi interrupt từ card mạng khi gói đến (bài 03). Đây là ví dụ hoàn hảo cho gợi ý 4: ít lần gọi nhưng tốn thời gian nhất, vì chờ chứ không phải vì gọi nhiều.

So sánh hai lệnh

flowchart LR
  subgraph cat["cat big.bin"]
    A["read x384"] --> B["write x383"]
    B --> C["Nhom: FILE<br/>Dat vi: cho dia"]
  end
  subgraph curl["curl URL"]
    D["connect x3"] --> E["recvfrom x10"]
    E --> F["Nhom: NETWORK<br/>Dat vi: cho mang"]
  end
Trụccat big.bincurl URL
Nhóm áp đảo (số lần)read / write (file)openat (nạp lib/cert) + recvfrom (network)
Nhóm đặc trưngFile I/ONetwork
Syscall gọi nhiều nhấtreadopenat
Syscall đắt thời gian nhấtread (chờ đĩa)recvfrom / connect (chờ mạng)
Vì sao đắtChờ dữ liệu từ đĩa; chuyển 50 MBChờ vòng đi-về mạng tới server

Kết luận lõi: cột calls và cột % time kể hai câu chuyện khác nhau. openat bị gọi nhiều nhất trong curl nhưng rẻ (không chờ gì); recvfrom gọi ít nhưng đắt (chờ mạng). Người debug giỏi nhìn cả hai: số lần cao gợi ý anti-pattern (syscall thừa), thời gian cao gợi ý điểm chờ (I/O/mạng).

🎓 Mở rộng

Xem từng syscall thay vì tổng hợp: bỏ -c, thêm -T để thấy thời gian mỗi call:

strace -T -e trace=network curl -s https://example.com -o /dev/null

Bạn sẽ thấy dòng connect(...) = ... <0.08...> — con số trong <> là thời gian thật của cú bắt tay TCP, thường là phần lâu nhất.

So sánh khối đọc lớn vs nhỏ: dùng dd với block size khác nhau và đếm syscall:

strace -c dd if=big.bin of=/dev/null bs=1024      # khoi 1KB -> rat nhieu read
strace -c dd if=big.bin of=/dev/null bs=1048576   # khoi 1MB -> it read

Số read chênh nhau khoảng một nghìn lần (1 MB / 1 KB = 1024) cho cùng một file — minh hoạ sống động bài học "gom syscall lớn" của bài 02.

Đếm theo nhóm process: thử strace -f -e trace=%process bash -c 'ls | wc -l' để thấy clone/execve/wait4 khi shell tạo tiến trình con và nối pipe — hé lộ module tiếp theo về vòng đời tiến trình.

✨ Điều bạn vừa làm được

Bạn vừa tự chẩn đoán hai chương trình chỉ từ dấu vết syscall — không đọc một dòng source nào. Hãy nhìn lại những gì bạn kết nối:

  • Ranh giới user/kernel: mỗi dòng trace là một lần chương trình vượt biên nhờ kernel làm việc (bài 01).
  • Cơ chế và chi phí syscall: hiểu vì sao khối đọc nhỏ sinh nhiều mode switch và tốn thời gian (bài 02).
  • Chờ = interrupt: biết recvfrom/read block là tiến trình đang ngủ, chờ interrupt phần cứng đánh thức (bài 03).
  • Phân nhóm và xếp hạng: đọc strace -c, phân biệt "gọi nhiều" với "tốn thời gian" (bài 04).

Kỹ năng này — nhìn một chương trình lạ, chạy strace, và nói được nó đang làm gì / kẹt ở đâu — là thứ phân biệt người hiểu hệ điều hành với người chỉ biết chương trình "chạy" hay "không chạy".

Bài tiếp theo: Tổng kết module — Kernel & System Call

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