Linux内核分析之内存管理-11

韩乔落

第11章:内存映射

基于 Linux 6.12.38 源码


11.1 内存映射概述

11.1.1 mmap 系统调用

mmap (memory map) 是 Linux 提供的一种内存映射机制,可以将文件或设备映射到进程的虚拟地址空间。

映射类型:

  • 文件映射: 将文件映射到内存
  • 匿名映射: 不映射文件,用于内存分配
  • 共享映射: 多个进程共享映射
  • 私有映射: 写时复制 (COW)

11.1.2 映射示例

1
2
3
4
5
6
7
8
9
10
11
/* 匿名映射 (malloc 实现) */
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

/* 文件映射 */
int fd = open("file.txt", O_RDWR);
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);

/* 解除映射 */
munmap(ptr, size);

11.2 mmap 系统调用

11.2.1 sys_mmap_pgoff

位置: mm/mmap.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, off)
{
return ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
}

static unsigned long ksys_mmap_pgoff(unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoff)
{
struct mm_struct *mm = current->mm;
unsigned long retval;

/* 1. 参数校验 */
if (!(flags & MAP_FIXED))
addr = round_hint_to_min(addr);

len = PAGE_ALIGN(len);
if (!len)
return -EINVAL;

/* 2. 查找未使用的虚拟地址区域 */
addr = get_unmapped_area(file, addr, len, pgoff, flags);
if (addr & ~PAGE_MASK)
return addr;

/* 3. 执行映射 */
retval = vm_mmap_pgoff(addr, len, prot, flags, fd, pgoff);

return retval;
}

11.2.2 mmap 标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* mmap 标志定义 */
#define MAP_SHARED 0x01 /* 共享映射 */
#define MAP_PRIVATE 0x02 /* 私有映射 (COW) */
#define MAP_FIXED 0x10 /* 固定地址映射 */
#define MAP_ANONYMOUS 0x20 /* 匿名映射 */
#define MAP_GROWSDOWN 0x0100 /* 向下增长 (栈) */
#define MAP_DENYWRITE 0x0800 /* 拒绝写入 */
#define MAP_EXECUTABLE 0x1000 /* 可执行 */
#define MAP_LOCKED 0x2000 /* 锁定内存 */
#define MAP_NORESERVE 0x4000 /* 不保留交换空间 */
#define MAP_POPULATE 0x8000 /* 预分配页面 */
#define MAP_NONBLOCK 0x10000 /* 非阻塞 */
#define MAP_STACK 0x20000 /* 映射用于栈 */
#define MAP_HUGETLB 0x40000 /* 使用巨页 */
#define MAP_SYNC 0x80000 /* 同步写 */
#define MAP_FIXED_NOREPLACE 0x100000 /* 固定地址,不替换 */

11.2.3 保护标志

1
2
3
4
5
6
7
8
/* mmap 保护标志 (与 VMA 标志对应) */
#define PROT_READ 0x1 /* 可读 */
#define PROT_WRITE 0x2 /* 可写 */
#define PROT_EXEC 0x4 /* 可执行 */
#define PROT_SEM 0x8 /* 用于 IPC (已废弃) */
#define PROT_NONE 0x0 /* 不可访问 */
#define PROT_GROWSDOWN 0x01000000 /* 向下增长 */
#define PROT_GROWSUP 0x02000000 /* 向上增长 */

11.3 do_mmap 实现

11.3.1 核心映射函数

位置: mm/mmap.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
static unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flags, unsigned long pgoff,
unsigned long *populate)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
int error;

/* 1. 参数校验 */
if (flags & MAP_FIXED) {
/* 固定地址映射 */
if (addr & ~PAGE_MASK)
return -EINVAL;
} else {
/* 动态选择地址 */
addr = get_unmapped_area(file, addr, len, pgoff, flags);
if (IS_ERR_VALUE(addr))
return addr;
}

len = PAGE_ALIGN(len);
if (!len)
return -ENOMEM;

/* 2. 创建 VMA */
vma = vm_area_alloc(mm);
if (!vma)
return -ENOMEM;

vma->vm_start = addr;
vma->vm_end = addr + len;
vma->vm_flags = vm_flags;
vma->vm_page_prot = vm_get_page_prot(vm_flags);
vma->vm_pgoff = pgoff;

/* 3. 设置 VMA 操作 */
if (file)
vma->vm_ops = file->f_op->mmap;

/* 4. 插入 VMA */
error = mmap_region(file, addr, len, vm_flags, pgoff, vma);
if (error)
return error;

/* 5. 预填充页面 (如果需要) */
if (populate && (vm_flags & MAP_POPULATE)) {
error = populate_vma_page_range(vma);
if (error)
return error;
}

return addr;
}

11.3.2 mmap 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
mmap(addr, len, prot, flags, fd, offset)


┌──────────────────────────────────────┐
│ 1. 参数校验 │
│ - 校验标志 │
│ - 对齐长度 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 2. 查找未使用的虚拟地址区域 │
│ - get_unmapped_area │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 3. 创建 VMA │
│ - vm_area_alloc │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 4. 设置 VMA 属性 │
│ - vm_start, vm_end │
│ - vm_flags, vm_page_prot │
│ - vm_pgoff │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 5. 插入 VMA │
│ - mmap_region │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 6. 预填充页面 (可选) │
│ - populate_vma_page_range │
└──────────────────────────────────────┘

11.4 mprotect 系统调用

11.4.1 sys_mprotect

位置: mm/mprotect.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
unsigned long, prot)
{
struct mm_struct *mm = current->mm;
unsigned long end, nstart, nend;
struct vm_area_struct *vma;

/* 1. 参数校验 */
end = start + len;
if (end < start)
return -EINVAL;

/* 2. 查找 VMA */
vma = find_vma(mm, start);
if (!vma)
return -ENOMEM;

/* 3. 修改保护 */
while (start < end) {
unsigned long newflags;

nstart = start;
nend = end;

/* 分割 VMA */
if (start > vma->vm_start)
nstart = start;
if (end < vma->vm_end)
nend = end;

/* 计算新标志 */
newflags = calc_vm_prot_bits(prot, 0);
newflags |= (vma->vm_flags & ~(VM_READ | VM_WRITE | VM_EXEC));

/* 更新 VMA */
error = mprotect_fixup(vma, &prev, nstart, nend, newflags);
if (error)
return error;

start = nend;
vma = prev->vm_next;
}

return 0;
}

11.4.2 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
/* 分配内存 */
void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

/* 修改为只读 */
mprotect(ptr, 4096, PROT_READ);

/* 修改为可执行 */
mprotect(ptr, 4096, PROT_READ | PROT_EXEC);

/* 解除映射 */
munmap(ptr, 4096);

11.5 munmap 系统调用

11.5.1 sys_munmap

位置: mm/mmap.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
SYSCALL_DEFINE2(munmap, unsigned long, addr, size_t, len)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long end;

/* 1. 参数校验 */
end = addr + len;
if (end < addr)
return -EINVAL;

/* 2. 查找 VMA */
while ((vma = find_vma(mm, addr)) != NULL) {
/* 删除 VMA */
detach_vmas_to_be_unmapped(mm, vma, prev, end);

/* 释放资源 */
unmap_region(mm, vma, prev, start, end);

break;
}

return 0;
}

11.5.2 解除映射流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
munmap(addr, len)


┌──────────────────────────────────────┐
│ 1. 参数校验 │
│ - 校验地址范围 │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 2. 查找 VMA │
│ - find_vma │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 3. 分离 VMA │
│ - detach_vmas_to_be_unmapped │
└──────────────────────────────────────┘


┌──────────────────────────────────────┐
│ 4. 解除映射 │
│ - unmap_region │
│ - 释放页表 │
│ - 释放页面 │
└──────────────────────────────────────┘

11.6 mremap 系统调用

11.6.1 sys_mremap

位置: mm/mmap.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len,
unsigned long, new_len, unsigned long, flags,
unsigned long, new_addr)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;

/* 1. 参数校验 */
old_len = PAGE_ALIGN(old_len);
new_len = PAGE_ALIGN(new_len);

if (flags & MREMAP_FIXED) {
/* 固定地址重映射 */
if (new_addr & ~PAGE_MASK)
return -EINVAL;
}

/* 2. 查找 VMA */
vma = find_vma(mm, addr);
if (!vma)
return -EFAULT;

/* 3. 执行重映射 */
return do_mremap(addr, old_len, new_len, flags, new_addr);
}

11.6.2 do_mremap 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static unsigned long do_mremap(unsigned long addr,
unsigned long old_len, unsigned long new_len,
unsigned long flags, unsigned long new_addr)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long ret = -EINVAL;

if (flags & MREMAP_FIXED) {
/* 固定地址重映射 */
if (new_addr & ~PAGE_MASK)
goto out;

if (new_len > TASK_SIZE || new_addr > TASK_SIZE - new_len)
goto out;
}

/* 查找 VMA */
vma = find_vma(mm, addr);
if (!vma || vma->vm_start != addr)
goto out;

if (old_len != vma->vm_end - addr)
goto out;

/* 缩小映射 */
if (new_len <= old_len) {
ret = do_munmap(mm, addr + new_len, old_len - new_len);
if (ret && old_len != new_len)
ret = addr;
goto out;
}

/* 扩大映射 */
if (new_len > old_len) {
/* 扩大映射 */
ret = mremap_to(addr, old_len, new_len, flags, new_addr);
}

out:
return ret;
}

11.7 msync 系统调用

11.7.1 sys_msync

位置: mm/msync.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
SYSCALL_DEFINE3(msync, unsigned long, addr, size_t, length, int, flags)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long start, end;
int error = 0;

/* 1. 参数校验 */
start = untagged_addr(addr);
end = start + length;

if (offset_in_page(start))
return -EINVAL;
if (!access_ok((void __user *)start, length))
return -ENOMEM;

/* 2. 查找 VMA */
while ((vma = find_vma(mm, start)) != NULL) {
/* 3. 同步页面到文件 */
error = sync_page_range(vma, start, end);

start = vma->vm_end;
if (start >= end)
break;
}

return error;
}

11.7.2 msync 标志

1
2
3
#define MS_ASYNC        1   /* 异步写 */
#define MS_INVALIDATE 2 /* 使缓存无效 */
#define MS_SYNC 4 /* 同步写 */

11.8 madvise 系统调用

11.8.1 sys_madvise

位置: mm/madvise.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SYSCALL_DEFINE3(madvise, unsigned long, start, size_t, len_in, int, behavior)
{
struct mm_struct *mm = current->mm;
unsigned long end, tmp;
int error;

/* 1. 参数校验 */
end = start + len_in;
if (end < start)
return -EINVAL;

/* 2. 查找 VMA */
while (start < end) {
struct vm_area_struct *vma;

vma = find_vma(mm, start);
if (!vma)
return -ENOMEM;

/* 3. 处理建议 */
error = madvise_behavior(vma, start, end, behavior);
if (error)
return error;

start = vma->vm_end;
}

return 0;
}

11.8.2 madvise 建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define MADV_NORMAL     0   /* 无特殊建议 */
#define MADV_RANDOM 1 /* 随机访问 */
#define MADV_SEQUENTIAL 2 /* 顺序访问 */
#define MADV_WILLNEED 3 /* 很快会访问 */
#define MADV_DONTNEED 4 /* 不再需要 */
#define MADV_REMOVE 9 /* 移除页面 */
#define MADV_DONTFORK 10 /* fork 时不复制 */
#define MADV_DOFORK 11 /* fork 时复制 */
#define MADV_MERGEABLE 12 /* 启用 KSM */
#define MADV_UNMERGEABLE 13 /* 禁用 KSM */
#define MADV_HUGEPAGE 14 /* 使用巨页 */
#define MADV_NOHUGEPAGE 15 /* 不使用巨页 */
#define MADV_DONTDUMP 16 /* 不 dump */
#define MADV_DODUMP 17 /* dump */
#define MADV_WIPEONFORK 18 /* fork 时清零 */
#define MADV_KEEPONFORK 19 /* fork 时保留 */

11.9 本章小结

本章介绍了 Linux 6.12 的内存映射:

  1. mmap 概述: 文件映射、匿名映射、共享/私有映射
  2. mmap 系统调用: sys_mmap_pgoff,创建内存映射
  3. mmap 标志: MAP_SHARED、MAP_PRIVATE、MAP_ANONYMOUS 等
  4. do_mmap: 核心映射实现,创建 VMA
  5. mprotect: 修改内存保护权限
  6. munmap: 解除内存映射
  7. mremap: 重新映射内存
  8. msync: 同步内存到文件
  9. madvise: 给出内存使用建议

下一章将介绍匿名内存与页缓存。

  • Title: Linux内核分析之内存管理-11
  • Author: 韩乔落
  • Created at : 2026-01-20 21:39:37
  • Updated at : 2026-02-24 14:05:12
  • Link: https://jelasin.github.io/2026/01/20/Linux内核分析之内存管理-11/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments