104. munmap - 取消内存映射 見出しへのリンク
1. 函数介绍 見出しへのリンク
munmap
是一个 Linux 系统调用,用于取消之前通过 mmap
建立的内存映射。它释放指定的内存映射区域,使该区域不再可访问,并可能释放相关的物理内存或文件资源。
2. 函数原型 見出しへのリンク
#include <sys/mman.h>
int munmap(void *addr, size_t length);
3. 功能 見出しへのリンク
取消从 addr
开始、长度为 length
字节的内存映射区域。这个操作会释放映射区域,使程序无法再通过该地址访问映射的内容。
4. 参数 見出しへのリンク
void *addr
: 要取消映射的内存区域起始地址- 必须是之前
mmap
调用返回的地址 - 必须是页面对齐的地址
- 必须是之前
size_t length
: 要取消映射的内存区域长度(以字节为单位)- 可以是原始映射长度的一部分
- 系统会将其向上舍入到页面边界
5. 返回值 見出しへのリンク
- 成功时返回 0
- 失败时返回 -1,并设置
errno
6. 常见 errno 错误码 見出しへのリンク
EINVAL
: 参数无效(地址不合法或长度为 0)ENOMEM
: 指定的地址范围未映射EAGAIN
: 暂时无法完成操作(很少见)EFAULT
: 地址范围包含无效内存
7. 相似函数,或关联函数 見出しへのリンク
mmap()
: 创建内存映射mremap()
: 重新映射内存区域msync()
: 同步内存映射与文件mprotect()
: 修改内存保护属性shm_open()
,shm_unlink()
: POSIX 共享内存brk()
,sbrk()
: 调整程序数据段大小
8. 示例代码 見出しへのリンク
示例1:基本使用 - 文件映射的取消 見出しへのリンク
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main() {
const char *filename = "test_file.txt";
const char *content = "This is test content for memory mapping.\nSecond line of content.\nThird line of content.";
printf("=== munmap 基本使用演示 ===\n");
// 创建测试文件
int fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
exit(EXIT_FAILURE);
}
// 写入测试内容
write(fd, content, strlen(content));
// 获取文件大小
struct stat st;
if (fstat(fd, &st) == -1) {
perror("获取文件状态失败");
close(fd);
unlink(filename);
exit(EXIT_FAILURE);
}
printf("创建测试文件: %s (大小: %ld 字节)\n", filename, (long)st.st_size);
// 创建内存映射
void *mapped_addr = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapped_addr == MAP_FAILED) {
perror("mmap 失败");
close(fd);
unlink(filename);
exit(EXIT_FAILURE);
}
printf("✓ 成功创建内存映射\n");
printf(" 映射地址: %p\n", mapped_addr);
printf(" 映射内容: %.*s\n", (int)st.st_size, (char*)mapped_addr);
// 修改映射内容
strcpy((char*)mapped_addr, "Modified content via mmap");
printf("✓ 修改映射内容\n");
printf(" 新内容: %.*s\n", (int)st.st_size, (char*)mapped_addr);
// 取消内存映射
printf("\n取消内存映射...\n");
if (munmap(mapped_addr, st.st_size) == -1) {
perror("munmap 失败");
close(fd);
unlink(filename);
exit(EXIT_FAILURE);
}
printf("✓ 成功取消内存映射\n");
// 验证文件内容(通过重新打开文件)
printf("\n验证文件内容:\n");
lseek(fd, 0, SEEK_SET);
char buffer[256];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf(" 文件内容: %s", buffer);
}
// 清理资源
close(fd);
unlink(filename);
printf("\n✓ 完成 munmap 演示\n");
return 0;
}
示例2:部分映射区域的取消 見出しへのリンク
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
void demonstrate_partial_unmap() {
printf("=== 部分映射区域取消演示 ===\n");
// 创建大文件用于测试
const char *filename = "large_test_file.txt";
int fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
return;
}
// 创建 1MB 的文件
size_t file_size = 1024 * 1024; // 1MB
if (ftruncate(fd, file_size) == -1) {
perror("设置文件大小失败");
close(fd);
unlink(filename);
return;
}
printf("创建 %zu 字节的测试文件\n", file_size);
// 创建内存映射
void *mapped_addr = mmap(NULL, file_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapped_addr == MAP_FAILED) {
perror("mmap 失败");
close(fd);
unlink(filename);
return;
}
printf("✓ 创建内存映射: %p (大小: %zu)\n", mapped_addr, file_size);
// 初始化部分数据
size_t page_size = getpagesize();
printf("系统页面大小: %zu 字节\n", page_size);
// 在前几个页面写入数据
for (int i = 0; i < 5; i++) {
char data[64];
snprintf(data, sizeof(data), "Page %d data", i);
strcpy((char*)mapped_addr + i * page_size, data);
}
printf("✓ 在前5个页面写入测试数据\n");
// 显示部分数据
printf("映射区域前 5 个页面的内容:\n");
for (int i = 0; i < 5; i++) {
printf(" 页面 %d: %s\n", i, (char*)mapped_addr + i * page_size);
}
// 取消前两个页面的映射
printf("\n取消前两个页面的映射...\n");
if (munmap(mapped_addr, 2 * page_size) == -1) {
perror("取消部分映射失败");
} else {
printf("✓ 成功取消前 %zu 字节的映射\n", 2 * page_size);
}
// 尝试访问已取消映射的区域(应该失败)
printf("尝试访问已取消映射的区域:\n");
if (munmap(mapped_addr, page_size) == -1) {
if (errno == EINVAL) {
printf("✓ 访问已取消映射区域失败 (EINVAL)\n");
} else {
printf(" 失败: %s\n", strerror(errno));
}
}
// 取消剩余映射区域
printf("\n取消剩余映射区域...\n");
void *remaining_addr = (char*)mapped_addr + 2 * page_size;
size_t remaining_size = file_size - 2 * page_size;
if (munmap(remaining_addr, remaining_size) == -1) {
perror("取消剩余映射失败");
} else {
printf("✓ 成功取消剩余映射区域\n");
}
// 清理资源
close(fd);
unlink(filename);
}
void test_unmap_errors() {
printf("\n=== munmap 错误处理测试 ===\n");
// 测试无效地址
printf("测试无效地址:\n");
if (munmap((void*)0x1, 4096) == -1) {
printf(" 结果: 失败 - %s\n", strerror(errno));
if (errno == EINVAL) {
printf(" 原因: 无效的地址\n");
}
}
// 测试 NULL 地址
printf("测试 NULL 地址:\n");
if (munmap(NULL, 4096) == -1) {
printf(" 结果: 失败 - %s\n", strerror(errno));
if (errno == EINVAL) {
printf(" 原因: NULL 地址无效\n");
}
}
// 测试零长度
printf("测试零长度:\n");
void *dummy_addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (dummy_addr != MAP_FAILED) {
if (munmap(dummy_addr, 0) == -1) {
printf(" 结果: 失败 - %s\n", strerror(errno));
if (errno == EINVAL) {
printf(" 原因: 长度为 0\n");
}
}
// 清理
munmap(dummy_addr, 4096);
}
// 测试未映射的区域
printf("测试未映射的区域:\n");
if (munmap((void*)0x10000000, 4096) == -1) {
printf(" 结果: 失败 - %s\n", strerror(errno));
if (errno == ENOMEM) {
printf(" 原因: 指定区域未映射\n");
}
}
}
int main() {
printf("=== munmap 错误处理和部分取消演示 ===\n");
demonstrate_partial_unmap();
test_unmap_errors();
return 0;
}
示例3:匿名映射和共享内存的管理 見出しへのリンク
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
void demonstrate_anonymous_mapping() {
printf("=== 匿名内存映射管理演示 ===\n");
size_t page_size = getpagesize();
size_t map_size = 4 * page_size; // 4 个页面
// 创建匿名内存映射
void *anon_map = mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (anon_map == MAP_FAILED) {
perror("创建匿名映射失败");
return;
}
printf("✓ 创建匿名映射: %p (大小: %zu)\n", anon_map, map_size);
// 初始化数据
for (int i = 0; i < 4; i++) {
char data[64];
snprintf(data, sizeof(data), "Anonymous page %d", i);
strcpy((char*)anon_map + i * page_size, data);
}
printf("✓ 初始化匿名映射数据\n");
// 显示数据
printf("匿名映射内容:\n");
for (int i = 0; i < 4; i++) {
printf(" 页面 %d: %s\n", i, (char*)anon_map + i * page_size);
}
// 取消映射
printf("\n取消匿名映射...\n");
if (munmap(anon_map, map_size) == -1) {
perror("取消匿名映射失败");
} else {
printf("✓ 成功取消匿名映射\n");
}
// 尝试再次取消同一映射(应该失败)
printf("再次取消同一映射:\n");
if (munmap(anon_map, map_size) == -1) {
printf(" 结果: 失败 - %s\n", strerror(errno));
if (errno == ENOMEM) {
printf(" 原因: 映射区域已不存在\n");
}
}
}
void demonstrate_shared_memory_unmap() {
printf("\n=== 共享内存映射取消演示 ===\n");
size_t page_size = getpagesize();
size_t map_size = 2 * page_size;
// 创建共享匿名映射
void *shared_map = mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (shared_map == MAP_FAILED) {
perror("创建共享映射失败");
return;
}
printf("✓ 创建共享映射: %p (大小: %zu)\n", shared_map, map_size);
// 创建子进程测试共享内存
pid_t pid = fork();
if (pid == -1) {
perror("fork 失败");
munmap(shared_map, map_size);
return;
}
if (pid == 0) {
// 子进程
printf("子进程 (PID: %d):\n", getpid());
printf(" 访问共享映射: %p\n", shared_map);
// 写入数据
strcpy((char*)shared_map, "Data from child process");
printf(" ✓ 写入共享数据\n");
// 等待父进程处理
sleep(1);
// 取消自己的映射
if (munmap(shared_map, map_size) == -1) {
printf(" 子进程取消映射失败: %s\n", strerror(errno));
} else {
printf(" ✓ 子进程取消映射成功\n");
}
exit(0);
} else {
// 父进程
printf("父进程 (PID: %d):\n", getpid());
printf(" 创建子进程 PID: %d\n", pid);
// 等待子进程写入数据
sleep(2);
// 读取子进程写入的数据
printf(" 读取共享数据: %s\n", (char*)shared_map);
// 取消映射
printf(" 父进程取消映射...\n");
if (munmap(shared_map, map_size) == -1) {
perror(" 父进程取消映射失败");
} else {
printf(" ✓ 父进程取消映射成功\n");
}
// 等待子进程结束
int status;
waitpid(pid, &status, 0);
printf(" 子进程已结束\n");
}
}
int main() {
printf("=== 匿名和共享内存映射管理 ===\n");
demonstrate_anonymous_mapping();
demonstrate_shared_memory_unmap();
return 0;
}
示例4:内存映射管理工具 見出しへのリンク
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
typedef struct {
void *addr;
size_t size;
int fd;
char description[256];
} mapping_info_t;
#define MAX_MAPPINGS 64
static mapping_info_t mappings[MAX_MAPPINGS];
static int mapping_count = 0;
int register_mapping(void *addr, size_t size, int fd, const char *description) {
if (mapping_count >= MAX_MAPPINGS) {
return -1;
}
mappings[mapping_count].addr = addr;
mappings[mapping_count].size = size;
mappings[mapping_count].fd = fd;
strncpy(mappings[mapping_count].description, description,
sizeof(mappings[mapping_count].description) - 1);
mappings[mapping_count].description[sizeof(mappings[mapping_count].description) - 1] = '\0';
return mapping_count++;
}
int unregister_mapping(void *addr) {
for (int i = 0; i < mapping_count; i++) {
if (mappings[i].addr == addr) {
// 将后续映射前移
for (int j = i; j < mapping_count - 1; j++) {
mappings[j] = mappings[j + 1];
}
mapping_count--;
return 0;
}
}
return -1;
}
void list_mappings() {
printf("=== 当前内存映射列表 ===\n");
if (mapping_count == 0) {
printf("没有活动的内存映射\n");
return;
}
printf("%-4s %-14s %-10s %-6s %s\n", "ID", "地址", "大小", "文件描述符", "描述");
printf("%-4s %-14s %-10s %-6s %s\n", "--", "----", "----", "--------", "----");
for (int i = 0; i < mapping_count; i++) {
printf("%-4d %p %-10zu %-6d %s\n",
i, mappings[i].addr, mappings[i].size,
mappings[i].fd, mappings[i].description);
}
printf("总计: %d 个映射\n", mapping_count);
}
void interactive_mapping_manager() {
int choice;
char filename[256];
size_t map_size;
int fd;
void *mapped_addr;
while (1) {
printf("\n=== 内存映射管理工具 ===\n");
printf("1. 创建文件映射\n");
printf("2. 创建匿名映射\n");
printf("3. 取消内存映射\n");
printf("4. 列出所有映射\n");
printf("5. 显示系统页面大小\n");
printf("6. 查看 /proc/self/maps\n");
printf("0. 退出\n");
printf("请选择操作: ");
if (scanf("%d", &choice) != 1) {
printf("输入无效\n");
while (getchar() != '\n'); // 清空输入缓冲区
continue;
}
switch (choice) {
case 1:
printf("创建文件映射:\n");
printf("输入文件名: ");
scanf("%255s", filename);
fd = open(filename, O_RDWR);
if (fd == -1) {
// 文件不存在,尝试创建
fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
printf("✗ 无法打开或创建文件: %s\n", strerror(errno));
break;
}
printf("✓ 创建新文件: %s\n", filename);
} else {
printf("✓ 打开现有文件: %s\n", filename);
}
printf("输入映射大小 (字节): ");
if (scanf("%zu", &map_size) == 1) {
// 设置文件大小
if (ftruncate(fd, map_size) == -1) {
printf("✗ 设置文件大小失败: %s\n", strerror(errno));
close(fd);
break;
}
// 创建映射
mapped_addr = mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapped_addr == MAP_FAILED) {
printf("✗ 创建映射失败: %s\n", strerror(errno));
close(fd);
break;
}
char description[256];
snprintf(description, sizeof(description), "文件: %s", filename);
int id = register_mapping(mapped_addr, map_size, fd, description);
if (id != -1) {
printf("✓ 成功创建映射 (ID: %d)\n", id);
printf(" 地址: %p\n", mapped_addr);
printf(" 大小: %zu 字节\n", map_size);
} else {
printf("✗ 注册映射失败\n");
munmap(mapped_addr, map_size);
close(fd);
}
}
break;
case 2:
printf("创建匿名映射:\n");
printf("输入映射大小 (字节): ");
if (scanf("%zu", &map_size) == 1) {
mapped_addr = mmap(NULL, map_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mapped_addr == MAP_FAILED) {
printf("✗ 创建匿名映射失败: %s\n", strerror(errno));
break;
}
int id = register_mapping(mapped_addr, map_size, -1, "匿名映射");
if (id != -1) {
printf("✓ 成功创建匿名映射 (ID: %d)\n", id);
printf(" 地址: %p\n", mapped_addr);
printf(" 大小: %zu 字节\n", map_size);
} else {
printf("✗ 注册映射失败\n");
munmap(mapped_addr, map_size);
}
}
break;
case 3: {
if (mapping_count == 0) {
printf("没有活动的映射\n");
break;
}
list_mappings();
printf("输入要取消的映射 ID: ");
int map_id;
if (scanf("%d", &map_id) == 1) {
if (map_id >= 0 && map_id < mapping_count) {
mapping_info_t *map = &mappings[map_id];
printf("取消映射: %p (大小: %zu)\n", map->addr, map->size);
if (munmap(map->addr, map->size) == 0) {
printf("✓ 成功取消映射\n");
if (map->fd != -1) {
close(map->fd);
}
unregister_mapping(map->addr);
} else {
printf("✗ 取消映射失败: %s\n", strerror(errno));
}
} else {
printf("无效的映射 ID\n");
}
}
break;
}
case 4:
list_mappings();
break;
case 5:
printf("系统页面大小: %ld 字节\n", (long)getpagesize());
break;
case 6:
printf("当前进程内存映射:\n");
system("cat /proc/self/maps | head -20");
break;
case 0:
printf("退出内存映射管理工具\n");
// 清理所有剩余映射
for (int i = 0; i < mapping_count; i++) {
munmap(mappings[i].addr, mappings[i].size);
if (mappings[i].fd != -1) {
close(mappings[i].fd);
}
}
mapping_count = 0;
return;
default:
printf("无效选择\n");
break;
}
}
}
void demonstrate_munmap_features() {
printf("=== munmap 特性演示 ===\n");
printf("munmap 主要特性:\n");
printf("1. 取消内存映射区域\n");
printf("2. 释放相关资源\n");
printf("3. 更新进程地址空间\n");
printf("4. 支持部分区域取消\n");
printf("5. 线程安全操作\n");
printf("\n使用注意事项:\n");
printf("• 地址必须是页面对齐的\n");
printf("• 长度会被向上舍入到页面边界\n");
printf("• 取消后原地址不可访问\n");
printf("• 可能触发页面写回操作\n");
printf("• 对共享映射影响所有进程\n");
}
int main() {
printf("=== 内存映射管理工具 ===\n");
// 显示系统信息
printf("系统信息:\n");
printf(" 页面大小: %ld 字节\n", (long)getpagesize());
printf(" PID: %d\n", getpid());
// 演示特性
demonstrate_munmap_features();
// 启动交互式管理器
char choice;
printf("\n是否启动交互式内存映射管理器? (y/N): ");
if (scanf(" %c", &choice) == 1 && (choice == 'y' || choice == 'Y')) {
interactive_mapping_manager();
}
return 0;
}
9. munmap 行为说明 見出しへのリンク
// munmap 的重要行为特性:
// 1. 地址对齐
// • addr 参数必须是页面对齐的
// • length 会被向上舍入到页面边界
// 2. 资源释放
// • 释放虚拟地址空间
// • 可能释放物理内存
// • 对于文件映射,可能触发写回
// 3. 共享映射
// • 影响所有映射同一区域的进程
// • 修改对所有进程可见
// 4. 错误处理
// • 失败时不保证部分操作的原子性
// • 需要检查返回值和 errno
// 5. 性能考虑
// • 可能涉及页面写回操作
// • 大量取消操作可能影响性能
10. 实际应用场景 見出しへのリンク
场景1:大文件处理 見出しへのリンク
void process_large_file(const char *filename) {
int fd = open(filename, O_RDONLY);
if (fd == -1) return;
struct stat st;
if (fstat(fd, &st) == -1) {
close(fd);
return;
}
// 分块映射处理大文件
size_t chunk_size = 1024 * 1024; // 1MB
off_t offset = 0;
while (offset < st.st_size) {
size_t current_size = (st.st_size - offset > chunk_size) ?
chunk_size : (st.st_size - offset);
void *mapped = mmap(NULL, current_size, PROT_READ, MAP_PRIVATE, fd, offset);
if (mapped != MAP_FAILED) {
// 处理映射的数据
process_data(mapped, current_size);
// 立即取消映射释放资源
munmap(mapped, current_size);
}
offset += current_size;
}
close(fd);
}
场景2:动态内存池管理 見出しへのリンク
typedef struct {
void *addr;
size_t size;
int is_mapped;
} memory_pool_t;
memory_pool_t *create_memory_pool(size_t size) {
memory_pool_t *pool = malloc(sizeof(memory_pool_t));
if (!pool) return NULL;
pool->addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (pool->addr == MAP_FAILED) {
free(pool);
return NULL;
}
pool->size = size;
pool->is_mapped = 1;
return pool;
}
void destroy_memory_pool(memory_pool_t *pool) {
if (pool && pool->is_mapped) {
munmap(pool->addr, pool->size);
pool->is_mapped = 0;
free(pool);
}
}
场景3:临时文件映射 見出しへのリンク
void *map_temporary_file(size_t size, int *fd_out) {
char temp_name[] = "/tmp/mmap_temp_XXXXXX";
int fd = mkstemp(temp_name);
if (fd == -1) return MAP_FAILED;
if (ftruncate(fd, size) == -1) {
close(fd);
unlink(temp_name);
return MAP_FAILED;
}
void *mapped = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
close(fd);
unlink(temp_name);
return MAP_FAILED;
}
// 立即删除文件(仅保留文件描述符)
unlink(temp_name);
*fd_out = fd;
return mapped;
}
void unmap_temporary_file(void *addr, size_t size, int fd) {
munmap(addr, size);
close(fd);
}
11. 注意事项 見出しへのリンク
使用 munmap
时需要注意:
- 地址对齐: 地址必须是页面对齐的
- 长度处理: 长度会被向上舍入到页面边界
- 资源释放: 确保释放相关的文件描述符
- 并发安全: 多线程环境中注意同步
- 错误检查: 始终检查返回值和
errno
- 性能影响: 大量取消操作可能影响性能
- 共享映射: 注意对其他进程的影响
12. 系统配置检查 見出しへのリンク
# 查看系统页面大小
getconf PAGESIZE
# 查看进程内存映射
cat /proc/self/maps
# 查看系统内存信息
cat /proc/meminfo
# 查看虚拟内存统计
vmstat
# 查看内存映射统计
cat /proc/self/smaps | head -20
13. 性能优化建议 見出しへのリンク
// 性能优化建议:
// 1. 批量取消映射
void batch_unmap(void **addrs, size_t *sizes, int count) {
for (int i = 0; i < count; i++) {
munmap(addrs[i], sizes[i]);
}
}
// 2. 及时释放不需要的映射
void *large_data = mmap(NULL, huge_size, PROT_READ, MAP_PRIVATE, fd, 0);
// 使用完后立即释放
process_large_data(large_data);
munmap(large_data, huge_size);
// 3. 合理使用映射粒度
// 避免过于细碎的映射和取消操作
总结 見出しへのリンク
munmap
是 Linux 系统中管理内存映射的重要系统调用:
关键特性:
- 资源释放: 释放虚拟地址空间和相关资源
- 灵活操作: 支持部分区域取消映射
- 共享感知: 正确处理共享内存映射
- 系统集成: 与虚拟内存管理系统紧密集成
主要应用:
- 大文件处理和数据分析
- 动态内存池管理
- 共享内存和进程间通信
- 高性能数据处理应用
使用要点:
- 确保地址页面对齐
- 合理管理映射生命周期
- 正确处理错误情况
- 注意对共享映射的影响
- 及时释放不需要的资源
正确使用 munmap
可以帮助应用程序高效管理内存资源,是现代 Linux 系统编程的重要技能。