mmap
是 Unix/Linux 系統中一個強大且多用途的系統調用,用于將文件或設備映射到進程的地址空間,實現內存映射I/O。
1. 函數的概念與用途
mmap
(內存映射)函數允許程序將文件或其他對象直接映射到其地址空間,這樣文件內容就可以通過內存指針直接訪問,而不需要使用傳統的 read
/write
系統調用。
主要用途:
- 文件I/O優化:提供對文件的高效隨機訪問,避免頻繁的
read
/write
系統調用 - 進程間通信:通過映射同一文件實現共享內存
- 內存分配:用于實現高效的內存分配器(如
malloc
) - 程序加載:用于加載可執行文件和共享庫
- 零拷貝I/O:在某些情況下可以實現零拷貝網絡傳輸
2. 函數的聲明與出處
mmap
函數定義在 <sys/mman.h>
頭文件中,是 POSIX 標準的一部分。
#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
3. 返回值的含義與取值范圍
- 成功時:返回映射區域的起始地址
- 失敗時:返回
MAP_FAILED
(通常是(void *)-1
),并設置errno
來指示錯誤原因
常見錯誤碼 (errno):
EACCES
:文件描述符不支持所請求的訪問類型EAGAIN
:文件已被鎖定,或太多內存被鎖定EBADF
:文件描述符無效EINVAL
:參數無效(如長度為零、不支持的保護或標志)ENFILE
:已達到系統對打開文件總數的限制ENODEV
:文件所在文件系統不支持內存映射ENOMEM
:沒有可用內存,或進程超出內存映射限制EPERM
:操作被拒絕(如嘗試寫入只寫區域)
4. 參數的含義與取值范圍
-
void *addr
- 含義:建議的映射起始地址(通常設為 NULL,由內核自動選擇)
- 取值范圍:任何有效的地址或 NULL
-
size_t length
- 含義:要映射的字節數
- 取值范圍:大于 0 的值
-
int prot
- 含義:內存保護標志,指定訪問權限
- 常見取值(使用按位或組合):
PROT_NONE
:頁面不可訪問PROT_READ
:頁面可讀PROT_WRITE
:頁面可寫PROT_EXEC
:頁面可執行
-
int flags
- 含義:映射類型和選項
- 常見取值(使用按位或組合):
MAP_SHARED
:共享映射,修改對其他進程可見MAP_PRIVATE
:私有映射,修改不會寫回文件MAP_ANONYMOUS
:不映射文件,創建匿名內存MAP_FIXED
:必須使用指定地址MAP_LOCKED
:鎖定頁面在內存中
-
int fd
- 含義:要映射的文件描述符
- 取值范圍:有效的文件描述符(對于匿名映射,設為 -1)
-
off_t offset
- 含義:文件中的偏移量,必須是系統頁面大小的倍數
- 取值范圍:非負值,通常是頁面大小的倍數
5. 函數使用案例
案例1:文件映射與讀取
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main() {const char *filename = "example.txt";const int file_size = 1024;// 創建并寫入示例文件int fd = open(filename, O_RDWR | O_CREAT, 0644);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 調整文件大小if (ftruncate(fd, file_size) == -1) {perror("ftruncate");close(fd);exit(EXIT_FAILURE);}// 映射文件到內存char *mapped = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}// 通過內存訪問文件內容const char *message = "Hello, mmap!";strncpy(mapped, message, strlen(message));printf("File content via mmap: %s\n", mapped);// 取消映射并關閉文件if (munmap(mapped, file_size) == -1) {perror("munmap");}close(fd);return 0;
}
案例2:進程間共享內存
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>#define SHM_SIZE 1024int main() {// 創建共享內存區域(匿名映射)char *shared_mem = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (shared_mem == MAP_FAILED) {perror("mmap");exit(EXIT_FAILURE);}pid_t pid = fork();if (pid == -1) {perror("fork");exit(EXIT_FAILURE);}if (pid == 0) { // 子進程printf("Child process writing to shared memory\n");strcpy(shared_mem, "Message from child process");exit(EXIT_SUCCESS);} else { // 父進程wait(NULL); // 等待子進程結束printf("Parent process reading from shared memory: %s\n", shared_mem);// 清理if (munmap(shared_mem, SHM_SIZE) == -1) {perror("munmap");}}return 0;
}
案例3:高效大數據處理
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>#define FILE_SIZE (1024 * 1024 * 100) // 100MBint main() {const char *filename = "large_file.bin";int fd = open(filename, O_RDWR | O_CREAT, 0644);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 調整文件大小if (ftruncate(fd, FILE_SIZE) == -1) {perror("ftruncate");close(fd);exit(EXIT_FAILURE);}// 映射文件char *mapped = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}// 使用內存映射進行高效處理clock_t start = clock();for (size_t i = 0; i < FILE_SIZE; i++) {mapped[i] = (char)(i % 256); // 填充數據}clock_t end = clock();double elapsed = (double)(end - start) / CLOCKS_PER_SEC;printf("Processed %d MB in %.2f seconds (%.2f MB/s)\n", FILE_SIZE / (1024 * 1024), elapsed, (FILE_SIZE / (1024.0 * 1024.0)) / elapsed);// 清理if (munmap(mapped, FILE_SIZE) == -1) {perror("munmap");}close(fd);remove(filename); // 刪除臨時文件return 0;
}
6. 編譯方式與注意事項
編譯命令:
gcc -o mmap_example1 mmap_example1.c
gcc -o mmap_example2 mmap_example2.c
gcc -o mmap_example3 mmap_example3.c
注意事項:
- 權限檢查:確保對映射的文件有適當的訪問權限
- 對齊要求:
offset
參數必須是系統頁面大小的倍數(可用sysconf(_SC_PAGE_SIZE)
獲取) - 資源清理:始終使用
munmap()
取消映射,并使用close()
關閉文件描述符 - 錯誤處理:始終檢查返回值并處理可能的錯誤
- 同步:對于共享映射,修改可能不會立即寫回文件,可使用
msync()
強制同步 - 內存保護:注意設置適當的保護標志,避免安全漏洞
- 可移植性:不同系統可能有不同的頁面大小和限制
7. 執行結果說明
案例1輸出:
File content via mmap: Hello, mmap!
這個示例創建了一個文件,通過內存映射寫入內容,然后讀取并顯示內容。
案例2輸出:
Child process writing to shared memory
Parent process reading from shared memory: Message from child process
這個示例演示了父子進程如何通過匿名映射共享內存區域。
案例3輸出:
Processed 100 MB in 0.15 seconds (666.67 MB/s)
這個示例展示了使用內存映射處理大文件的高效性,通過直接內存訪問避免了頻繁的系統調用。
8. 圖文總結
以下是 mmap
系統調用的工作流程:
關鍵點總結:
mmap
提供了一種高效的文件訪問方式,避免了頻繁的read
/write
系統調用- 支持文件映射和匿名映射兩種模式,分別用于文件I/O和進程間通信
- 采用惰性加載機制,只有在實際訪問時才會將數據加載到內存
- 對于共享映射,修改可能會自動寫回文件(取決于標志)
- 必須正確使用
munmap()
釋放映射區域,避免資源泄漏 - 適當設置保護標志和映射選項對性能和安全性至關重要
mmap
是一個強大的系統調用,正確使用可以顯著提高I/O性能,但需要仔細考慮內存管理和同步問題。