3 điểm bởi GN⁺ 2025-11-05 | 1 bình luận | Chia sẻ qua WhatsApp
  • Giải thích cấu trúc bộ nhớ tiến trình của Linux ở mức hoạt động thực tế, đồng thời diễn giải từng bước mối quan hệ giữa không gian địa chỉ ảo và bộ nhớ vật lý
  • Tập trung vào các cơ chế cốt lõi như bảng trang, VMA, mmap, page fault, CoW để mô tả cụ thể cách tiến trình sở hữu và truy cập bộ nhớ
  • Giới thiệu cách quan sát trạng thái bộ nhớ theo từng tiến trình thông qua hệ thống tệp /proc, cùng vai trò của các công cụ chẩn đoán nâng cao như pagemap, kpageflags
  • Đề cập đến tối ưu hiệu năng và kỹ thuật theo dõi dirty trong không gian người dùng thông qua các tính năng kernel mới như Transparent Huge Pages (THP), userfaultfd, PAGEMAP_SCAN
  • Đồng thời giải thích các nguyên lý thiết kế kernel liên quan đến bảo mật và hiệu năng như PTI để ứng phó Meltdown, TLB flush, chính sách W^X, từ đó giúp hiểu toàn diện về quản lý bộ nhớ trên Linux

Cấu trúc cơ bản của bộ nhớ tiến trình

  • Khi chương trình chạy, có vẻ như tồn tại một vùng nhớ liên tục khổng lồ, nhưng trên thực tế kernel Linux động cấu thành nó theo đơn vị trang
    • CPU tra cứu bảng trang để chuyển đổi địa chỉ ảo thành frame vật lý
    • Nếu không có ánh xạ, sẽ xảy ra page fault và kernel sẽ cấp phát trang mới hoặc trả về lỗi
  • Khi RAM vật lý không đủ, kernel sẽ chuyển các trang không dùng tới ra đĩa hoặc loại bỏ các trang tệp để giải phóng không gian
  • /proc là một hệ thống tệp ảo do kernel dựng lên trong bộ nhớ, dùng để phơi bày trạng thái của tiến trình và kernel dưới dạng tệp

Không gian địa chỉ và VMA

  • Mỗi tiến trình có một đối tượng không gian địa chỉ duy nhất, bên trong gồm nhiều VMA (Virtual Memory Area)
    • VMA là một dải địa chỉ liên tục có cùng quyền hạn (R/W/X) và cùng backend (bộ nhớ ẩn danh hoặc tệp)
  • Bảng trang là cấu trúc mà phần cứng tham chiếu tới, lưu thông tin ánh xạ (PTE) giữa trang ảo và trang vật lý
  • Việc thay đổi không gian địa chỉ được thực hiện qua ba system call
    • mmap: tạo vùng mới
    • mprotect: thay đổi quyền
    • munmap: xóa ánh xạ
  • Trang có kích thước cơ bản 4KiB, một số hệ thống còn hỗ trợ trang lớn 2MiB và 1GiB

Xem cấu trúc bộ nhớ bằng /proc/self/maps

  • Có thể kiểm tra memory map của tiến trình bằng lệnh cat /proc/self/maps
    • Sẽ hiển thị mã thực thi, dữ liệu, bss, heap, ánh xạ ẩn danh, thư viện dùng chung, stack...
  • Các vùng [vdso][vvar]mã và dữ liệu cho system call tốc độ cao do kernel ánh xạ vào

Nguyên lý hoạt động của mmap

  • mmap không phải là cấp phát bộ nhớ thực ngay lập tức, mà là ghi lại một cam kết đối với không gian địa chỉ
    • Trang chỉ được cấp phát tại thời điểm truy cập đầu tiên
  • Khi ánh xạ tệp, offset phải được căn chỉnh theo trang; nếu truy cập vượt quá cuối tệp sẽ phát sinh SIGBUS
  • MAP_SHARED phản ánh trực tiếp vào tệp, còn MAP_PRIVATE sẽ tạo trang độc lập theo cơ chế copy-on-write (CoW) khi ghi
  • MAP_FIXED_NOREPLACE giúp đảm bảo an toàn bằng cách thất bại nếu địa chỉ chỉ định đã có ánh xạ

Lần truy cập đầu tiên và page fault

  • Khi truy cập lần đầu vào một ánh xạ mới, nếu CPU không tìm thấy mục tương ứng trong bảng trang thì sẽ xảy ra page fault
    • Kernel sẽ kiểm tra tính hợp lệ của địa chỉ, quyền truy cập và sự tồn tại
    • Nếu là ánh xạ ẩn danh thì cấp phát trang mới được điền số 0; nếu là ánh xạ tệp thì đọc từ page cache
  • minor fault là khi dữ liệu đã có sẵn trong RAM, còn major fault là khi cần I/O đĩa
  • Stack được bảo vệ bằng guard page, nên nếu truy cập quá xa xuống dưới sẽ phát sinh SIGSEGV

fork() và Copy-on-Write của MAP_PRIVATE

  • Khi fork, tiến trình cha và con cùng chia sẻ các trang vật lý giống nhau, và tất cả đều được đánh dấu chỉ đọc
    • Chỉ tại thời điểm ghi mới sao chép sang trang mới để duy trì tính độc lập
  • Ánh xạ tệp MAP_PRIVATE cũng hoạt động theo nguyên lý tương tự
  • Các tùy chọn liên quan
    • vfork: chia sẻ không gian địa chỉ của tiến trình cha
    • clone(CLONE_VM): tạo thread
    • MADV_DONTFORK, MADV_WIPEONFORK: loại trừ ánh xạ khỏi tiến trình con hoặc khởi tạo lại bằng 0

Thay đổi quyền và vô hiệu hóa TLB

  • Khi thay đổi quyền của trang bằng mprotect, kernel sẽ tách VMA và sửa bảng trang, sau đó thực hiện vô hiệu hóa TLB
  • Theo chính sách W^X, một trang không thể vừa ghi vừa thực thi cùng lúc
  • TLB (Translation Lookaside Buffer) là bộ đệm các phép chuyển đổi địa chỉ gần đây, nên việc vô hiệu hóa có thể gây ra độ trễ ngắn

Quan sát chi tiết qua /proc

  • Có thể dùng /proc/<pid>/maps, smaps, smaps_rollup để kiểm tra quyền của từng vùng, RSS và mức sử dụng HugePage
  • /proc/<pid>/pagemap cung cấp trạng thái theo đơn vị trang (tồn tại, swap, PFN...), nhưng PFN không công khai cho người dùng thông thường
  • /proc/kpagecount, /proc/kpageflags hiển thị số lượng ánh xạ và thuộc tính trang theo từng PFN (ẩn danh, tệp, dirty...)
  • Có thể dùng mincore, SEEK_DATA/SEEK_HOLE để nhận diện vùng dữ liệu/lỗ của sparse file
  • Có thể kết hợp PAGEMAP_SCAN với userfaultfd để triển khai theo dõi dirty trong không gian người dùng

Transparent Huge Pages (THP) và mTHP

  • THP tự động gộp các vùng nhớ được truy cập thường xuyên thành các trang lớn hơn (như 2MiB) để cải thiện hiệu quả của TLB
    • Thread khugepaged sẽ hợp nhất các trang liền kề
  • mTHP hỗ trợ trang lớn biến thiên (folio) với nhiều kích thước như 16KiB, 64KiB
  • Có thể kiểm tra việc sử dụng thông qua AnonHugePages, FilePmdMapped trong /proc/self/smaps
  • Quản lý cấu hình toàn hệ thống tại /sys/kernel/mm/transparent_hugepage/
  • Có thể điều khiển theo từng vùng bằng MADV_HUGEPAGE, MADV_NOHUGEPAGE

Theo dõi dirty trong không gian người dùng

  • Có thể dùng userfaultfdPAGEMAP_SCAN để chỉ sao chép những trang đã thay đổi
    • Kernel thực hiện quét và write-protect trong một phép toán nguyên tử duy nhất
    • Hiệu quả cho snapshot, live migration và các tình huống tương tự

Cơ chế TLB flush

  • Trên x86, việc vô hiệu hóa TLB có hai cách
    • INVLPG: vô hiệu hóa một trang đơn lẻ
    • Reload gốc bảng trang để flush toàn bộ
  • PCIDINVPCID giúp quản lý thẻ TLB theo từng tiến trình để giảm các lần flush không cần thiết
  • tlb_single_page_flush_ceiling là ngưỡng để kernel chọn giữa flush theo trang hay flush toàn bộ

Ứng phó Meltdown: Page Table Isolation (PTI)

  • Meltdown là lỗ hổng mà dữ liệu kernel có thể bị lộ qua cache trong quá trình thực thi suy đoán
  • Linux dùng PTI (Page Table Isolation) để tách không gian địa chỉ người dùng và kernel
    • Khi đi vào kernel sẽ chuyển CR3 để dùng bảng trang dành riêng cho kernel
    • Tận dụng PCID để giảm thiểu TLB flush
  • Tính năng này được bật mặc định và có thể tắt bằng nopti

Quy trình thay đổi ánh xạ an toàn của kernel

  • Khi thay đổi ánh xạ, thứ tự thực hiện là
    1. Xử lý quy tắc cache
    2. Sửa bảng trang
    3. Vô hiệu hóa TLB
  • Các ánh xạ nội bộ của kernel (vmap, vmalloc) cũng đồng bộ cache và TLB trước/sau I/O
  • Một số kiến trúc cần flush instruction cache sau khi sao chép mã

Cấu trúc stack và lời gọi hàm trên x86

  • Ở chế độ 64-bit, dùng các thanh ghi RIP, RSP, RBP, và stack tăng trưởng theo hướng xuống
  • Theo System V AMD64 ABI, tham số được truyền qua RDI, RSI, RDX, RCX, R8, R9, còn giá trị trả về nằm trong RAX
  • Chế độ người dùng là ring 3, kernel là ring 0, còn system call và interrupt chuyển đổi qua gate

Tình huống lỗi và chẩn đoán

  • mmapEINVAL: lỗi căn chỉnh file offset
  • mmapENOMEM: thiếu không gian ảo hoặc bị giới hạn overcommit
  • Truy cập ánh xạ tệp gây SIGBUS: truy cập vượt EOF
  • mprotect(PROT_EXEC)EACCES: mount noexec hoặc chính sách W^X
  • RSS tăng sau fork(): sao chép trang do CoW
  • Ghi đè ánh xạ hiện có bằng MAP_FIXED → nên dùng MAP_FIXED_NOREPLACE

Checklist thực tế

  • Cần có bộ nhớ ngay: mmap + PROT_READ|PROT_WRITE + MAP_PRIVATE|MAP_ANONYMOUS
  • Khi sinh mã: duy trì W^X, dùng mprotect(PROT_READ|PROT_EXEC)
  • Khi ánh xạ tệp: căn chỉnh offset theo trang, không truy cập vượt EOF
  • Khi có nhiều page fault: dùng MADV_WILLNEED hoặc truy cập trước
  • Phân tích sử dụng bộ nhớ: /proc/<pid>/smaps_rollup/proc/<pid>/maps
  • fork tiến trình lớn: cân nhắc CoW, dùng exec trong tiến trình con
  • Môi trường nhạy với độ trễ: theo dõi THP/mTHP, mlock, và hành vi TLB

1 bình luận

 
GN⁺ 2025-11-05
Ý kiến trên Hacker News
  • Rất thích những bài giải thích ngắn như thế này
    Dù là nội dung đã biết rồi, khi đọc vẫn giúp xác nhận lại một lần nữa

  • Khi thấy những cụm như “mmap, without the fog”, tôi lại có cảm giác đây là bài viết có LLM đồng tác giả, nên tự nhiên thấy bất an và khó chịu

    • Giọng văn nghe như kiểu đã nhờ Gemini giải thích cho dễ hiểu
      Lại còn có những cách diễn đạt kỳ lạ như “without the fog”, nên tạo cảm giác như chatgpt cũng góp phần viết vào
  • Đọc đoạn nói về instruction pipelining lại khiến tôi muốn quay về thời kiến trúc đơn giản như 6502 ngày xưa
    Khi đó mọi thứ hoạt động “đúng như nó là”, không cần ánh xạ hay proxy phức tạp
    Nếu có interconnect đủ nhanh, có lẽ ta vẫn có thể mơ về kiểu đơn giản đó thêm lần nữa

    • Dĩ nhiên tôi thừa nhận những “mẹo vặt” (cheats) này đã góp phần cải thiện hiệu năng
      Nhưng nhìn vào các vấn đề như Meltdown, Spectre thì rõ ràng cái giá của sự phức tạp gia tăng cũng không hề nhỏ
      Trong thời điểm hiện tại, khi định luật Moore đang chạm tới giới hạn, tôi tự hỏi liệu đánh đổi về độ phức tạp như vậy còn là lựa chọn tốt nhất hay không
    • Thực ra điều bài viết đang giải thích là khái niệm virtual memory và công nghệ này có trước 6502 khoảng 10 năm
    • Đúng là độ phức tạp đã tăng lên, nhưng những gì đạt được cũng rất nhiều
      Tôi không nghĩ sự đơn giản lúc nào cũng tốt hơn
    • Nhưng tôi cũng tò mò vì sao bạn lại nhớ kiểu đơn giản đó đến vậy
  • Trang web hiện thông báo là đã bị chặn vì thuộc miền nguy hiểm hoặc không an toàn

    • Có phải bạn đang dùng laptop công ty không? Bộ phận bảo mật của công ty có thể không tin tưởng các miền .xyz
    • Có lẽ phần mềm bảo mật đã hoạt động sai
      Kết quả kiểm tra trên VirusTotal cho thấy không có vấn đề gì
    • Có vẻ chỉ là một cảnh báo nhầm (false alarm)
    • Tôi tò mò không biết là trình duyệt nào đã chặn nó
    • Buồn cười thật lol
  • Tôi không rõ ý nói rằng báo cáo lỗi chỉ là “noise” là như thế nào