16/10/2026 · VietTechies · Biên tập

Năm 1991, Richard P. Gabriel — người sáng lập Lucid, một công ty chuyên về Lisp — công bố một bài luận bàn về chuyện vì sao C và Unix trở nên phổ biến còn Lisp thì không. Phần nổi tiếng nhất của bài luận minh họa lập luận ấy bằng một quyết định thiết kế nhỏ: hệ điều hành nên làm gì khi một chương trình bị ngắt giữa lúc đang thực hiện một lời gọi hệ thống (system call). Unix để chương trình tự lo việc thử lại — cách làm mà Gabriel gọi là “tệ hơn lại tốt hơn” (worse is better).
Gabriel kể chuyện này qua cuộc gặp gỡ giữa hai nhân vật nổi tiếng, “một người đến từ MIT và một người đến từ Berkeley (nhưng làm về Unix)”. Bài luận không nêu tên hai người. Người bên MIT vốn rành ITS, hệ điều hành của Phòng thí nghiệm Trí tuệ nhân tạo thuộc Viện Công nghệ Massachusetts (MIT), và đã đọc mã nguồn của Unix. Ông muốn biết Unix giải quyết bài toán “PC loser-ing” (lời gọi hệ thống bị ngắt giữa chừng) như thế nào.
Bài toán này nảy sinh khi một chương trình yêu cầu hệ điều hành thực hiện một thao tác kéo dài — loại thao tác giữ khá nhiều trạng thái, chẳng hạn các bộ đệm vào/ra — rồi giữa chừng thì một tín hiệu ngắt ập đến. Yêu cầu ấy thường chỉ là một lệnh máy, nên bộ đếm chương trình — tức PC (program counter), thứ ghi lại vị trí hiện tại của chương trình — không thể cho biết thủ tục hệ thống đã làm đến đâu. Cái tên “PC loser-ing” vốn là một câu đùa ở MIT: ở đó, “loser” (kẻ thua cuộc) là cách gọi trìu mến dành cho người dùng. Gabriel viết rằng khi ấy, thủ tục hệ thống “hoặc phải lùi lại, hoặc phải tiến tiếp”.
Ba cách xử lý một lời gọi hệ thống bị ngắt
Phương án mà Gabriel gọi là “cách làm đúng đắn” (the right thing) chính là lùi lại: đưa PC về đúng lệnh đã phát ra lời gọi, để khi được chạy tiếp, chương trình chỉ việc đi vào thủ tục ấy thêm một lần nữa. ITS, tức Incompatible Timesharing System, được xây dựng sao cho chương trình không bao giờ thấy một lời gọi hệ thống đang làm dở. Theo mô tả của Alan Bawden về kỹ thuật này, một lời gọi bị ngắt giữa chừng tự cập nhật ngay tại chỗ các tham số của chính nó, nên lần chạy lại sẽ tiếp tục đúng từ điểm đã dừng. “Nhờ vậy, các lời gọi hệ thống hoạt động như thể chúng được cài đặt trực tiếp trong phần cứng,” ông viết.
Unix chọn lối đi đơn giản hơn. Theo lời kể của Gabriel, thủ tục hệ thống bao giờ cũng kết thúc, nhưng đôi khi trả về một mã lỗi cho biết nó không làm xong được việc của mình. Một chương trình viết đúng phải kiểm tra mã lỗi đó rồi quyết định có thử lại hay không. Trên System V của AT&T và trên 4.1BSD, một bản phát hành Unix của Berkeley, mã lỗi ấy là EINTR: một lời gọi bị cắt ngang khi chưa kịp truyền dữ liệu nào sẽ trả về −1 kèm mã lỗi này.
Mỗi lối đi đều có cái giá của nó:
- Lùi lại, như ITS đã làm: chương trình không bao giờ thấy một lời gọi làm dở, nhưng hệ điều hành phải biết cách hoàn tác hoặc làm tiếp mọi lời gọi kéo dài. Về sau, khi viết dưới một bút danh, Gabriel ước tính phần code cho giải pháp ấy là “chừng một trăm trang hợp ngữ”.
- Kết thúc rồi báo lỗi, như Unix đã làm: cũng theo bài viết đó, phần việc ở phía hệ thống “gần như chẳng có gì”, nhưng chương trình viết đúng nào cũng cần một bước kiểm tra và một vòng lặp.
- Tự động chạy lại, như 4.2BSD về sau đã làm: một lối đi trung gian. Berkeley lấy việc chạy lại làm hành vi mặc định, nhưng chỉ với những lời gọi bị ngắt khi chưa có dữ liệu nào được truyền đi.
Vì sao phía Unix coi phương án đơn giản là đúng
Người bên Unix không phủ nhận cái giá ấy. Gabriel gọi ông là “anh chàng New Jersey” (the New Jersey guy), ám chỉ Bell Labs ở bang New Jersey, nơi Unix và C được phát triển. Theo lời anh chàng New Jersey, dân Unix có biết vấn đề này, nhưng triết lý thiết kế của Unix là sự đơn giản, còn “cách làm đúng đắn” thì quá phức tạp. “Vả lại, lập trình viên có thể dễ dàng chèn thêm bước kiểm tra và vòng lặp này.” Khi người bên MIT phản bác rằng như vậy thì giao diện (interface) lại trở nên phức tạp, câu trả lời ông nhận được là: sự đơn giản của phần cài đặt (implementation) quan trọng hơn sự đơn giản của giao diện.
Trước đó trong bài luận, Gabriel đã dọn đường cho câu chuyện bằng hai danh sách cùng gồm bốn giá trị — sự đơn giản, tính đúng đắn, tính nhất quán và tính đầy đủ — nhưng với mức độ coi trọng khác nhau. “Cách làm đúng đắn” ưu tiên giao diện đơn giản hơn phần cài đặt đơn giản, và không chấp nhận bất cứ chỗ sai nào. Còn lối “tệ hơn lại tốt hơn” thì đặt sự đơn giản của phần cài đặt lên hàng đầu: “Đơn giản thì tốt hơn một chút so với đúng đắn.” Một thiết kế theo lối “tệ hơn lại tốt hơn” vẫn phải “đúng ở mọi khía cạnh quan sát được”, nhưng tính đầy đủ thì có thể hy sinh để đổi lấy bất kỳ phẩm chất nào khác.
Kết luận của ông nghe khá lạ khi đến từ một người kinh doanh Lisp: lối “tệ hơn lại tốt hơn”, “ngay cả ở dạng bị biếm họa, vẫn có khả năng sinh tồn tốt hơn ‘cách làm đúng đắn’”. Ông lập luận rằng nhờ đặt sự đơn giản của phần cài đặt lên hàng đầu, Unix và C dễ chuyển (port) sang các loại máy khác. Phần mềm như thế sẽ lan rộng, và một khi người ta đã nghiện, nó có thể được cải thiện dần để tiệm cận 90% “cách làm đúng đắn”. “Unix và C là những virus máy tính tối thượng,” ông viết.
Cái giá phải trả: vòng lặp thử lại sống lâu hơn cả cuộc tranh luận
Trên Linux ngày nay, nhiều lời gọi chặn (blocking), nếu bị một hàm xử lý tín hiệu cắt ngang, thì chỉ tự động chạy lại khi hàm xử lý ấy được đăng ký kèm cờ cấu hình (flag) SA_RESTART. Nếu không, như tài liệu hướng dẫn của Linux viết, “lời gọi thất bại và báo lỗi EINTR”. Một số lời gọi, chẳng hạn poll và select, không bao giờ tự chạy lại, dù có bật cờ này hay không.
Trước phiên bản 3.5, code Python nào cần chịu được các lần bị ngắt thì bắt lỗi ngay tại từng chỗ gọi hàm. Ở những phiên bản cuối cùng trước 3.5, một vòng lặp điển hình trông như sau:
while True:
try:
data = file.read(size)
break
except InterruptedError:
continue
PEP 475, một đề xuất cải tiến Python năm 2014 của Charles-François Natali và Victor Stinner, đã chuyển vòng lặp đó vào bên trong chính Python. Lập luận của đề xuất này là phiên bản hiện đại của lời phàn nàn từ người bên MIT: “Nếu Python không có hỗ trợ đặc biệt, việc này có thể khiến code ứng dụng dài dòng hơn nhiều so với mức cần thiết.”
Gabriel cũng từng đứng về phía bên kia
Bài “Worse Is Better Is Worse” ký tên Nickieben Bourbaki và được giới thiệu là tác phẩm của một người bạn lâu năm. Trong bài, Gabriel viết rằng “Richard đã phạm một sai lầm nguy hiểm khi công bố bài báo đó”. Cũng theo bài viết này, Gabriel đặt ra khẩu hiệu “tệ hơn lại tốt hơn” vào mùa xuân năm 1989, có vẻ là nghĩ ra ngay tại chỗ, trong lúc bạn bè tán gẫu về chuyện vì sao Lisp không phổ biến được.
Thế nhưng, cũng chính bài viết ấy lại đưa ra lý lẽ mạnh mẽ bênh vực quyết định của Unix. Phần code theo “cách làm đúng đắn” phức tạp đến mức “nhiều người cho rằng gần như không thể viết nó cho đúng”. Bài viết còn lập luận rằng chưa từng có một cuộc cạnh tranh trực diện nào: Unix chạy trên các máy PDP-11 của Digital Equipment Corporation, còn “những hệ điều hành khác, tốt hơn, mà Richard đang nói tới thì chạy trên PDP-10, dòng máy vốn không cạnh tranh với PDP-11”. Jerome Saltzer của MIT, trong một tài liệu hướng dẫn giảng dạy về bài luận của Gabriel, còn đi xa hơn: ông khuyên bỏ qua đoạn nói về PC loser-ing và cho rằng hai nhân vật trong đoạn ấy đang “ông nói gà, bà nói vịt”.
Nguyên tắc ra quyết định: ai sẽ viết thêm vòng lặp?
Quyết định về PC loser-ing không làm phần việc biến mất. Nó chuyển phần việc ấy ra khỏi một thủ tục trong nhân hệ điều hành (kernel) và đẩy vào mọi chương trình thực hiện lời gọi đó. Đôi khi, đánh đổi như vậy là đúng: một kernel nhỏ thì dễ viết và dễ chuyển sang máy khác hơn, và theo lập luận của Gabriel, Unix đã lan rộng chính nhờ vậy. Nhưng cái giá ấy thì mỗi bên gọi đều phải trả, chừng nào giao diện còn tồn tại.
Vì vậy, trước khi đơn giản hóa phía mà bạn phụ trách trong một giao diện, câu hỏi hữu ích là: từ nay ai sẽ viết thêm bước kiểm tra và vòng lặp ấy, và viết bao nhiêu lần? Với Python, câu trả lời đến cùng phiên bản 3.5, khi chính ngôn ngữ nhận về mình vòng lặp mà anh chàng New Jersey từng bảo là dễ chèn thêm.
Bài liên quan
Đọc bằng ngôn ngữ khác: English
Nguồn
- Richard P. Gabriel, “The Rise of ‘Worse is Better’,” trích từ “Lisp: Good News, Bad News, How to Win Big,” AI Expert, tháng 6 năm 1991 (bản sao dùng cho môn học tại MIT)
- Nickieben Bourbaki (Richard P. Gabriel), “Worse Is Better Is Worse,” bản sao đề ngày 4 tháng 12 năm 2000 (bản dùng cho môn học tại Princeton)
- Alan Bawden, “PCLSRing: Keeping Process State Modular” (bản sao)
- J. H. Saltzer, tài liệu hướng dẫn giảng dạy về bài “Lisp: Good News, Bad News, How to Win Big” của Gabriel, MIT
- Dự án Linux man-pages, signal(7): phần nói về việc hàm xử lý tín hiệu ngắt các lời gọi hệ thống và hàm thư viện
- Trang hướng dẫn BSD siginterrupt(3) (kho tài liệu lưu trữ dành cho lập trình viên của Apple)
- Charles-François Natali và Victor Stinner, PEP 475, “Retry system calls failing with EINTR,” 2014
- Wikipedia, “Worse is better”
- Wikipedia, “Lucid Inc.”
- Ảnh đại diện (ảnh minh họa, không phải cỗ máy nào trong câu chuyện này): DECSYSTEM-10 KL10 2065, bên trái là một phần máy PDP-11, tại Living Computer Museum, Seattle, tác giả Joe Mabel, qua Wikimedia Commons, CC BY-SA 3.0
