
Bài viết này phân tích một tình huống debug memory leak thực tế trong kernel, minh họa các kỹ thuật được sử dụng và quy trình xác định nguyên nhân gốc rễ.
1. Bối cảnh: Memory Leak ở User-Space vs. Kernel-Space
Trước tiên, cần phân biệt rõ hai loại memory leak:
- Memory Leak ở User-Space: Đây là kết quả của việc một tiến trình (process) cấp phát bộ nhớ (ví dụ: qua
malloc) nhưng quên không giải phóng. Tuy nhiên, loại leak này chỉ tồn tại trong vòng đời của tiến trình đó. Khi tiến trình kết thúc (exit), hệ điều hành (OS) sẽ tự động thu hồi và giải phóng toàn bộ những vùng nhớ mà tiến trình quên chưa giải phóng. - Memory Leak ở Kernel-Space: Đây là lỗi rò rỉ xảy ra bên trong chính nhân hệ điều hành. Do kernel chỉ “kết thúc” khi hệ thống tắt nguồn hoặc khởi động lại, nên nếu một vùng nhớ bị leak dưới kernel, nó sẽ tồn tại vĩnh viễn trong suốt thời gian hệ thống hoạt động, dần dần làm cạn kiệt tài nguyên.
![]()
2. Kỹ thuật Debug Memory Leak trong Kernel
Việc debug memory leak dưới kernel khác biệt so với user-space, do kernel sử dụng một bộ cấp phát bộ nhớ (Slab/Slub/Slub allocator) riêng. Rất may, kernel hỗ trợ nhiều tính năng mạnh mẽ để debug chính nó, nổi bật là SLUB_DEBUG.
Quy trình debug cơ bản thường bao gồm các bước:
- Kích hoạt
SLUB_DEBUG: Tính năng này cần được bật (enable) khi cấu hình và build kernel image. - Tái lập lỗi: Load kernel image mới vào thiết bị và tiến hành chạy các bài test để gây ra memory leak.
- Phân tích
slabinfo: Đọc file/proc/slabinfođể lấy thông tin thống kê về các đối tượng (object) đang được cấp phát động. Chúng ta cần tìm kiếm các đối tượng có số lượng tăng cao một cách bất thường. Trong ảnh chụpslabinfo, cột ngoài cùng bên trái là tên đối tượng (slab cache), và cột thứ hai là số lượng đối tượng đang hoạt động (active). - Truy vết (Trace) Allocator: Ghi ‘1’ vào file
/sys/kernel/slab/<tên-đối-tượng>/alloc_calls(nếu kernel hỗ trợ) để lấy ra tên của các hàm đã gọi cấp phát đối tượng đó.
3. Phân tích Tình huống Thực tế (Case Study)
Dưới đây là một quy trình debug một lỗi memory leak thực tế:
A. Hiện tượng (Symptom)
Sau khi áp dụng kỹ thuật debug ở trên, hệ thống cho thấy một đối tượng tên là kmalloc-8192 được cấp phát liên tục, chiếm dụng lên đến 2GB bộ nhớ.
B. Phân tích Ban đầu
- Thử nghiệm 1: Cố gắng ghi ‘1’ vào file
/sys/kernel/slab/kmalloc-8192. Thao tác này thất bại vìkmalloc-8192là một thư mục (chứa các slab con cho từng CPU node), không phải là một file. - Phân tích báo cáo lỗi: Báo cáo từ phía khách hàng (QA) cho thấy lỗi này chỉ xảy ra khi ghi dữ liệu nhiều lần vào port UART của thiết bị.
- Thử nghiệm 2 (Review code): Kiểm tra lại code của driver UART, phát hiện có một số chỗ cấp phát bộ nhớ (ví dụ: cho DMA), nhưng không tìm thấy mối liên hệ rõ ràng ngay lập tức với đối tượng 8192 bytes.
Mấu chốt tại thời điểm này là phải tìm ra được hàm nào trong kernel đang liên tục gọi cấp phát kmalloc(8192).
C. Kỹ thuật Debug Tùy chỉnh (Custom Tracing)
Do các công cụ tiêu chuẩn chưa chỉ ra được thủ phạm, một phương pháp debug tùy chỉnh đã được áp dụng:
- Sửa code Kernel: Chỉnh sửa trực tiếp hàm
kmalloc(hoặc hàm cấp phát nội bộ mà nó gọi). - Thêm điều kiện: Thêm logic để kiểm tra
if (size == 8192). - In Call Trace: Nếu điều kiện đúng, gọi
dump_stack()hoặc một hàm tương đương để in ra “call trace” (danh sách các hàm đã gọi) dẫn đến việc cấp phát này. - Thêm Toggle: Để tránh làm ngập log hệ thống, một biến toggle (có thể bật/tắt qua
/prochoặcdebugfs) được thêm vào. Chỉ khi toggle này được bật, hàmkmallocmới in log ra.
D. Phát hiện và Kết luận
Sau khi build lại kernel với bản vá debug và chạy lại bài test:
- Khi bật toggle, log hệ thống cho thấy hàm
inode_createđược gọi ra liên tục, và chính hàm này (hoặc các hàm con của nó) đã gọikmalloc(8192). - Phân tích sâu hơn cho thấy
inode_createlà hàm được hệ thống gọi khi một file được mở (open). - Kiểm tra lại script test của khách hàng: Họ đang dùng lệnh
echo "data" > /dev/ttyS_trong một vòng lặp. Câu lệnhecho(với toán tử>) sẽ thực hiện mở file (open) cho mỗi lần nó được gọi. - Kết luận: Vấn đề được khoanh vùng nằm trong hàm
opencủa driver port UART.
Nhìn lại code của hàm open() trong driver UART, đã phát hiện chính xác một hành vi cấp phát bộ nhớ nhưng không có lời gọi free() tương ứng trong hàm release() (hàm được gọi khi file bị đóng).
4. Tổng kết
Các lỗi rò rỉ bộ nhớ trong kernel, mặc dù nghiêm trọng, nhưng thường có thể được tìm ra một cách có hệ thống. Kinh nghiệm trong việc sử dụng các tính năng hỗ trợ debug của kernel (như SLUB_DEBUG, slabinfo) và khả năng áp dụng các kỹ thuật debug tùy chỉnh (như thêm trace có điều kiện) là chìa khóa để xác định nhanh chóng nguyên nhân của lỗi.
