0%

Linux内存管理(六)进程地址空间

原图

进程地址空间

进程地址空间在内核中使用vm_area_struct数据结构来描述,简称VMA,表示进程地址空间或进程线性区。由于这些地址空间属于各个用户进程,因此在用户进程的mm_struct数据结构中有相应的成员,用于对这些VMA进行管理。

内存区域

进程地址空间(process address space)是指进程可寻址的虚拟地址空间,进程可以通过内核的内存管理机制动态地添加和删除内存区域,这些内存区域在Linux内核采用VMA数据结构来抽象描述。

每个内存区域具有相关的权限,如可读、可写或者可执行权限。若一个进程访问了不在有效范围的内存区域,或者非法访问了内存区域,或者以不正确的方式访问了内存区域,那么处理器会报告缺页异常。在Linux内核的缺页异常处理中会处理这些情况,严重的会报告“SegmentFault'”并终止该进程。

内存区域主要包含内容如下:

  • 代码段映射:可执行文件中包含只读并可执行的程序头,如代码段和init段等
  • 数据段映射:可执行文件中包含可读/可写的程序头,如数据段和未初始化数据段等
  • 用户进程栈:通常位于用户空间的最高地址,从上往下延伸。它包含栈帧,里面包含了局部变量和函数调用参数等
  • mmap映射区域:位于用户进程栈下面,主要用于mmap系统调用
  • 堆映射区域:malloc()函数分配的进程虚拟地址就是这段区域

每个进程都有一套页表,这样每个进程地址空间就是相互隔离的。即使两个进程地址空间的虚拟地址是相同的,但是经过两套不同页表的转换之后,它们也会对应不同的物理地址。

mm_struct数据结构

Linux内核需要管理每个进程所有的内存区域以及它们对应的页表映射,所以必须抽象出一个数据结构,这就是mm_struct数据结构。进程控制块(Process Control Block,PCB)数据结构task_struct中有一个指针mm,该指针指向这个mm_struct数据结构。mm_struct数据结构定义在include/linux/mm_types.h文件中,下面是它的主要成员。

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
struct mm_struct {
struct {
struct vm_area_struct *mmap; /* list of VMAs */
struct rb_root mm_rb;
u64 vmacache_seqnum; /* per-thread vmacache */
#ifdef CONFIG_MMU
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
#endif
unsigned long mmap_base; /* base of mmap area */
unsigned long mmap_legacy_base; /* base of mmap area in bottom-up allocations */
#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES
/* Base addresses for compatible mmap() */
unsigned long mmap_compat_base;
unsigned long mmap_compat_legacy_base;
#endif
unsigned long task_size; /* size of task vm space */
unsigned long highest_vm_end; /* highest vma end address */
pgd_t * pgd;

#ifdef CONFIG_MEMBARRIER
atomic_t membarrier_state;
#endif

atomic_t mm_users;

atomic_t mm_count;

#ifdef CONFIG_MMU
atomic_long_t pgtables_bytes; /* PTE page table pages */
#endif
int map_count; /* number of VMAs */

spinlock_t page_table_lock;
struct rw_semaphore mmap_lock;

struct list_head mmlist;

unsigned long hiwater_rss; /* High-watermark of RSS usage */
unsigned long hiwater_vm; /* High-water virtual memory usage */

unsigned long total_vm; /* Total pages mapped */
unsigned long locked_vm; /* Pages that have PG_mlocked set */
atomic64_t pinned_vm; /* Refcount permanently increased */
unsigned long data_vm; /* VM_WRITE & ~VM_SHARED & ~VM_STACK */
unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE & ~VM_STACK */
unsigned long stack_vm; /* VM_STACK */
unsigned long def_flags;

seqcount_t write_protect_seq;

spinlock_t arg_lock; /* protect the below fields */

unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;

unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

struct mm_rss_stat rss_stat;

struct linux_binfmt *binfmt;

/* Architecture-specific MM context */
mm_context_t context;

unsigned long flags; /* Must use atomic bitops to access */

struct core_state *core_state; /* coredumping support */

#ifdef CONFIG_AIO
spinlock_t ioctx_lock;
struct kioctx_table __rcu *ioctx_table;
#endif
struct user_namespace *user_ns;

/* store ref to file /proc/<pid>/exe symlink points to */
struct file __rcu *exe_file;
#ifdef CONFIG_MMU_NOTIFIER
struct mmu_notifier_subscriptions *notifier_subscriptions;
#endif
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
pgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
#ifdef CONFIG_NUMA_BALANCING
unsigned long numa_next_scan;

/* Restart point for scanning and setting pte_numa */
unsigned long numa_scan_offset;

/* numa_scan_seq prevents two threads setting pte_numa */
int numa_scan_seq;
#endif
atomic_t tlb_flush_pending;
#ifdef CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
/* See flush_tlb_batched_pending() */
bool tlb_flush_batched;
#endif
struct uprobes_state uprobes_state;
#ifdef CONFIG_HUGETLB_PAGE
atomic_long_t hugetlb_usage;
#endif
struct work_struct async_put_work;

#ifdef CONFIG_IOMMU_SUPPORT
u32 pasid;
#endif
} __randomize_layout;

unsigned long cpu_bitmap[];
};

mm_struct数据结构中主要成员的含义如下:

  • mmap:进程里所有的VMA形成一个单链表,这是该链表的头
  • mmrb:VMA红黑树的根节点
  • get_unmapped_area:用于判断虚拟内存空间是否有足够的空间,返回一段没有映射过的空间的起始地址,这个函数会使用具体的处理器架构的实现
  • mmap_base:指向mmap空间的起始地址。在32位处理器中,mmap空间的起始地是0x4000 0000
  • pgd:指向进程的PGD(一级页表)
  • mm_users:记录正在使用该进程地址空间的进程数目,如果两个线程共享该地址空间那么mm_users的值等于2
  • mm_count:mm_struct结构体的主引用计数
  • mmap_sem:保护VMA的一个读写信号量
  • mmlist:所有的mm_struct数据结构都连接到一个双向链表中,该链表的头是init_mm内存描述符,它是init进程的地址空间
  • start_code,end_code:代码段的起始地址和结束地址
  • start_data,end_data:数据段的起始地址和结束地址
  • start_brk:堆空间的起始地址
  • brk:表示当前堆中的VMA的结束地址
  • total_vm:已经使用的进程地址空间总和

从进程的角度来观察内存管理,可以沿着mm_struct数据结构进行延伸和思考,如下图所示。

VMA数据结构

VMA(vm_area_struct)数据结构定义在mm_types.h文件中,其主要成员如下。

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
include/1inux/mm_types.h>
struct vm_area_struct {
/* The first cache line has the info for VMA tree walking. */

unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address
within vm_mm. */

/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next, *vm_prev;

struct rb_node vm_rb;

unsigned long rb_subtree_gap;

/* Second cache line starts here. */

struct mm_struct *vm_mm; /* The address space we belong to. */

pgprot_t vm_page_prot;
unsigned long vm_flags; /* Flags, see mm.h. */

struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} shared;

struct list_head anon_vma_chain; /* Serialized by mmap_lock &
* page_table_lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */

/* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops;

/* Information about our backing store: */
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
units */
struct file * vm_file; /* File we map to (can be NULL). */
void * vm_private_data; /* was vm_pte (shared mem) */

#ifdef CONFIG_SWAP
atomic_long_t swap_readahead_info;
#endif
#ifndef CONFIG_MMU
struct vm_region *vm_region; /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;

VMA数据结构中各个成员的含义如下:

  • vm_start和vm_end:指定VMA在进程地址空间的起始地址和结束地址
  • vm_next和vm_prev:进程的VMA都连接成一个链表
  • vmrb:VMA作为一个节点加入红黑树,每个进程的mm_struct数据结构中都有一棵红黑树mm->mm_rb
  • vm_mm:指向该VMA所属进程的mm_struct数据结构
  • vm_page_prot:VMA的访问权限
  • vm_flags:描述该VMA的一组标志位
  • anon_vma_chain和anon_vma:用于管理反向映射(Reverse Mapping,RMAP)
  • vm_ops:指向许多方法的集合,这些方法用于在VMA中执行各种操作,通常用于文件映射
  • vm_pgoff:指定文件映射的偏移量,这个变量的单位不是字节,而是页面的大小件映射。(PAGE SIZE)。对于匿名页面来说,它的值可以是0或者vm_addr/PAGE_SIZE
  • vm_file:指向file的实例,描述一个被映射的文件

mm_struct数据结构是描述进程内存管理的核心数据结构,该数据结构提供了管理VMA所需要的信息,每个VMA都要连接到mm_struct中的链表和红黑树,以方便查找。

VMA按照起始地址以递增的方式插入mm_struct->mmp链表中。当进程拥有大量的VMA时,扫描链表和查找特定的VMA是非常低效的操作,如在云计算的机器中,所以内核中通常需要红黑树来协助,以便提高查找速度。 站在进程的角度来看,我们可以从进程控制块。task_struct数据结构里顺藤摸瓜找到该进程所有的VMA,如图所示。

  • task_struct结结构中有一个mm成员指向进程的内存管理描述符mm_struct数据结构
  • 可以通过mm_struct数据结构中的mmap成员来遍历所有的VMA
  • 也可以通过mm_struct数据结构中的mm_rb成员来遍历和查找VMA
  • mm_struct数据结构的pgd成员指向进程的页表,每个进程都有一份独立的页表
  • 当CPU第一次访问虚拟地址空间时会触发缺页异常。在缺页异常处理中,分配物理页面,利用分配的物理页面来创建页表项并且填充页表,完成虚拟地址到物理地址的映射关系的建立

VMA的属性

作为一个进程地址空间的区间,VMA是有属性的,如可读/可写、共享等属性。vm_flags成员描述这些属性,描述了该VMA的全部页面信息,包括如何映射页面、访问每个页面的权限等信息,VMA属性的标志位如下所示。

  • VM_READ: 可读属性
  • VM_WRITE: 可写属性
  • VM_EXEC: 可执行
  • VM_SHARED: 允许被多个进程共享
  • VM_MAYREAD:允许设置VM_READ属性
  • VM_MAYWRITE: 允许设置VM WRITE属性
  • VM_MAYEXEC: 允许设置VM EXEC属性
  • VM_MAYSHARE: 允许设置VM SHARED属性
  • VM_GROWSDOWN: 该VMA允许向低地址增长
  • VM_UFFD_MISSING: 表示该VMA适用于用户态的缺页异常处理
  • VM_PFNMAP: 表示使用纯正的PFN,不需要使用内核的page数据结构来管理物理页面
  • VM_DENYWRITE: 表示不允许写入
  • VM_UFFD_WP: 用于页面的写保护跟踪
  • VM_LOCKED: 表示该VMA的内存会立刻分配物理内存,并且页面被锁定,不会被交换到交换分区
  • VM_IO: 表示I/0内存映射
  • VM_SEQ_READ: 表示应用程序会顺序读该VMA的内容
  • VM_RAND_READ: 表示应用程序会随机读该VMA的内容
  • VM_DONTCOPY: 表示在创建分支时不要复制该VMA
  • VM_DONTEXPAND: 通过mremapo系统调用禁止VMA扩展
  • VM_ACCOUNT: 在创建IPC以共享VMA时,检测是否有足够的空闲内存用于映射
  • VM_HUGETLB: 用于巨页的映射
  • VM_SYNC: 表示同步的缺页异常
  • VM_ARCH_1: 与架构相关的标志位
  • VM_WIPEONFORK: 表示不会从父进程相应的VMA中复制页表到子进程的VMA中
  • VM_DONTDUMP: 表示该VMA不包含到核心转储文件中
  • VM_SOFTDIRTY: 软件模拟实现的脏位。用于一些特殊的架构,需要打开CONFIG_MEM_SOFT_DIRTY
  • VM_MIXEDMAP: 表示混合使用了纯PFN以及page数据结构的页面,如使用vm_insert_page()函数插入VMA
  • VM_HUGEPAGE: 表示在madvise系统调用中使用MADV_HUGEPAGE标志位来标记该VMA
  • VM_NOHUGEPAGE: 表示在madvise系统调用中使用MADV_NOHUGEPAGE标志位来标记该VMA
  • VM_MERGEABLE: 表示该VMA是可以合并的,用于KSM机制
  • VM_SPECIAL: 表示该VMA是不可以合并的

VMA属性的标志位可以任意组合,但是最终要落实到硬件机制上,即页表项的属性中。VMA属性到页表属性的转换如下图所示。vm_area_struct数据结构中有两个成员和属性相关:一个是vm_flags成员,用于描述VMA的属性;另外一个是vm_page_prot成员,用于将VMA属性标志位转换成与处理器相关的页表项的属性,它和具体架构相关。

在创建一个新的VMA时使用vm_get_page_prot()函数可以把vm_flags标志位转化成具页表项的硬件标志位。

1
2
3
4
5
6
7
8
9
10
<mm/mmap.c>
pgprot_t vm_get_page_prot(unsigned long vm_flags)
{
pgprot_t ret = __pgprot(pgprot_val(protection_map[vm_flags &
(VM_READ|VM_WRITE|VM_EXEC|VM_SHARED)]) |
pgprot_val(arch_vm_get_page_prot(vm_flags)));

return arch_filter_pgprot(ret);
}
EXPORT_SYMBOL(vm_get_page_prot);

这个转化过程得益于内核预先定义了一个内存属性数组protection_map[],我们只需要根据vm_flag标志位来查询这个数组即可,在这个场景下,通过查询protection_map[]数组可以获得页表属性。

1
2
3
4
pgprot_t protection_map[16] __ro_after_init = {
__P000, __P001, __P010, __P011, __P100, __P101, __P110, __P111,
__S000, __S001, __S010, __S011, __S100, __S101, __S110, __S111
};

protection_map[]数组的每个成员代表一个属性的组合,如__P000表示无效的PTE属性,__P001表示只读属性,__P1O0表示可执行属性(PAGE_EXECONLY)等.

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
#define __P000		PAGE_NONE
#define __P001 __pgprot(CF_PAGE_VALID \
| CF_PAGE_ACCESSED \
| CF_PAGE_READABLE)
#define __P010 __pgprot(CF_PAGE_VALID \
| CF_PAGE_ACCESSED \
| CF_PAGE_WRITABLE)
#define __P011 __pgprot(CF_PAGE_VALID \
| CF_PAGE_ACCESSED \
| CF_PAGE_READABLE \
| CF_PAGE_WRITABLE)
#define __P100 __pgprot(CF_PAGE_VALID \
| CF_PAGE_ACCESSED \
| CF_PAGE_EXEC)
#define __P101 __pgprot(CF_PAGE_VALID \
| CF_PAGE_ACCESSED \
| CF_PAGE_READABLE \
| CF_PAGE_EXEC)
#define __P110 __pgprot(CF_PAGE_VALID \
| CF_PAGE_ACCESSED \
| CF_PAGE_WRITABLE \
| CF_PAGE_EXEC)
#define __P111 __pgprot(CF_PAGE_VALID \
| CF_PAGE_ACCESSED \
| CF_PAGE_READABLE \
| CF_PAGE_WRITABLE \
| CF_PAGE_EXEC)

下面以只读属性(PAGE_READONLY)来看,它究竟包含哪些页表项的标志位。

1
2
3
#define PAGE_READONLY		__pgprot(_PAGE_DEFAULT | PTE_USER | PTE_RDONLY | PTE_NG | PTE_PXN | PTE_UXN)
#define _PAGE_DEFAULT (_PROT_DEFAULT | PTE_ATTRINDX(MT_NORMAL))
#define _PROT_DEFAULT (PTE_TYPE_PAGE | PTE_AF | PTE_SHARED)

把上述的宏全部展开,我们可以得到如下页表项的标志位。

  • PTE_TYPE_PAGE:表示这是一个基于页面的页表项,即设置页表项的Bit[1:O]
  • PTE_AF:设置访问位
  • PTE_SHARED:设置内存共享属性
  • MT_NORMAL:设置内存属性为normal
  • PTE_USER:设置AP访问位,允许通过用户权限访问该内存
  • PTE_NG:设置该内存对应的TLB只属于该进程
  • PTE_PXN:表示该内存不能在特权模式下执行
  • PTE_UXN:表示该内存不能在用户模式下执行
  • PTE_RDONLY:表示只读属性

内核如何管理内存

Linux 进程在内核中作为进程描述符task_struct 的实例实现。task_struct 中的mm字段指向内存描述符mm_struct ,它是程序内存的执行内容。它存储了如上所示的内存段的开始和结束、进程使用的物理内存页数(rss代表Resident Set Size)、使用的虚拟地址空间 以及其他信息。

每个虚拟内存区域(VMA)是一个连续的虚拟地址范围;这些区域永远不会重叠。vm_area_struct的实例完整地描述了一个内存区域,包括其起始和结束地址、用于确定访问权限和行为的标志,以及用于指定该区域映射的文件(如果有)的vm_file字段。不映射文件的 VMA 是匿名的。除了内存映射段之外,上面的每个内存段(例如,堆、堆栈)对应于单个VMA。这不是必需的,尽管这在 x86 机器中很常见。VMA 不关心它们位于哪个段。 程序的 VMA 都以链表形式存储在其内存描述符中mmap字段,按起始虚拟地址排序,并作为以mm_rb字段为根的红黑树 。红黑树允许内核快速搜索覆盖给定虚拟地址的内存区域。当您读取文件/proc/pid_of_process/maps时,内核只是遍历进程的 VMA 链接列表并打印每一个VMA。

VMA 的大小必须是页面大小的倍数。处理器查阅页表以将虚拟地址转换为物理内存地址。每个进程都有自己的一组页表;每当发生进程切换时,用户空间的页表也会切换。Linux 在内存描述符的pgd字段中存储指向进程页表的指针。页表中的每个虚拟页都对应一个页表项(PTE),在常规 x86 分页中,它是一个简单的 4 字节记录,如下所示:

malloc函数

malloc()函数是C标准库封装的一个核心函数,C标准库做一些处理后会调用Linux的系线调用接口brk向系统申请内存。

brk系统调用

brk系统调用主要实现在mm/mmap.c文件中。

1
SYSCALL_DEFINE1(brk, unsigned long, brk)

详细流程这里不一一列出来了,下面用一张图概括brk的流程,如下:

malloc流程

假设不考虑libc的因素,malloc()分配100字节,那么内核会分配多少字节呢?处理器的MMU的最小处理单元是页面,所以内核分配内存、建立虚拟地址和物理地址映射关系都以页面为单位,PAGE_ALIGN(addr)宏让地址按页面大小对齐。 下图所示为malloc()函数的实现流程。

mmap函数

mmap/munmap函数是用户空间中常用的系统调用函数,无论是在用户程序中分配内存、读写大文件、链接动态库文件,还是多进程间共享内存,都可以看到mmp/munmap()函数的身影。mmp/munmap函数的声明如下。

1
2
3
4
#include <sys/mman.h>
void *mmap (void *addr,size_t length,int prot,int flags,
int fd,off_t offset);
int munmap(void *addr,size_t length);

mmap/munmap函数的参数如下。

  • addr:用于指定映射到进程地址空间的起始地址,为了提高应用程序的可移植性,一般设置为NULL,让内核来分配一个合适的地址
  • length:表示映射到进程地址空间的大小
  • prot:用于设置内存映射区域的读写属性等
  • flags:用于设置内存映射的属性,如共享映射、私有映射等
  • fd:表示这是一个文件映射,fd是打开的文件的句柄
  • offset:在文件映射时,表示文件的偏移量。prot参数通常表示映射页面的读写权限,有如下参数组合
  • PROT_EXEC:表示映射的页面是可以执行的
  • PROT_READ:表示映射的页面是可以读取的
  • PROT_WRITE:表示映射的页面是可以写入的
  • PROT_NONE:表示映射的页面是不可访问的

flags参数是一个很重要的参数,可以设置为以下值。

  • MAP_SHARED:创建一个共享映射的区域。多个进程可以通过共享映射方式来映射一个文件,这样其他进程也可以看到映射内容的改变,修改后的内容会同步到磁盘文件中
  • MAP_PRIVATE:创建一个私有的写时复制的映射。多个进程可以通过私有映射的方式来映射一个文件,这样其他进程不会看到映射内容的改变,修改后的内容也不会同步到磁盘文件中
  • MAP_ANONYMOUS:创建一个匿名映射,即没有关联到文件的映射
  • MAP_FIXED:使用参数addr创建映射,如果在内核中无法映射指定的地址,那么mmap会返回失败,参数addr要求按页对齐。如果addr和length指定的进程地址空间和已有的VMA重叠,那么内核会调用do_munmapO函数把这段重叠区域销毁,然后重新映射新的内容
  • MAP_POPULATE:对于文件映射来说,会提前预读文件内容到映射区域,该特性只支持私用映射

通过参数fd可以看出mmap映射是否和文件相关联,因此在Linux内核中,映射可以分成匿名映射和文件映射。

  • 匿名映射:没有映射对应的相关文件,匿名映射的内存区域的内容会初始化为0
  • 文件映射:映射和实际文件相关联,通常把文件内容映射到进程地址空间,这样应用程序就可以像操作进程地址空间一样读写文件

私有匿名映射

当使用参数fd=-1且flags = MAP_ANONYMOUS|MAP_PRIVATE时,创建的mmap映射是私有匿名映射。私有匿名映射常见的用途是在glbc分配大内存块时,如果需要分配的内存大MMAP_THREASHOLD(128KB),glibc会默认使用mmap代替brk来分配内存。

共享匿名映射

当使用参数fd=-1且flags = MAP_ANONYMOUS | MAP_SHARED时,创建的mmap映射是共享匿名映射。共享匿名映射让相关进程共享一块内存区域,通常用于父、子进程之间的通信创建共享匿名映射有如下两种方式。

  • 使fd=-1且flags = MAP_ANONYMOUS | MAP_SHARED。在这种情况下,do_mmap_pgoffO->mmap_region()函数最终会调用shmem_zero_setup():来打开一个特殊的“/dev/zero”设备文件
  • 直接打开“/dev/zero”设备文件,然后使用这个文件句柄来创建mmap

上述两种方式最终都调用shmem模块来创建共享匿名映射。

私有文件映射

创建文件射时如果flags设置为MAPP_PRIVATE,就会创建私有文件映射。私有文件映射常用的场景是加载动态共享库。

共享文件映射

创建文件映射时,如果flags设置为MAP_SHARED,就会创建共享文件映射。如果prot参数指定了PROT_WRITE,那么打开文件时需要指定O_RDWR标志位。共享文件映射通常有mmap如下两个常用的场景。

  • 读写文件。把文件内容映射到进程地址空间,同时对映射的内容做了修改,内核的回写(writeback)机制最终会把修改的内容同步到磁盘中
  • 进程间通信。进程之间的进程地址空间相互隔离,一个进程不能访问另外一个进程的地址空间。如果多个进程同时映射到一个文件,就实现了多进程间的共享内存通信。如果一个进程对映射内容做了修改,那么另外的进程是可以看到的

小结

mmap机制在Linux内核中实现的代码框架和brk机制非常类似,其中有很多关于VMA的操作。mmap机制和缺页中断机制结合在一起会变得复杂很多。mmap机制在Linux内核中的实现流程如图所示。

参考文献

https://letrungthang.blogspot.com/2010/12/chuyen-tiet-kiem-mua-nha-cua-gioi.html
《奔跑吧Linux内核》