mmap() 將文件或設備直接映射到進程虛擬地址空間,實現用戶態直接訪問頁面緩存,是高性能文件 I/O、進程間共享內存、大數據處理的核心系統調用。本文涵蓋原理、API 詳解、典型用法、零拷貝網絡傳輸、匿名映射共享內存、常見錯誤與調優參數。
mmap 在進程頁表中建立虛擬頁面 → 物理頁面緩存的映射,無需拷貝到用戶態緩衝區。MAP_SHARED 修改經 msync / munmap / 內核定期回寫刷入磁盤;MAP_PRIVATE 觸發 Copy-on-Write,修改不回寫文件。#include#include #include void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length); int msync(void *addr, size_t length, int flags); // 同步到磁盤 int mprotect(void *addr, size_t length, int prot); // 修改保護屬性 int madvise(void *addr, size_t length, int advice); // 訪問提示
| 參數 | 常用值 | 說明 |
|---|---|---|
| addr | NULL | 建議映射地址,NULL 由內核選擇(推薦) |
| length | 文件大小 | 映射字節數,非頁面對齊會向上取整到頁面 |
| prot | PROT_READ \| PROT_WRITE | PROT_READ/WRITE/EXEC/NONE,需與 open 模式一致 |
| flags | MAP_SHARED / MAP_PRIVATE | SHARED:修改可見並回寫;PRIVATE:COW 私有副本 |
| fd | open() 返回 | 文件描述符,-1 配合 MAP_ANONYMOUS 做匿名映射 |
| offset | 0 | 文件偏移,必須頁面對齊 (通常 4KB) |
// 讀取整個文件到內存(自動分頁、按需加載)
int fd = open("large.dat", O_RDONLY);
off_t fsize = lseek(fd, 0, SEEK_END);
void *map = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fd, 0);
close(fd); // fd 關閉不影響映射
// 直接訪問
char *data = (char *)map;
process(data, fsize);
munmap(map, fsize);
優勢:無 read/memcpy 開銷,內核自動預讀、回收頁面,適合大文件隨機訪問。
// 匿名映射,無文件後備,父子進程 fork 後共享
void *shared = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
int *counter = (int *)shared;
*counter = 0;
pid_t pid = fork();
if (pid == 0) {
for (int i = 0; i < 1000000; ++i) __sync_fetch_and_add(counter, 1);
exit(0);
}
wait(NULL);
printf("Counter: %d\n", *counter); // 2000000 (需原子操作)
munmap(shared, 4096);
// sendfile / splice 替代方案:mmap + write void *map = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fd, 0); write(sockfd, map, fsize); // 數據直接從頁面緩存經 Socket 發送,無用戶態拷貝 munmap(map, fsize);
現代內核 sendfile/splice 更高效(內核態直連),但 mmap+write 通用性更好。
| 特性 | API/標誌 | 場景 |
|---|---|---|
| 修改保護 | mprotect(map, len, PROT_READ) | 實現只讀緩存、JIT 代碼頁保護、GC 寫屏障 |
| 同步刷盤 | msync(map, len, MS_SYNC) | 數據庫 WAL、關鍵配置持久化 |
| 訪問提示 | madvise(map, len, MADV_SEQUENTIAL|MADV_WILLNEED) | 指導內核預讀/回收策略 |
| 大頁面 | MAP_HUGETLB (需 hugetlbfs) | 減少 TLB Miss,大內存數據庫/科學計算 |
| 文件擴展 | ftruncate(fd, new_size) 後 mremap | 動態增長映射文件 |
EINVAL。解決:offset & ~(pagesize-1) 對齊下取整,讀取時手動偏移。MAP_SHARED。mmap 不提供同步。addr 指定非 NULL 易衝突。解決:始終傳 NULL。madvise(MADV_SEQUENTIAL) 觸發激進預讀。madvise(MADV_RANDOM) 禁用預讀,節省帶寬。MAP_SHARED | PROT_READ,內核可直接丟棄頁面緓存頁面。mmap + 用戶態鎖 或改用 write + O_DIRECT 繞過頁面緩存。/proc/vmstat pgmajfault/minflt、pidstat -d、perf stat -e page-faults。mmap 以虛擬內存映射替代數據拷貝,實現文件 I/O「零拷貝」、進程共享內存「零拷貝」、網絡發送「零拷貝」。掌握 MAP_SHARED/PRIVATE 語義、offset 對齊、msync/mprotect/madvise 進階控制、生命周期管理,是構建高性能存儲引擎、數據庫、消息隊列、多媒體處理器的核心技能。