The Linux Programming Interface Chương 49 · Memory Mappings

Chương 49 · Ánh xạ bộ nhớ Chapter 49 · Memory Mappings

Đưa tệp và vùng nhớ trống thẳng vào không gian địa chỉ với mmap() Dùng mmap() để coi một tệp (hoặc vùng nhớ trống) như một mảng trong bộ nhớ Mapping files and blank memory straight into the address space with mmap()

Kerrisk mở đầu bằng lời gọi hệ thống mmap(), thứ tạo một ánh xạ bộ nhớ (memory mapping) mới trong không gian địa chỉ ảo của tiến trình gọi.12 Một ánh xạ có hai trục phân loại: nó là ánh xạ tệp (file mapping — đưa một vùng của tệp thẳng vào bộ nhớ) hay ánh xạ ẩn danh (anonymous mapping — không gắn với tệp, các trang được khởi tạo về 0); và nó riêng tư (MAP_PRIVATE) hay chia sẻ (MAP_SHARED). Chương đi qua: tạo ánh xạ bằng mmap(), gỡ bằng munmap(); các trường hợp biên khi tệp nhỏ hơn ánh xạ; đồng bộ với msync(); sao chép khi ghi (copy-on-write); ánh xạ ẩn danh; đổi kích thước bằng mremap(); cờ MAP_FIXED; ánh xạ phi tuyến; và thay đổi bảo vệ bằng mprotect().1

Thường để đọc một tệp, bạn gọi read() để bê từng khúc dữ liệu vào một ô nhớ. mmap() làm khác hẳn: nó bảo hệ điều hành "hãy để nguyên tệp đó xuất hiện như một mảng trong bộ nhớ của tôi". Từ đó bạn chỉ việc đọc/ghi các ô nhớ như biến bình thường, còn nhân lo chuyện trang nào cần nạp từ đĩa. Bạn cũng có thể xin một vùng nhớ trống, toàn số 0, mà không cần tệp nào. Nhớ nhé: mmap() biến một tệp (hoặc vùng nhớ trống) thành một mảng mà bạn truy cập trực tiếp.

Kerrisk opens with the mmap() system call, which creates a new memory mapping in the calling process's virtual address space.12 A mapping has two classification axes: it is a file mapping (mapping a region of a file directly into memory) or an anonymous mapping (no backing file, pages initialized to 0); and it is private (MAP_PRIVATE) or shared (MAP_SHARED). The chapter covers: creating mappings with mmap(), removing them with munmap(); boundary cases where the file is smaller than the mapping; synchronizing with msync(); copy-on-write; anonymous mappings; resizing with mremap(); the MAP_FIXED flag; nonlinear mappings; and changing protection with mprotect().1

Góc nhìn hệ thống: bộ nhớ của một ánh xạ trong tiến trình này có thể được chia sẻ với ánh xạ trong tiến trình khác — các mục trong bảng trang của mỗi tiến trình cùng trỏ về một trang RAM. Điều này xảy ra theo hai cách: khi hai tiến trình cùng ánh xạ một vùng của cùng một tệp, hoặc khi một tiến trình con sinh ra bởi fork() thừa kế bản sao các ánh xạ của cha. Ánh xạ bị mất khi tiến trình gọi exec() nhưng được thừa kế qua fork(); kiểu ánh xạ (MAP_PRIVATE hay MAP_SHARED) cũng được thừa kế. Mọi ánh xạ của một tiến trình đều hiện ra trong tệp đặc thù Linux /proc/PID/maps.1A system view: the memory of one process's mapping may be shared with another process's mapping — each process's page-table entries point to the same page of RAM. This happens in two ways: when two processes map the same region of the same file, or when a child created by fork() inherits copies of its parent's mappings. Mappings are lost when a process performs exec() but are inherited across fork(); the mapping type (MAP_PRIVATE or MAP_SHARED) is inherited too. All of a process's mappings are visible in the Linux-specific /proc/PID/maps file.1

Luận đề của chương: mmap() cho phép đặt một tệp — hoặc một vùng nhớ trống khởi tạo về 0 — thẳng vào không gian địa chỉ ảo, để truy cập bằng phép toán trên bộ nhớ thay vì read()/write(); hai cặp thuộc tính (tệp/ẩn danh × riêng tư/chia sẻ) sinh ra bốn loại ánh xạ, trong đó ánh xạ tệp chia sẻ phục vụ hai mục đích đinh: I/O ánh xạ bộ nhớ và IPC.1

Ý chính: mmap() biến tệp hoặc vùng nhớ trống thành một mảng bạn truy cập trực tiếp. Có bốn kiểu, nhưng kiểu đáng nhớ nhất là "ánh xạ tệp chia sẻ": vừa để đọc/ghi tệp qua bộ nhớ, vừa để nhiều tiến trình nói chuyện với nhau. Nhớ nhé: ánh xạ tệp chia sẻ dùng cho I/O qua bộ nhớ và cho giao tiếp giữa các tiến trình.

The chapter's thesis: mmap() lets you place a file — or a zero-initialized blank region — directly into the virtual address space, to be accessed by memory operations rather than read()/write(); the two attribute pairs (file/anonymous × private/shared) yield four mapping types, of which the shared file mapping serves the two headline purposes: memory-mapped I/O and IPC.1

Cụm khái niệm 01 Concept cluster 01

Hai trục phân loại sinh ra bốn kiểu ánh xạ — mỗi kiểu một công dụng Tệp hay trống, riêng hay chung: bốn kiểu ánh xạ bộ nhớ Two axes yield four mapping types — each with its own purpose

Một ánh xạ có thể thuộc một trong hai kiểu. Ánh xạ tệp đưa một vùng của tệp thẳng vào bộ nhớ ảo của tiến trình; sau khi ánh xạ, nội dung tệp được truy cập qua các byte trong vùng nhớ tương ứng, và các trang của ánh xạ được nhân nạp từ tệp khi cần. Ánh xạ ẩn danh không có tệp đi kèm — các trang được khởi tạo về 0 (có thể hình dung nó như ánh xạ của một "tệp ảo" mà nội dung luôn là số 0).1 Trục thứ hai quyết định ai thấy các thay đổi: MAP_PRIVATE giữ thay đổi riêng cho mỗi tiến trình (và với ánh xạ tệp thì không ghi ngược ra tệp), còn MAP_SHARED cho các tiến trình cùng ánh xạ thấy nhau và (với ánh xạ tệp) ghi ngược ra tệp.1

Tưởng tượng mmap() cho bạn hai câu hỏi. Thứ nhất: "Bạn muốn một tệp có thật, hay chỉ muốn một tờ giấy trắng toàn số 0?" — đó là tệp hay ẩn danh. Thứ hai: "Khi bạn viết lên đó, chỉ mình bạn thấy hay mọi người cùng thấy?" — đó là riêng tư hay chia sẻ. Hai câu hỏi, bốn tổ hợp. Nhớ nhé: tệp hay trống là một câu hỏi, riêng hay chung là câu hỏi kia.

A mapping can be one of two kinds. A file mapping maps a region of a file directly into the process's virtual memory; afterward the file's contents are accessed through the bytes of the corresponding memory region, and the mapping's pages are loaded by the kernel from the file as required. An anonymous mapping has no backing file — its pages are initialized to 0 (think of it as mapping a "virtual file" whose contents are always zero).1 The second axis decides who sees changes: MAP_PRIVATE keeps modifications private to each process (and, for a file mapping, does not carry them through to the file), while MAP_SHARED makes them visible to other processes sharing the mapping and (for a file mapping) carries them through to the file.1

Định nghĩa. Khi từ hai tiến trình trở lên cùng chia sẻ các trang (do cùng ánh xạ một tệp, hoặc do fork() thừa kế), mỗi tiến trình có thể thấy thay đổi của tiến trình khác — tùy ánh xạ là riêng tư hay chia sẻ. Với MAP_PRIVATE, các trang ban đầu được chia sẻ, nhưng khi một tiến trình ghi, nhân dùng kỹ thuật sao chép khi ghi (copy-on-write) tạo một bản riêng cho tiến trình đó.1

Nói đơn giản. Nhiều tiến trình có thể cùng dùng chung một trang nhớ để tiết kiệm. Nếu là kiểu "riêng tư" thì ngay khi ai đó định sửa, hệ thống lặng lẽ photo riêng cho người đó một bản — nên không ai dẫm chân nhau. Nhớ nhé: riêng tư = ai sửa thì được photo một bản riêng.

Definition. When two or more processes share pages (because they map the same file, or inherited them via fork()), each can see the others' changes — depending on whether the mapping is private or shared. With MAP_PRIVATE the pages are initially shared, but when a process writes, the kernel uses the copy-on-write technique to make a separate copy for that process.1

Bảng 49-1 · công dụng
        │ Tệp     │ Ẩn danh
riêng │ nạp từ tệp│ cấp bộ nhớ
chung│ I/O + IPC│ IPC (cùng họ)

4 ô:
riêng+tệp → nạp dữ liệu từ tệp
riêng+trống → xin bộ nhớ
chung+tệp → đọc/ghi + nói chuyện
chung+trống → nói chuyện (cùng họ)

Table 49-1 · purposes
       │ File    │ Anonymous
priv │ init mem │ allocation
shar │ I/O + IPC│ IPC (related)

  1. file vs anonymous

    Ánh xạ tệp có "kho giấy" là tệp; ẩn danh thì không Một kiểu gắn với tệp thật, một kiểu là giấy trắng A file mapping is backed by a file; an anonymous one is not

    Với ánh xạ tệp, tệp đóng vai trò nơi lưu trữ các trang: trang được nạp từ tệp khi lần đầu chạm tới, và (nếu chia sẻ) thay đổi được ghi ngược ra tệp. Với ánh xạ ẩn danh, không có tệp — các trang đơn giản là vùng nhớ khởi tạo về 0.1 Một công dụng khác của mmap() là với đối tượng bộ nhớ chia sẻ POSIX, cho phép chia sẻ một vùng nhớ giữa các tiến trình không cùng họ mà không cần tệp trên đĩa (xem Chương 54 trong sách).1

    Ánh xạ tệp giống như mở một quyển sổ có sẵn chữ: đọc được chữ cũ, viết thêm thì lưu lại vào sổ. Ánh xạ ẩn danh giống như lấy một tờ giấy trắng mới tinh: không có chữ cũ, viết gì thì đó, và không lưu vào sổ nào. Nhớ nhé: ánh xạ tệp có sổ để lưu, ẩn danh chỉ là giấy nháp.

    For a file mapping, the file acts as the paging store: pages load from the file on first access and (if shared) changes are written back to it. For an anonymous mapping there is no file — the pages are simply a zero-initialized region of memory.1 Another use of mmap() is with POSIX shared memory objects, which let unrelated processes share a region without a disk file (see the book's Chapter 54).1

    Bạn gặp nó ở đâuWhere you meet it Dòng mã khởi động của mọi chương trình: bộ nạp và trình liên kết động dùng ánh xạ tệp riêng tư để nạp segment text và data từ tệp thực thi và thư viện chia sẻ.1 Mỗi lần chạy một chương trình — hệ thống đã lặng lẽ dùng ánh xạ tệp để nạp mã lệnh vào bộ nhớ. At the start of every program: the loader and dynamic linker use private file mappings to load the text and data segments from the executable and shared libraries.1
  2. MAP_PRIVATE / MAP_SHARED

    Riêng tư là sao chép khi ghi; chia sẻ là cùng thấy Sửa riêng mình biết, hay sửa ai cũng biết Private is copy-on-write; shared is seen by all

    Với MAP_PRIVATE, thay đổi không hiện ra với tiến trình khác và (với ánh xạ tệp) không ghi ngược ra tệp — vì thế nó còn gọi là ánh xạ riêng tư, sao chép khi ghi. Với MAP_SHARED, thay đổi hiện ra với mọi tiến trình cùng ánh xạ vùng đó bằng MAP_SHARED, và (với ánh xạ tệp) được ghi ngược ra tệp — tuy việc cập nhật tệp không đảm bảo tức thì (xem msync()).12

    Kiểu "riêng tư": bạn viết lên bản photo của mình, không ai khác thấy, tệp gốc giữ nguyên. Kiểu "chia sẻ": mọi người viết lên cùng một tờ, ai cũng thấy, và với tệp thì những gì viết sẽ được lưu lại. Nhớ nhé: riêng tư thì một mình bạn thấy, chia sẻ thì cả nhóm cùng thấy.

    With MAP_PRIVATE, modifications are invisible to other processes and (for a file mapping) are not carried through to the file — hence it is called a private, copy-on-write mapping. With MAP_SHARED, modifications are visible to all processes mapping that region with MAP_SHARED, and (for a file mapping) are carried through to the file — though updates to the file are not guaranteed to be immediate (see msync()).12

    Bạn gặp nó ở đâuWhere you meet it Đúng một trong hai cờ MAP_PRIVATE hoặc MAP_SHARED bắt buộc có mặt trong đối số flags của mọi lời gọi mmap() — quên là lỗi lập trình.12 Mỗi lần gọi mmap() đều phải chọn đúng một trong hai: riêng tư hay chia sẻ — không được bỏ trống. Exactly one of MAP_PRIVATE or MAP_SHARED must appear in the flags argument of every mmap() call — omitting it is a programming error.12

    Cơ chế sao chép khi ghiThe copy-on-write mechanism

    Trang của ánh xạ MAP_PRIVATE ban đầu được chia sẻ. Khi một tiến trình định sửa nội dung một trang, nhân trước tiên tạo một bản sao mới, riêng biệt của trang đó cho tiến trình (và chỉnh lại bảng trang của nó). Vì vậy thay đổi vẫn riêng với mỗi tiến trình dù ban đầu các trang được dùng chung.1The pages of a MAP_PRIVATE mapping are initially shared. When a process attempts to modify a page's contents, the kernel first makes a new, separate copy of that page for the process (and adjusts its page tables). So changes stay private to each process even though the pages were initially shared.1

    Thừa kế qua fork()Inheritance across fork()

    Một tiến trình con sinh ra bởi fork() thừa kế bản sao các ánh xạ của cha, trỏ về cùng các trang RAM. Với ánh xạ ẩn danh MAP_SHARED, cha và con dùng chung chính các trang đó (không sao chép khi ghi), nên thấy thay đổi của nhau — nền cho IPC giữa các tiến trình cùng họ.1A child created by fork() inherits copies of its parent's mappings, referring to the same RAM pages. For a MAP_SHARED anonymous mapping, parent and child share those very pages (not copied-on-write), so each sees the other's changes — the basis for IPC between related processes.1

  3. the four combinations

    Bốn ô của Bảng 49-1 và công dụng của từng ô Bốn tổ hợp, bốn việc khác nhau Table 49-1's four cells and what each is for

    Ánh xạ tệp riêng tư: khởi tạo một vùng nhớ từ nội dung tệp (ví dụ nạp segment text và data). Ánh xạ ẩn danh riêng tư: cấp vùng nhớ mới toàn số 0 (ví dụ malloc() dùng khi cấp khối lớn). Ánh xạ tệp chia sẻ: cho I/O ánh xạ bộ nhớ và cho IPC giữa các tiến trình không cùng họ. Ánh xạ ẩn danh chia sẻ: cho IPC nhưng chỉ giữa các tiến trình cùng họ.1

    Bốn ô như bốn công cụ: nạp dữ liệu từ tệp; xin bộ nhớ trống; đọc/ghi tệp qua bộ nhớ và cho các chương trình nói chuyện; và cho các chương trình cùng họ nói chuyện qua vùng nhớ chung. Nhớ nhé: bốn tổ hợp phục vụ bốn việc, nhớ nhất là ô "tệp + chia sẻ".

    Private file mapping: initialize a memory region from a file's contents (e.g. load text and data segments). Private anonymous mapping: allocate new zero-filled memory (e.g. malloc() for large blocks). Shared file mapping: for memory-mapped I/O and for IPC between unrelated processes. Shared anonymous mapping: for IPC but only between related processes.1

    Bạn gặp nó ở đâuWhere you meet it Điểm phân biệt IPC qua ánh xạ tệp chia sẻ với bộ nhớ chia sẻ System V (Chương 48): nội dung được ghi ngược ra tệp, nên tồn tại qua các lần khởi động lại ứng dụng hay hệ thống.1 Khác với bộ nhớ chia sẻ thông thường: dùng tệp chia sẻ thì dữ liệu còn nguyên cả sau khi tắt rồi bật lại. What distinguishes shared-file-mapping IPC from System V shared memory (Chapter 48): contents are carried through to the file, so they persist across restarts of the application or system.1

Cụm khái niệm 02 Concept cluster 02

Sáu đối số của mmap(), quyền bảo vệ PROT_*, và phép gỡ munmap() Gọi mmap() với sáu đối số, và gỡ bằng munmap() The six arguments of mmap(), the PROT_* protections, and munmap()

mmap() nhận sáu đối số: addr (địa chỉ gợi ý, nên để NULL để nhân tự chọn), length (kích thước ánh xạ tính theo byte), prot (mặt nạ bit quyền bảo vệ), flags (tùy chọn, bắt buộc có MAP_PRIVATE hoặc MAP_SHARED), fd (bộ mô tả tệp — chỉ dùng cho ánh xạ tệp), và offset (điểm bắt đầu trong tệp, phải là bội của kích thước trang). Thành công trả về địa chỉ bắt đầu của ánh xạ; thất bại trả về MAP_FAILED.128

Hãy coi mmap() như một phiếu đặt chỗ gồm sáu ô cần điền: đặt ở đâu (thường để trống cho hệ thống chọn), đặt bao lớn, cho phép làm gì (đọc/ghi/chạy), kiểu gì (riêng hay chung), từ tệp nào, và bắt đầu từ chỗ nào trong tệp. Điền xong, hệ thống trả về địa chỉ bắt đầu. Nhớ nhé: mmap() là phiếu đặt chỗ sáu ô; để ô địa chỉ trống cho hệ thống tự lo.

mmap() takes six arguments: addr (a hint address, best left NULL so the kernel chooses), length (the mapping size in bytes), prot (a bit mask of protections), flags (options, requiring MAP_PRIVATE or MAP_SHARED), fd (a file descriptor — used only for file mappings), and offset (the starting point in the file, which must be a multiple of the page size). On success it returns the mapping's start address; on failure it returns MAP_FAILED.128

Định nghĩa. length không cần là bội của kích thước trang hệ thống (lấy qua sysconf(_SC_PAGESIZE)), nhưng nhân tạo ánh xạ theo đơn vị trang, nên length trên thực tế được làm tròn lên bội kế tiếp của kích thước trang. Riêng offset thì bắt buộc là bội của kích thước trang.12

Nói đơn giản. Bộ nhớ được quản lý theo từng "trang" bằng nhau (thường 4096 byte). Dù bạn xin một con số lẻ, hệ thống vẫn làm tròn lên cho đủ trang. Nhớ nhé: mọi thứ ở đây đều tính theo trang, số lẻ sẽ được làm tròn lên.

Definition. length need not be a multiple of the system page size (from sysconf(_SC_PAGESIZE)), but the kernel creates mappings in page-sized units, so length is effectively rounded up to the next multiple of the page size. The offset, however, must be a multiple of the page size.12

#include <sys/mman.h>
void *mmap(void *addr, size_t length,
  int prot, int flags, int fd, off_t offset);
/* PROT_NONE | PROT_READ |
   PROT_WRITE | PROT_EXEC */
int munmap(void *addr, size_t length);

mmap(địa_chỉ, cỡ, quyền,
  kiểu, tệp, điểm_bắt_đầu)
quyền: đọc / ghi / chạy / cấm
munmap(địa_chỉ, cỡ) → gỡ bỏ

#include <sys/mman.h>
void *mmap(void *addr, size_t length,
  int prot, int flags, int fd, off_t offset);
/* PROT_NONE | PROT_READ |
   PROT_WRITE | PROT_EXEC */
int munmap(void *addr, size_t length);

  1. addr = NULL

    Hãy để nhân chọn địa chỉ — đừng ép Để trống ô địa chỉ cho hệ thống tự chọn Let the kernel pick the address — don't force it

    Nếu addr là NULL, nhân chọn một địa chỉ phù hợp — đây là cách nên dùng. Nếu đưa giá trị khác NULL, nhân chỉ coi đó là gợi ý và ít nhất sẽ làm tròn về biên trang gần đó; trong cả hai trường hợp nhân chọn địa chỉ không đụng độ với ánh xạ sẵn có. (Nếu có MAP_FIXED trong flags thì addr phải căn theo biên trang và được hiểu đúng y nguyên.)12

    Giống đặt bàn ở quán: bạn cứ để nhân viên xếp chỗ còn trống, đừng khăng khăng "tôi phải ngồi đúng bàn số 7". Để hệ thống chọn địa chỉ thì an toàn và dễ mang đi máy khác. Nhớ nhé: để NULL cho nhân chọn chỗ — đó là cách đúng.

    If addr is NULL, the kernel chooses a suitable address — this is the preferred way. If you pass a non-NULL value, the kernel takes it only as a hint and will at least round it to a nearby page boundary; in either case it chooses an address that doesn't conflict with an existing mapping. (If MAP_FIXED is in flags, addr must be page-aligned and is taken exactly.)12

    Bạn gặp nó ở đâuWhere you meet it Trên Linux (và hầu hết UNIX), MAP_FAILED bằng ((void *) -1); SUSv3 vẫn định nghĩa hằng này vì chuẩn C không đảm bảo ((void *) -1) khác mọi giá trị trả về hợp lệ.1 Luôn kiểm tra kết quả: nếu bằng MAP_FAILED thì ánh xạ hỏng, đừng dùng tiếp. On Linux (and most UNIX), MAP_FAILED equals ((void *) -1); SUSv3 still defines the constant because C can't guarantee ((void *) -1) differs from every valid return value.1
  2. PROT_* · SIGSEGV / SIGBUS

    Quyền bảo vệ: đọc, ghi, chạy — hoặc cấm tiệt Mỗi vùng được gắn quyền: đọc, ghi, chạy, hay cấm Protections: read, write, execute — or none at all

    Đối số prot là mặt nạ bit: hoặc PROT_NONE (không được truy cập), hoặc tổ hợp OR của PROT_READ, PROT_WRITE, PROT_EXEC. Nếu một tiến trình truy cập trái với quyền đã đặt, nhân gửi tín hiệu SIGSEGV cho nó.12 Quyền nằm trong bảng bộ nhớ ảo riêng của mỗi tiến trình, nên các tiến trình khác nhau có thể ánh xạ cùng một vùng với quyền khác nhau.1

    Mỗi vùng nhớ được dán một tấm biển: "được đọc", "được ghi", "được chạy", hoặc "cấm vào". Nếu chương trình làm trái tấm biển — ví dụ ghi vào vùng chỉ cho đọc — hệ thống lập tức báo lỗi và thường làm chương trình dừng. Nhớ nhé: làm trái tấm biển quyền thì nhận ngay tín hiệu lỗi.

    The prot argument is a bit mask: either PROT_NONE (no access) or an OR of PROT_READ, PROT_WRITE, PROT_EXEC. If a process accesses the region in a way that violates its protection, the kernel delivers SIGSEGV to it.12 Protections live in each process's private virtual-memory tables, so different processes may map the same region with different protections.1

    Bạn gặp nó ở đâuWhere you meet it Một công dụng của trang gắn PROT_NONE là làm trang canh (guard page) ở đầu hay cuối một vùng cấp phát: dẫm nhầm vào nó là nhận SIGSEGV ngay, giúp bắt lỗi tràn sớm.1 Người ta hay đặt một trang "cấm vào" ở rìa vùng nhớ để bẫy lỗi: chạm vào là báo động ngay. One use of PROT_NONE pages is as guard pages at the start or end of an allocation: stepping into one yields an immediate SIGSEGV, catching overruns early.1

    SIGSEGV hay SIGBUS?SIGSEGV or SIGBUS?

    SUSv3 quy định dùng SIGSEGV để báo vi phạm bảo vệ bộ nhớ, nhưng một số hệ dùng SIGBUS thay cho nó. Hành động mặc định của SIGSEGV là kết thúc tiến trình kèm kết xuất lõi (core dump).1SUSv3 specifies SIGSEGV for memory-protection violations, but some implementations use SIGBUS instead. The default action for SIGSEGV is to terminate the process with a core dump.1

    Độ mịn của phần cứngHardware granularity

    Trên vài kiến trúc (x86-32 đời cũ), độ mịn quyền hạn chế khiến PROT_READ kéo theo PROT_EXEC và ngược lại; ứng dụng không nên dựa vào điều đó mà hãy ghi đúng quyền cần. Từ nhân 2.6.8, Linux dùng bit NX của x86-32 hiện đại để tách bạch PROT_READ và PROT_EXEC.1On some architectures (older x86-32), limited granularity made PROT_READ imply PROT_EXEC and vice versa; applications should not rely on that but specify exactly the protections needed. Since kernel 2.6.8, Linux uses modern x86-32's NX bit to properly separate PROT_READ and PROT_EXEC.1

  3. munmap()

    munmap() gỡ ánh xạ — và có thể cắt một phần munmap() trả lại vùng nhớ đã ánh xạ munmap() removes a mapping — and can cut a slice

    munmap() làm điều ngược với mmap(): gỡ một ánh xạ khỏi không gian địa chỉ. addr phải căn theo biên trang; length được làm tròn lên bội kế tiếp của kích thước trang. Thường ta gỡ cả ánh xạ (truyền chính addr và length của lời mmap() trước), nhưng cũng có thể gỡ một phần — khi đó ánh xạ co lại hoặc bị cắt làm đôi tùy chỗ gỡ. Nếu khoảng chỉ ra không có ánh xạ nào, munmap() không làm gì và trả về 0.123

    Gỡ ánh xạ là trả lại chỗ đã mượn. Bạn có thể trả hết, hoặc chỉ trả một khúc ở giữa — khi đó phần còn lại có thể bị chia thành hai mảnh. Khi tiến trình kết thúc hay gọi exec(), mọi ánh xạ tự động được gỡ. Nhớ nhé: munmap() trả lại chỗ; có thể trả cả hoặc trả một khúc.

    munmap() does the converse of mmap(): it removes a mapping from the address space. addr must be page-aligned; length is rounded up to the next multiple of the page size. We usually unmap the whole mapping (passing the same addr and length as the earlier mmap()), but we can unmap part of one — the mapping then shrinks or is cut in two depending on where. If the range has no mappings, munmap() does nothing and returns 0.123

    Bạn gặp nó ở đâuWhere you meet it Để chắc nội dung một ánh xạ tệp chia sẻ đã được ghi ra tệp, hãy gọi msync() trước khi gỡ bằng munmap() — gỡ không tự động buộc dữ liệu xuống đĩa.1 Trước khi gỡ một ánh xạ tệp chia sẻ, nhớ gọi msync() để chắc dữ liệu đã lưu xuống đĩa. To ensure a shared file mapping's contents reached the file, call msync() before unmapping with munmap() — unmapping does not force data to disk.1

Cụm khái niệm 03 Concept cluster 03

Ánh xạ tệp: căn trang, trường hợp biên, và đồng bộ với msync() Tệp ngắn hơn vùng ánh xạ thì sao, và lưu xuống đĩa thế nào File mappings: page alignment, boundary cases, and msync()

Tạo ánh xạ tệp gồm hai bước: mở tệp lấy bộ mô tả (thường qua open()), rồi truyền bộ mô tả đó làm fd cho mmap(). Sau khi mmap() được gọi, có thể đóng bộ mô tả tệp mà không ảnh hưởng ánh xạ.1 Tệp phải được mở với quyền phù hợp: luôn phải mở để đọc; và nếu dùng PROT_WRITE cùng MAP_SHARED thì phải mở tệp để cả đọc lẫn ghi.1 Công dụng lớn của ánh xạ tệp chia sẻ là I/O ánh xạ bộ nhớ: tệp được nạp vào một vùng bộ nhớ ảo, và sửa đổi vùng đó được tự động ghi ra tệp — một lựa chọn thay cho read()/write().1

Để ánh xạ một tệp: mở tệp ra, rồi đưa nó cho mmap(). Xong là bạn có thể đóng tệp mà ánh xạ vẫn sống. Nếu muốn ghi và cho người khác thấy, phải mở tệp ở chế độ đọc-ghi. Mẹo hay nhất: thay vì read() từng khúc, bạn chỉ việc đọc/ghi các ô nhớ, còn hệ thống lo chuyện lưu ra tệp. Nhớ nhé: ánh xạ tệp chia sẻ cho phép đọc/ghi tệp như thể đọc/ghi một mảng.

Creating a file mapping takes two steps: obtain a descriptor for the file (usually via open()), then pass it as fd to mmap(). Once mmap() is called, you can close the descriptor without affecting the mapping.1 The file must be opened with appropriate permissions: always for reading; and if you use PROT_WRITE with MAP_SHARED, the file must be opened for both reading and writing.1 The big use of a shared file mapping is memory-mapped I/O: the file is loaded into a region of virtual memory, and modifications to it are automatically written to the file — an alternative to read()/write().1

Định nghĩa. Nhân tự động ghi sửa đổi của ánh xạ MAP_SHARED ra tệp, nhưng mặc định không đảm bảo thời điểm. msync() trao cho ứng dụng quyền kiểm soát tường minh thời điểm đồng bộ — ví dụ một ứng dụng cơ sở dữ liệu gọi msync() để buộc dữ liệu xuống đĩa nhằm đảm bảo toàn vẹn.14

Nói đơn giản. Khi bạn sửa vùng nhớ chia sẻ, hệ thống sẽ lưu ra tệp, nhưng không nói trước lúc nào. Muốn chắc "lưu ngay bây giờ", hãy gọi msync(). Nhớ nhé: msync() là nút "lưu ngay" cho ánh xạ tệp.

Definition. The kernel automatically carries a MAP_SHARED mapping's modifications through to the file, but by default gives no timing guarantee. msync() gives the application explicit control over when synchronization happens — e.g. a database application calls msync() to force data to disk for integrity.14

#include <sys/mman.h>
int msync(void *addr, size_t length,
           int flags);
MS_SYNC   → ghi đồng bộ, chờ xong
MS_ASYNC  → ghi bất đồng bộ
MS_INVALIDATE → bỏ bản nhớ cũ

msync(địa_chỉ, cỡ, cờ)
MS_SYNC → lưu ngay, chờ xong
MS_ASYNC → lưu sau, không chờ

#include <sys/mman.h>
int msync(void *addr, size_t length,
           int flags);
MS_SYNC   → synchronous write, blocks
MS_ASYNC  → asynchronous write
MS_INVALIDATE → drop cached copies

  1. boundary cases

    Quá cuối ánh xạ: SIGSEGV; quá cuối tệp: SIGBUS Chạm ra ngoài vùng hay ngoài tệp đều bị báo lỗi Past the mapping: SIGSEGV; past the file: SIGBUS

    Vì ánh xạ tạo theo đơn vị trang, length được làm tròn lên. Khi ánh xạ vượt quá cuối tệp, các byte trong phần trang được làm tròn nhưng không có byte tương ứng trong tệp sẽ truy cập được và khởi tạo về 0 (SUSv3 yêu cầu vậy); sửa chúng không ghi ra tệp. Truy cập các trang xa hơn vùng làm tròn — vẫn trong ánh xạ nhưng không có vùng tệp tương ứng — sinh SIGBUS. Truy cập vượt hẳn cuối ánh xạ sinh SIGSEGV.1

    Hình dung tệp dài 2200 byte nhưng bạn xin ánh xạ 8192 byte. Phần dư đến hết trang (tới byte 4095) là số 0, đọc/ghi được nhưng không lưu vào tệp. Chạm vào các trang xa hơn (từ 4096) mà không có trong tệp → lỗi SIGBUS. Chạm hẳn ra ngoài ánh xạ → lỗi SIGSEGV. Nhớ nhé: ra ngoài tệp là một kiểu lỗi, ra ngoài ánh xạ là kiểu lỗi khác.

    Because mappings are made in page units, length is rounded up. When a mapping extends beyond the end of the file, the bytes in the rounded-up page that have no corresponding file bytes are accessible and initialized to 0 (SUSv3 requires this); changes to them are not written to the file. Accessing pages beyond that rounded-up region — still within the mapping but with no corresponding file region — raises SIGBUS. Accessing past the end of the mapping raises SIGSEGV.1

    Bạn gặp nó ở đâuWhere you meet it Có vẻ vô nghĩa khi ánh xạ lớn hơn tệp, nhưng bằng cách mở rộng tệp sau đó (ví dụ ftruncate() hay write()), ta biến những phần trước đó không chạm được thành dùng được.1 Mẹo: ánh xạ sẵn vùng lớn, rồi nới tệp dài ra dần — phần mới sẽ tự dùng được. Mapping larger than the file seems pointless, but by later extending the file (e.g. with ftruncate() or write()) you render previously inaccessible parts usable.1
  2. memory-mapped I/O

    Vì sao I/O qua ánh xạ đôi khi nhanh hơn Đọc/ghi tệp qua bộ nhớ: khi nào lợi Why mapped I/O can be faster — sometimes

    Một lời read()/write() thường gồm hai lần chuyển: giữa tệp và bộ đệm nhân, rồi giữa bộ đệm nhân và bộ đệm không gian người dùng. mmap() loại bỏ lần chuyển thứ hai, và khi nhiều tiến trình cùng I/O trên một tệp, chúng có thể chia sẻ cùng bộ đệm nhân — tiết kiệm thêm bộ nhớ.1 Lợi ích rõ nhất khi truy cập ngẫu nhiên lặp lại trong một tệp lớn; với truy cập tuần tự, mmap() thường lợi rất ít so với read()/write() dùng bộ đệm đủ lớn.1

    Cách thường: dữ liệu phải đi qua hai trạm trung chuyển mới tới tay bạn. Dùng ánh xạ bộ nhớ: bỏ bớt một trạm, nên có lúc nhanh hơn — nhất là khi bạn nhảy đọc lung tung khắp một tệp lớn. Nhưng đọc tuần tự từ đầu tới cuối thì gần như không lợi. Nhớ nhé: ánh xạ bộ nhớ lợi nhất khi nhảy đọc ngẫu nhiên trong tệp lớn.

    A read()/write() usually involves two transfers: between the file and the kernel buffer cache, and between that cache and a user-space buffer. mmap() eliminates the second, and when several processes do I/O on one file they can share the same kernel buffer — saving more memory.1 The benefit is clearest for repeated random access in a large file; for sequential access, mmap() usually gains little over read()/write() with big enough buffers.1

    Bạn gặp nó ở đâuWhere you meet it Mục đích thứ hai của ánh xạ tệp chia sẻ là IPC: các tiến trình không cùng họ chia sẻ một vùng nhớ; khác với bộ nhớ chia sẻ System V ở chỗ nội dung được ghi ra tệp nên tồn tại lâu dài.1 Ngoài đọc/ghi, ánh xạ tệp chia sẻ còn để các chương trình nói chuyện với nhau qua vùng nhớ chung. A shared file mapping's second purpose is IPC: unrelated processes share a memory region; unlike System V shared memory, its contents go to the file and so persist.1

    Khi nào I/O ánh xạ bộ nhớ không lợiWhen mapped I/O does not pay

    Với các I/O nhỏ, chi phí ánh xạ, lỗi trang, gỡ ánh xạ và cập nhật TLB của bộ quản lý bộ nhớ có thể cao hơn một read()/write() đơn giản. Ngoài ra, nhân đôi khi khó xử lý ghi-ngược hiệu quả cho các ánh xạ ghi được.1For small I/Os, the cost of mapping, page faulting, unmapping, and updating the memory management unit's TLB can exceed a simple read()/write(). The kernel can also find it hard to handle write-back efficiently for writable mappings.1

    Bộ nhớ ảo hợp nhấtUnified virtual memory

    Linux dùng hệ bộ nhớ ảo hợp nhất: nơi nào có thể, ánh xạ bộ nhớ và các khối của bộ đệm nhân cùng chia sẻ một trang vật lý. Vì thế góc nhìn tệp qua ánh xạ và qua read()/write() luôn nhất quán, và msync() chỉ còn dùng để buộc dữ liệu xuống đĩa.1Linux uses a unified virtual memory system: where possible, memory mappings and buffer-cache blocks share the same physical page. So the views of a file via a mapping and via read()/write() are always consistent, and msync()'s only remaining use is to force contents to disk.1

  3. MS_SYNC / MS_ASYNC

    msync(): đồng bộ chờ xong, hay bất đồng bộ Lưu ngay và chờ, hay dặn lưu rồi đi tiếp msync(): block until done, or fire and continue

    addr của msync() phải căn trang, length được làm tròn lên. Đối số flags gồm một trong hai: MS_SYNC ghi đồng bộ — lời gọi chặn tới khi mọi trang đã sửa được ghi xuống đĩa; MS_ASYNC ghi bất đồng bộ — các trang sẽ được ghi xuống sau nhưng lập tức hiển thị cho tiến trình khác read() tệp. Nói cách khác: sau MS_SYNC vùng nhớ đã đồng bộ với đĩa, còn sau MS_ASYNC nó mới đồng bộ với bộ đệm nhân.14

    Hai kiểu lưu: MS_SYNC là "lưu xong tôi mới làm việc khác" — chờ đĩa ghi hẳn. MS_ASYNC là "cứ lưu đi, tôi đi tiếp" — không chờ. Nhớ nhé: MS_SYNC thì chờ, MS_ASYNC thì không chờ.

    msync()'s addr must be page-aligned and length is rounded up. The flags argument takes one of: MS_SYNC for a synchronous write — the call blocks until all modified pages are on disk; MS_ASYNC for an asynchronous write — pages are written later but are immediately visible to other processes that read() the file. Put differently: after MS_SYNC the region is synchronized with the disk, after MS_ASYNC merely with the kernel buffer cache.14

    Bạn gặp nó ở đâuWhere you meet it Cờ thêm MS_INVALIDATE làm mất hiệu lực các bản nhớ cũ không khớp với tệp; lần truy cập sau sẽ nạp lại từ tệp — cách để thấy được cập nhật mà tiến trình khác đã ghi vào tệp.1 Còn một cờ nữa, MS_INVALIDATE, bảo "quên bản cũ đi, đọc lại từ tệp" để thấy thay đổi của người khác. The extra flag MS_INVALIDATE invalidates cached copies inconsistent with the file; the next access reloads from it — a way to see updates another process wrote to the file.1

Cụm khái niệm 04 Concept cluster 04

Ánh xạ ẩn danh, đổi kích thước mremap(), MAP_FIXED và mprotect() Vùng nhớ trống, đổi kích thước, đặt địa chỉ và đổi quyền Anonymous mappings, mremap(), MAP_FIXED, and mprotect()

Ánh xạ ẩn danh là ánh xạ không có tệp đi kèm. Trên Linux có hai cách tương đương để tạo: (1) đặt MAP_ANONYMOUS trong flags và đặt fd là −1; hoặc (2) mở tệp thiết bị /dev/zero rồi truyền bộ mô tả đó cho mmap(). Cả hai cách đều cho các byte khởi tạo về 0, và đối số offset bị bỏ qua.1 Linux còn cung cấp MAP_ANON như tên đồng nghĩa của MAP_ANONYMOUS để tương thích với vài hệ UNIX khác.1

Ánh xạ ẩn danh là xin một vùng nhớ trống tinh, toàn số 0, không dính tới tệp nào. Có hai cách gọi nhưng kết quả như nhau: một cách dùng cờ MAP_ANONYMOUS, một cách mở "vòi nước số 0" tên /dev/zero — thiết bị đọc ra lúc nào cũng toàn số 0. Nhớ nhé: ánh xạ ẩn danh = xin vùng nhớ trống toàn số 0.

An anonymous mapping has no backing file. On Linux there are two equivalent ways to create one: (1) specify MAP_ANONYMOUS in flags and set fd to −1; or (2) open the /dev/zero device file and pass its descriptor to mmap(). Both give zero-initialized bytes, and the offset argument is ignored.1 Linux also provides MAP_ANON as a synonym for MAP_ANONYMOUS for compatibility with some other UNIX systems.1

Định nghĩa. Ánh xạ ẩn danh MAP_PRIVATE dùng để cấp các khối bộ nhớ riêng của tiến trình, khởi tạo về 0. Bản cài malloc() của glibc dùng chính loại ánh xạ này để cấp các khối lớn hơn MMAP_THRESHOLD byte (mặc định 128 kB, chỉnh được qua mallopt()) — nhờ vậy có thể giải phóng gọn gàng bằng munmap() khi gọi free().1

Nói đơn giản. Khi bạn xin một khối bộ nhớ thật lớn bằng malloc(), bên trong nó thường gọi mmap() để lấy một vùng ẩn danh riêng. Khối lớn thì trả lại dễ dàng. Nhớ nhé: malloc() khối lớn thực ra là mmap() một vùng ẩn danh.

Definition. A MAP_PRIVATE anonymous mapping allocates blocks of process-private, zero-initialized memory. The glibc malloc() uses exactly this to allocate blocks larger than MMAP_THRESHOLD bytes (default 128 kB, adjustable via mallopt()) — so they can be cleanly freed with munmap() on free().1

/* IPC cha–con */
addr = mmap(NULL, len,
  PROT_READ | PROT_WRITE,
  MAP_SHARED | MAP_ANONYMOUS, -1, 0);
fork(); /* con thừa kế → dùng chung */

void *mremap(old, old_sz, new_sz, flags);
int mprotect(addr, len, prot);

mmap(..., MAP_SHARED |
  MAP_ANONYMOUS, -1, 0)
rồi fork() → cha con dùng chung
mremap → đổi kích thước
mprotect → đổi quyền

/* parent-child IPC */
addr = mmap(NULL, len,
  PROT_READ | PROT_WRITE,
  MAP_SHARED | MAP_ANONYMOUS, -1, 0);
fork(); /* child inherits → shared */

void *mremap(old, old_sz, new_sz, flags);
int mprotect(addr, len, prot);

  1. MAP_ANONYMOUS · /dev/zero

    Vùng trống cho malloc() và cho IPC cha–con Hai công dụng: cấp bộ nhớ và cho cha con nói chuyện Blank memory for malloc() and parent-child IPC

    Ánh xạ ẩn danh MAP_SHARED cho các tiến trình cùng họ (cha và con) chia sẻ một vùng nhớ mà không cần tệp. Nếu sau khi tạo ánh xạ ta gọi fork(), con thừa kế ánh xạ nên cả hai cùng dùng chung vùng nhớ — thay đổi của bên này bên kia thấy ngay (không sao chép khi ghi).1 Ánh xạ ẩn danh MAP_SHARED chỉ có từ Linux 2.4 trở đi.1

    Hai lý do để dùng vùng trống: một là để malloc() lấy chỗ chứa dữ liệu; hai là để cha và con, sau khi tách ra bằng fork(), cùng viết lên một vùng nhớ chung và thấy được của nhau — như hai người cùng ghi lên một cái bảng. Nhớ nhé: vùng ẩn danh chia sẻ là cái bảng chung cho cha và con.

    A MAP_SHARED anonymous mapping lets related processes (parent and child) share a memory region without a file. If we fork() after creating the mapping, the child inherits it so both share the region — each sees the other's changes at once (not copied-on-write).1 MAP_SHARED anonymous mappings exist only from Linux 2.4 onward.1

    Bạn gặp nó ở đâuWhere you meet it Lý do có hai cách tạo trùng ngữ nghĩa: MAP_ANONYMOUS bắt nguồn từ BSD, còn /dev/zero bắt nguồn từ System V. Cả hai đều không được SUSv3 quy định nhưng hầu hết UNIX đều hỗ trợ một hoặc cả hai.1 Hai cách là do lịch sử: một kiểu từ dòng BSD, một kiểu từ dòng System V — nay đều dùng được. Why two equivalent techniques: MAP_ANONYMOUS comes from BSD, /dev/zero from System V. Neither is specified by SUSv3, but most UNIX systems support one or both.1
  2. mremap() · MAP_FIXED

    Đổi kích thước ánh xạ, và ép địa chỉ chính xác Nới/thu ánh xạ, và đặt đúng địa chỉ mong muốn Resize a mapping, and pin an exact address

    Trên hầu hết UNIX, đã tạo ánh xạ thì không đổi được vị trí và kích thước; Linux cung cấp mremap() (không khả chuyển) để nới hoặc thu. Khi đổi, nhân có thể di dời ánh xạ trong không gian địa chỉ — việc này do cờ điều khiển: MREMAP_MAYMOVE cho phép di dời (không có nó mà thiếu chỗ nới tại chỗ thì lỗi ENOMEM).15 Vì địa chỉ có thể đổi, ứng dụng dùng mremap() nên tham chiếu bằng độ lệch chứ không dùng con trỏ tuyệt đối.1

    Đã xin một vùng nhớ rồi mà muốn to hơn hay nhỏ đi? mremap() làm được. Nhưng coi chừng: hệ thống có thể dời cả vùng sang chỗ khác, nên mọi con trỏ cũ trỏ vào đó có thể hỏng. Cách an toàn là nhớ vị trí bằng "khoảng cách từ đầu" chứ đừng nhớ địa chỉ tuyệt đối. Nhớ nhé: mremap() có thể dời vùng đi, đừng tin con trỏ cũ.

    On most UNIX, a mapping's location and size can't change once created; Linux provides (nonportable) mremap() to grow or shrink one. The kernel may relocate the mapping in the address space, controlled by a flag: MREMAP_MAYMOVE permits relocation (without it, insufficient room to expand in place yields ENOMEM).15 Because the address may change, applications using mremap() should refer to the region by offset, not absolute pointers.1

    Bạn gặp nó ở đâuWhere you meet it Cờ MAP_FIXED buộc nhân hiểu addr đúng y nguyên (phải căn trang); nếu vùng mới đè lên ánh xạ cũ, các trang bị đè được thay bằng ánh xạ mới. Ứng dụng khả chuyển nên tránh MAP_FIXED và để addr là NULL.1 Cờ MAP_FIXED là "đặt đúng chỗ tôi chỉ, không xê dịch" — mạnh nhưng dễ gây lỗi, nên hạn chế dùng. The MAP_FIXED flag forces the kernel to take addr exactly (must be page-aligned); if the new region overlaps an old mapping, the overlapped pages are replaced. Portable applications should avoid MAP_FIXED and leave addr NULL.1

    realloc() dùng mremap()realloc() uses mremap()

    Trên Linux, realloc() dùng mremap() để cấp lại hiệu quả các khối lớn mà malloc() từng cấp bằng mmap() MAP_ANONYMOUS — nhờ đó tránh phải sao chép từng byte khi đổi kích thước.1On Linux, realloc() uses mremap() to efficiently reallocate large blocks that malloc() previously allocated with mmap() MAP_ANONYMOUS — avoiding a byte-by-byte copy on resize.1

    Dựng vùng liền nhau khả chuyểnPortable contiguous regions

    Một công dụng khả chuyển của MAP_FIXED: trước hết tạo một ánh xạ ẩn danh với addr NULL (để nhân chọn vùng liền nhau), rồi dùng loạt lời mmap() có MAP_FIXED để phủ nhiều phần tệp lên các phần khác nhau của vùng đó.1One portable use of MAP_FIXED: first create an anonymous mapping with addr NULL (letting the kernel pick a contiguous region), then use a series of MAP_FIXED mmap() calls to overlay file regions onto different parts of it.1

  3. remap_file_pages() · mprotect()

    Ánh xạ phi tuyến (đã lỗi thời) và đổi quyền sau này Sắp xếp trang theo thứ tự lạ, và đổi quyền về sau Nonlinear mappings (deprecated) and changing protection later

    Ánh xạ tệp của mmap() là tuyến tính: trang tệp ứng một-một theo thứ tự với trang bộ nhớ. Vài ứng dụng cần ánh xạ phi tuyến — các trang tệp xuất hiện theo thứ tự khác trong vùng nhớ liền nhau. Làm bằng nhiều lời mmap() MAP_FIXED thì không co giãn tốt, vì mỗi lời tạo một vùng VMA riêng tốn thời gian và bộ nhớ nhân. Từ nhân 2.6, Linux cung cấp remap_file_pages() để tạo ánh xạ phi tuyến mà không sinh nhiều VMA.1

    Thường các trang của tệp nằm trong bộ nhớ đúng thứ tự như trong tệp. Có cách xếp chúng lộn thứ tự ("phi tuyến") cho vài nhu cầu đặc biệt, nhưng cách đó nay đã cũ. Cái hay dùng hơn là đổi quyền một vùng về sau. Nhớ nhé: xếp trang lộn thứ tự là chuyện hiếm và đã lỗi thời.

    mmap()'s file mappings are linear: file pages correspond one-to-one, in order, with memory pages. Some applications need nonlinear mappings — file pages appearing in a different order within contiguous memory. Doing this with many MAP_FIXED mmap() calls scales poorly, since each creates a separate VMA that costs time and kernel memory. From kernel 2.6, Linux provided remap_file_pages() to create nonlinear mappings without many VMAs.1

    Bạn gặp nó ở đâuWhere you meet it Quyền của một vùng đã ánh xạ đổi được bằng mprotect() (sách bàn ở Chương 50): truyền addr căn trang, length, và prot mới; vi phạm quyền mới vẫn sinh SIGSEGV.16 Muốn đổi "tấm biển quyền" của một vùng đã có? Dùng mprotect() để đổi sang đọc/ghi/chạy/cấm. The protection of an already-mapped region is changed with mprotect() (the book's Chapter 50): pass a page-aligned addr, a length, and the new prot; violating the new protection still raises SIGSEGV.16

    Vì sao phi tuyến nay nên tránhWhy nonlinear is now avoided

    Sách (2010) mô tả remap_file_pages() như công cụ còn dùng; nhưng theo trang man Linux, lời gọi này đã lỗi thời kể từ Linux 3.16 và từ đó được mô phỏng bằng cơ chế chậm hơn — mã mới nên dùng nhiều ánh xạ riêng thay cho nó.7The book (2010) presents remap_file_pages() as a live tool; but per the Linux man page it has been deprecated since Linux 3.16 and is since emulated by a slower mechanism — new code should use separate mappings instead.7

    mprotect() và độ mịn trangmprotect() and page granularity

    Vì quyền đặt trên cả trang, length của mprotect() thực tế được làm tròn lên bội kế tiếp của kích thước trang; prot nhận cùng tập giá trị như mmap() (PROT_NONE hoặc OR của READ/WRITE/EXEC).16Because protections are set on whole pages, mprotect()'s length is effectively rounded up to the next page-size multiple; prot takes the same values as mmap() (PROT_NONE or an OR of READ/WRITE/EXEC).16

Ba ngộ nhận thường gặp Three common misconceptions

“mmap() nạp cả tệp vào RAM ngay” “mmap() loads the whole file into RAM immediately”

Ngộ nhận: gọi mmap() là toàn bộ tệp được đọc vào bộ nhớ.

Thực tế: trang của ánh xạ được nạp từ tệp khi cần — trên Linux là lúc lần đầu truy cập; chưa chạm thì chưa nạp.1

Myth: calling mmap() reads the entire file into memory.

Reality: the mapping's pages are loaded from the file as required — on Linux, on first access; untouched pages are not loaded.1

“MAP_SHARED ghi ra đĩa ngay lập tức” “MAP_SHARED writes to disk instantly”

Ngộ nhận: sửa một ánh xạ MAP_SHARED là tệp trên đĩa cập nhật tức thì.

Thực tế: nhân sẽ ghi ngược ra tệp nhưng không đảm bảo thời điểm; muốn chắc đã xuống đĩa phải gọi msync(MS_SYNC).14

Myth: modifying a MAP_SHARED mapping updates the on-disk file instantly.

Reality: the kernel will carry changes through but gives no timing guarantee; to be sure it hit disk, call msync(MS_SYNC).14

“SIGSEGV và SIGBUS là một” “SIGSEGV and SIGBUS are the same”

Ngộ nhận: chạm sai chỗ trong ánh xạ thì luôn nhận SIGSEGV.

Thực tế: chạm vượt cuối ánh xạ → SIGSEGV; nhưng chạm vùng trong ánh xạ mà không có byte tệp tương ứng → SIGBUS.1

Myth: any bad access in a mapping always yields SIGSEGV.

Reality: access past the mapping's end → SIGSEGV; but access within the mapping with no corresponding file bytes → SIGBUS.1

Tự kiểm Self-check

Năm câu hỏi để biết bạn đã nắm ánh xạ bộ nhớ Năm câu hỏi kiểm tra nhanh Five questions to check you grasped memory mappings

Trả lời được những câu này nghĩa là bạn đã nắm hai trục phân loại, lời gọi mmap()/munmap(), cơ chế đồng bộ và các công cụ ánh xạ ẩn danh — nền cho bộ nhớ chia sẻ POSIX và các thao tác bộ nhớ ảo ở Chương 50.

Nếu trả lời được, nghĩa là bạn đã hiểu chương này.

Answering these means you grasp the two axes, the mmap()/munmap() calls, synchronization, and the anonymous-mapping tools — groundwork for POSIX shared memory and the Chapter 50 virtual-memory operations.

  1. Phân biệt ánh xạ tệp với ánh xạ ẩn danh, và ánh xạ riêng tư với chia sẻ. Bốn tổ hợp phục vụ bốn mục đích nào?

    Gợi ý sâu hơn: vì sao ánh xạ tệp chia sẻ phù hợp cho IPC cần dữ liệu tồn tại qua các lần khởi động lại?

  2. Kể sáu đối số của mmap(). Vì sao nên để addr là NULL? Đối số nào bắt buộc là bội của kích thước trang?

    Gợi ý sâu hơn: length có cần là bội của kích thước trang không, và chuyện gì xảy ra nếu không?

  3. Khi một ánh xạ vượt quá cuối tệp, khi nào truy cập sinh SIGBUS và khi nào sinh SIGSEGV?

    Gợi ý sâu hơn: các byte trong phần trang được làm tròn nhưng không có trong tệp mang giá trị gì, và sửa chúng có ghi ra tệp không?

  4. msync() dùng để làm gì? Phân biệt MS_SYNC và MS_ASYNC. Vì sao nên gọi nó trước munmap()?

    Gợi ý sâu hơn: trên hệ bộ nhớ ảo hợp nhất của Linux, msync() còn lại công dụng gì?

  5. Hai cách tạo ánh xạ ẩn danh là gì? Chúng phục vụ malloc() và IPC cha–con ra sao? mremap() và mprotect() làm gì?

    Gợi ý sâu hơn: vì sao mã dùng mremap() nên tham chiếu bằng độ lệch thay vì con trỏ tuyệt đối?

  1. Ánh xạ tệp và ánh xạ ẩn danh khác nhau chỗ nào?

  2. mmap() cần những gì, và vì sao nên để hệ thống tự chọn địa chỉ?

  3. Nếu tệp ngắn hơn vùng bạn xin ánh xạ thì chuyện gì xảy ra?

  4. Làm sao để chắc chắn dữ liệu đã được lưu xuống đĩa?

  5. Vùng nhớ ẩn danh dùng để làm gì?

  1. Distinguish a file mapping from an anonymous one, and private from shared. What four purposes do the four combinations serve?

  2. Name the six arguments of mmap(). Why leave addr NULL? Which argument must be a multiple of the page size?

  3. When a mapping extends past the end of a file, when does access raise SIGBUS and when SIGSEGV?

  4. What is msync() for? Distinguish MS_SYNC and MS_ASYNC. Why call it before munmap()?

  5. What are the two ways to create an anonymous mapping? How do they serve malloc() and parent-child IPC? What do mremap() and mprotect() do?

Khép lại chương Closing the chapter

mmap() cho ta đặt một tệp hoặc một vùng nhớ trống thẳng vào không gian địa chỉ, rồi làm việc với nó bằng phép toán trên bộ nhớ; hai trục — tệp/ẩn danh và riêng tư/chia sẻ — sinh ra bốn loại ánh xạ, với munmap() gỡ bỏ, msync() đồng bộ, và mprotect() đổi quyền. Hai công dụng đáng nhớ nhất vẫn là I/O ánh xạ bộ nhớ và IPC — cây cầu dẫn thẳng sang bộ nhớ chia sẻ POSIX và các thao tác bộ nhớ ảo tiếp theo.1

mmap() biến tệp hoặc vùng nhớ trống thành một mảng bạn truy cập trực tiếp; có bốn kiểu, và kiểu hay nhớ nhất là ánh xạ tệp chia sẻ — vừa đọc/ghi tệp, vừa cho các chương trình nói chuyện. Nhớ nhé: ánh xạ bộ nhớ là coi tệp hay vùng trống như một mảng trong tay bạn.

mmap() lets us place a file or a blank region straight into the address space, then work with it by memory operations; two axes — file/anonymous and private/shared — yield four mapping types, with munmap() to remove, msync() to synchronize, and mprotect() to change protection. The two most memorable uses remain memory-mapped I/O and IPC — the bridge straight to POSIX shared memory and the virtual-memory operations ahead.1