VietTechiesHIỂU CÔNG NGHỆ. MỞ GÓC NHÌN.
☰ Chuyên mục

Mars Pathfinder liên tục tự khởi động lại trên sao Hỏa. Cách sửa nằm ở một thiết lập mà nhóm kỹ sư đã bỏ sót.

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

Xe tự hành Sojourner bên cạnh một tảng đá lớn trên bề mặt sao Hỏa đầy đá, nhìn từ tàu đổ bộ Mars Pathfinder, kèm chữ “Mars Pathfinder”, “Priority inversion · July 1997” và một sơ đồ nhỏ về dòng thời gian của các tác vụ.


Tháng 7 năm 1997, không lâu sau khi tàu đổ bộ Mars Pathfinder của NASA hạ cánh xuống sao Hỏa và bắt tay vào thu thập dữ liệu thời tiết, máy tính trên tàu bắt đầu tự khởi động lại. Mỗi lần như vậy, phần việc còn lại trong kế hoạch hôm đó phải dời sang ngày hôm sau, dù dữ liệu đã thu được không bị mất.

Nguyên nhân nằm ở phần mềm. Một tác vụ có mức ưu tiên thấp, phụ trách dữ liệu thời tiết, đang giữ một khóa (lock) mà một tác vụ ưu tiên cao cần dùng, trong khi các tác vụ ưu tiên trung bình vẫn tiếp tục chạy. Giới kỹ sư gọi hiện tượng này là đảo ngược ưu tiên (priority inversion).

Đây không phải một bug mới. “Chúng tôi đã thấy sự cố này trước khi hạ cánh, nhưng khi cố truy tìm thì không sao làm nó lặp lại được,” Glenn Reeves viết. Ông là trưởng nhóm phần mềm bay (flight software) của Pathfinder tại Phòng thí nghiệm Sức đẩy Phản lực (JPL) thuộc NASA. Trên sao Hỏa, chuyện khởi động lại cứ tái diễn, và lần này dữ liệu ghi vết đã giúp nhóm tóm được nguyên nhân.

Về sau, Reeves phản bác phiên bản nổi tiếng của câu chuyện, theo đó các kỹ sư đã sửa lỗi cho Pathfinder thông qua một trình thông dịch C mà nhờ may mắn vẫn còn được bật.

Một tác vụ thời tiết đã làm nghẽn bus dữ liệu của tàu đổ bộ ra sao

Phần mềm bay của Pathfinder chạy trên VxWorks, một hệ điều hành thời gian thực thương mại của hãng Wind River. Bus dữ liệu và các thiết bị gắn vào bus, trong đó có thiết bị đo thời tiết của tàu đổ bộ, do hai tác vụ điều khiển. Tác vụ thứ nhất, bc_sched, thiết lập các lượt trao đổi dữ liệu trên bus cho từng chu kỳ, với nhịp tám chu kỳ mỗi giây. Tác vụ này có mức ưu tiên cao nhất trong hệ thống, chỉ trừ một tác vụ của chính VxWorks. Tác vụ thứ hai, bc_dist, thu thập kết quả của từng chu kỳ rồi phân phối dữ liệu. Cứ mỗi chu kỳ, hai tác vụ lại kiểm tra lẫn nhau xem bên kia đã làm xong phần việc của mình chưa.

Các tác vụ chuyển thông điệp cho nhau qua pipe (kênh truyền dữ liệu) của VxWorks và dùng select() để chờ trên các pipe đó. Hàm select() dựa vào một semaphore kiểu mutex — loại khóa mà mỗi lúc chỉ một tác vụ được giữ. Tác vụ thời tiết, vốn có mức ưu tiên thấp hơn nhiều, vẫn đang giữ khóa mutex ấy thì bị vài tác vụ ưu tiên trung bình giành quyền chạy (preempt). Sau đó, bc_dist tìm cách ghi vào một pipe và bị chặn lại, phải chờ chính khóa ấy. Do các tác vụ ưu tiên trung bình được quyền chạy trước, tác vụ thời tiết không có cơ hội nhả khóa kịp lúc. Đến lần kiểm tra tiếp theo của bc_sched, bc_dist vẫn chưa hoàn thành chu kỳ của mình. Thế là bc_sched báo lỗi, và lỗi này khiến máy tính khởi động lại.

Một bài báo năm 1990 của Lui Sha, Ragunathan Rajkumar và John Lehoczky đã phân tích cái bẫy này và một cách khắc phục nó: kế thừa ưu tiên (priority inheritance). Chừng nào một tác vụ ưu tiên thấp còn giữ khóa mà một tác vụ ưu tiên cao hơn đang chờ, tác vụ giữ khóa sẽ chạy ở mức ưu tiên cao hơn đó, nhờ vậy các tác vụ ưu tiên trung bình không thể chen vào giữa. VxWorks có cung cấp tùy chọn này. Nhưng trên Pathfinder, các semaphore mà select() dùng đều được tạo ra mà không kèm tùy chọn đó.

Tìm ra bug trong phòng thí nghiệm

Với Pathfinder, một lần khởi động lại không phải chuyện chí mạng. Nhóm đã thiết kế phần mềm để chịu được những lần như thế: “Chúng tôi đã tích hợp sẵn (và đã kiểm thử) khả năng trải qua nhiều lần khởi động lại trong lúc đang đi qua bầu khí quyển sao Hỏa,” Reeves viết. Trước khi tàu hạ cánh, lúc nhóm đang “tập trung cao độ vào phần mềm cho giai đoạn đi vào khí quyển và hạ cánh”, họ xếp lần khởi động lại chưa rõ nguyên nhân ấy vào loại vấn đề có mức ưu tiên thấp hơn, và rồi không còn thời gian cho những vấn đề như vậy.

Nhóm đã cố ý giữ lại một cơ chế ghi vết và ghi nhật ký (trace and log) trong phần mềm bay. David Cummings, một kỹ sư phần mềm trong nhóm, là người xây dựng cơ chế này, còn Lisa Stanley của Wind River gắn thêm mã theo dõi vào các dịch vụ của VxWorks, như pipe và select(), để phục vụ cơ chế đó. Phần mềm vốn đã tự dừng ghi vết và lưu lại dữ liệu khi lỗi này xảy ra, nên nhóm có thể bắt đầu thử nghiệm mà không phải sửa chút code nào. Trong phòng thí nghiệm, họ cho chạy lại đúng những hoạt động như trên tàu. “Trong vòng chưa đầy 18 giờ, chúng tôi đã khiến sự cố lặp lại được,” Reeves về sau viết trên tạp chí Dr. Dobb’s Journal. Bản tường thuật năm 1997 của ông kể lại những gì diễn ra tiếp theo: “Một khi đã tái hiện được lỗi thì vấn đề đảo ngược ưu tiên đã lộ rõ.”

Vì sao kế thừa ưu tiên lại bị tắt? Reeves viết rằng Wind River đã cố ý không đưa tùy chọn này vào thiết lập mặc định cho thư viện select mà hãng cung cấp. Còn nhóm thì không có chủ ý để tùy chọn đó tắt: “Đơn giản là chúng tôi đã bỏ sót.”

Cách sửa: một biến toàn cục, gửi lên dưới dạng bản vá

Bản sửa không lớn. Nhóm thay đổi một biến toàn cục để các semaphore do select() tạo ra đều có tùy chọn kế thừa ưu tiên. Thay đổi này sẽ áp dụng cho mọi semaphore của select được tạo ra từ đó về sau, nên đã được kiểm chứng qua phân tích và thử nghiệm. Wind River kết luận rằng ảnh hưởng tới hiệu năng sẽ rất nhỏ, và select() sẽ vẫn hoạt động như cũ, miễn là mỗi bộ mô tả tệp (file descriptor) chỉ có một tác vụ chờ. “Chúng tôi đã thử nghiệm hệ thống kỹ lưỡng trước khi thay đổi phần mềm trên tàu,” Reeves viết.

Bản vá được gửi lên sao Hỏa dưới dạng một diff: phần khác biệt giữa code trên tàu và phiên bản mà nhóm muốn có. “Việc này đã khắc phục được sự cố,” Reeves viết.

Phiên bản nổi tiếng của câu chuyện sai ở đâu

Câu chuyện lan rộng qua một bản tóm tắt của Mike Jones ở Microsoft. Bản tóm tắt này được chuyền tay qua email với tiêu đề “What really happened on Mars?” (tạm dịch: Điều gì đã thực sự xảy ra trên sao Hỏa?) và được đăng lên danh sách thư điện tử RISKS Digest vào tháng 12 năm 1997. Jones viết bản tóm tắt dựa trên bài phát biểu chính mà David Wilner, giám đốc kỹ thuật của Wind River Systems, trình bày tại hội nghị Real-Time Systems Symposium. Theo phiên bản của Jones, các kỹ sư JPL đã “may mắn” phóng tàu khi shell của VxWorks — một trình thông dịch C — vẫn đang bật, và sửa bug bằng cách tải lên một chương trình C ngắn thông qua shell này. Jones còn viết rằng mỗi lần khởi động lại đều làm mất dữ liệu, và một hoặc hai lần khởi động lại trong quá trình thử nghiệm trước khi phóng đã bị quy cho một trục trặc phần cứng.

Reeves trả lời vào ngày 15 tháng 12 năm 1997. “Không, chúng tôi không dùng shell của vxWorks để thay đổi phần mềm (dù shell vẫn dùng được trên tàu),” ông viết. Theo ông, các tính năng gỡ lỗi “không phải nhờ ‘may mắn’ mới còn được bật, mà được giữ lại trong phần mềm theo thiết kế.” Dữ liệu đã thu được không hề bị mất. Lần phát hiện lỗi trước đó cũng không bị gạt đi: “Chuyện đó không bị lãng quên, cũng không bị xem là không quan trọng.”

Những lần khởi động lại của Pathfinder cho thấy gì về khóa và mức ưu tiên

Mức ưu tiên có tác dụng đến đâu còn tùy vào những khóa mà tác vụ phải chờ. Tác vụ điều khiển bus bc_dist có mức ưu tiên cao hơn tác vụ thời tiết, nhưng một khi bc_dist cần đến khóa mà tác vụ thời tiết đang giữ, thời gian nó phải chờ lại do các tác vụ ưu tiên trung bình quyết định.

Nắm rõ thiết lập mặc định của nền tảng bạn dùng. Trong một hệ điều hành mua sẵn, kế thừa ưu tiên cho select() mặc định bị tắt. Reeves rút ra bài học cho bất kỳ ai đưa phần mềm thương mại mua sẵn (commercial off-the-shelf, COTS) lên các phương tiện bay: “hãy chắc chắn rằng bạn biết nó hoạt động ra sao.”

Giữ lại công cụ theo dõi giúp bạn nhìn thấy sự cố. Trước khi tàu hạ cánh, bug này đã lọt khỏi tay nhóm. Với cơ chế ghi vết đã có sẵn, nhóm ở phòng thí nghiệm mất chưa đầy 18 giờ để khiến bug tái diễn — và một khi bug đã tái diễn thì nguyên nhân lộ rõ.

Bản sửa không làm tác vụ thời tiết trở nên quan trọng hơn, mà cho tác vụ ấy mượn mức ưu tiên của bc_dist, chỉ vừa đủ lâu để trả lại khóa.

Bài liên quan

Đọc bằng ngôn ngữ khác: English

Nguồn