Pipes, Forks và Zombie: Hiểu sâu về lập trình hệ thống

Phần mềm14 tháng 5, 2026·4 phút đọc

Bài viết phân tích sự khác biệt giữa triết lý Lập trình văn bản của Don Knuth và cách tiếp cận Pipes của Doug McIlroy. Chúng ta cũng sẽ tìm hiểu về cơ chế tín hiệu SIGPIPE, quản lý tiến trình và hiện tượng Zombie trong hệ điều hành Unix/Linux.

Doug McIlroy đã mô tả khái niệm "pipes" (đường ống) từ rất lâu trước khi nó được hiện thực hóa trong các hệ điều hành. Trong khi đó, Don Knuth — người được coi là cha đẻ của thuật ngữ "khoa học máy tính", tác giả của bộ sách The Art of Computer Programming và người tạo ra ngôn ngữ LaTeX — lại phát triển triết lý "Literate Programming" (Lập trình văn bản). Knuth rất thích viết văn và lập trình, nên ông đã phát triển một phong cách cho phép viết văn bản mô tả chương trình ngay bên cạnh mã nguồn. Ý tưởng là viết văn xuôi và mã nguồn đồng thời. Tuy nhiên, ý tưởng này không thực sự phổ biến vì nó tạo ra chi phí quá lớn (overhead) để hoàn thành các tác vụ đơn giản như phân tích văn bản.

Từ góc độ lập trình hệ thống sử dụng pipes, McIlroy đã phản hồi công việc của Knuth. Ông có thể hoàn thành chính xác nhiệm vụ phân tích văn bản đó chỉ với 6 dòng mã shell bằng cách sử dụng pipes. Trong khi Knuth tiếp cận vấn đề từ góc độ thuật toán, McIlroy lại tiếp cận từ góc độ hệ thống, xâu chuỗi các đầu ra trung gian lại với nhau để ra kết quả cuối cùng.

Pipes và tín hiệu SIGPIPE

Chương trình seq nhận một số làm đối số và in ra các số liên tiếp bắt đầu từ số đó. Nếu cung cấp đối số thứ hai, việc in số sẽ dừng lại khi đạt đến số đó. Ngược lại, các số sẽ được in ra mãi mãi.

Nếu chúng ta chuyển đầu ra (pipe) của chương trình seq sang lệnh less, dữ liệu hiển thị trên màn hình sẽ bị cắt ngắn vì less chỉ hiển thị đủ lượng dữ liệu để lấp đầy màn hình. Lúc này, chương trình seq có vẻ như bị tạm dừng. Tuy nhiên, liệu nó có thực sự đang chạy không?

Nếu chúng ta pipe seq sang less và xem danh sách các tiến trình đang chạy bằng ps aux, chúng ta sẽ thấy rằng chương trình seq không còn chạy nữa. Sử dụng strace để kiểm tra sâu hơn sẽ tiết lộ rằng sau một loạt lệnh ghi (write), có một tín hiệu SIGPIPE xuất hiện. SIGPIPE xảy ra khi bạn cố ghi dữ liệu vào một đường ống mà không có tiến trình nào đang đọc (no readers). Hành động mặc định sau một SIGPIPE là giết chương trình. Pipes tự động giết các chương trình khi đầu ra của chúng không còn cần thiết nữa. Điều này giải thích tại sao seq bị giết khi được pipe sang less.

Quản lý tiến trình và waitpid

Mọi tiến trình đều có một tiến trình cha duy nhất. Gốc của cây phân cấp tiến trình (process tree) là một tiến trình gọi là init, có pid là 1. Đây là tiến trình duy nhất không thể bị giết. Lệnh waitpid được sử dụng để lấy trạng thái thoát của một tiến trình. Trạng thái thoát của một tiến trình được lưu trong cấu trúc tiến trình cho đến khi tiến trình cha cần đến trạng thái đó. waitpid sẽ thu thập trạng thái này và tái chế cấu trúc tiến trình. Điều này có nghĩa là cấu trúc tiến trình đó có thể được sử dụng lại cho một tiến trình khác.

Fork và Zombie Process

Chương trình manyfork cố gắng thực hiện lệnh fork 10.000 lần. Tuy nhiên, nếu chạy lệnh ./manyfork, chỉ có khoảng 3.400 tiến trình được tạo ra. Chạy sudo ./manyfork, cấp quyền cao hơn cho chương trình, kết quả là khoảng 6.890 tiến trình được tạo. Hệ điều hành đang bảo vệ người dùng khỏi các chương trình bị "lỡ tay" (runaway program). Nếu chúng ta nhìn vào các tiến trình được tạo bởi manyfork, chúng ta thấy phần lớn trong số đó ở trạng thái "defunct".

Chương trình manyfork không chờ đợi các tiến trình con của nó bằng waitpid. Điều này sẽ tạo ra cái được gọi là "zombie process" (tiến trình ma). Zombie là một tiến trình đã bị chấm dứt nhưng chưa được tiến trình cha thực hiện lệnh wait (hoặc waitpid). Lệnh ps cho phép chúng ta xác định các zombie này. Dưới đây là ví dụ đầu ra của ps sau khi chạy manyfork:

user 78623 0.0 0.0 0 0 pts/0 Z+ 15:44 0:00 [manyfork]
user 78624 0.0 0.0 0 0 pts/0 Z+ 15:44 0:00 [manyfork]
user 78625 0.0 0.0 0 0 pts/0 Z+ 15:44 0:00 [manyfork]

Cột Z+ cho biết các tiến trình này là zombie. Các zombie này tiêu tốn tài nguyên, cụ thể là Process IDs (PID). Khi một tiến trình con sống sót lâu hơn tiến trình cha của nó, tiến trình cha của tiến trình con đó sẽ được gán lại cho tiến trình init với pid 1. Tiến trình init thu thập các tiến trình con mồ côi theo cách này. Nhiệm vụ của init là gọi waitpid trên các tiến trình con mồ côi để thu hồi tài nguyên của chúng.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗