用户虚拟地址空间布局架构
目录
一、Linux内核整体架构及子系统
二、内存管理模块架构
三、用户地址空间的内存布局
(1)mm_struct
(2)vm_area_struct
一、Linux内核整体架构及子系统
Linux内核是操作系统中最为关键的一个部分,对下管理所有硬件资源,对上提供系统调用接口给程序员使用。
下图是对操作系统整体架构的简图:
但是我们可以将其更具体化一点,专门看看内核以下部分:
二、内存管理模块架构
整体架构如下:
对于我们上层无论是malloc还是new,本质都调用到了比如ptmalloc、tcmalloc等。即malloc是对外呈现的状态,但是可以根据需求选择底层的逻辑。
ptmalloc和tcmalloc本质是一个内存池,因为操作系统本身的系统调用并未提供合适的内存管理机制,仅仅是通过brk、sbrk等来移动堆的指针(是在操作虚拟地址,实际物理地址分配则是第一次使用的时候再和硬件交互)。但是这样就必须要求程序员自己管理每一个内存块,而ptmalloc等则直接利用brk向内存硬件申请一大片连续的内存,然后自己对这个内存进行切分,挂载形成内存池,我们之前曾模拟过tcmalloc,原理也是如此。
比如tcmalloc就很适合多线程高并发的状态,而ptmalloc则更加全能一点,各方面都比较均衡。
MMU是CPU内部的一个模块,他的作用就将虚拟地址转换成物理地址,即每一个线程误以为自己独享整个内存空间,而其中有一个页表缓存,用于缓解CPU和内存速度不匹配的问题。
每一次进程切换的时候,都会把pgd的值给到MMU模块,MMU根据pgd的地址找到真实物理内存的页表,从而完成进程后续的操作。
三、用户地址空间的内存布局
(1)mm_struct
/* 包含必要的头文件 */
#include <linux/list.h>
#include <linux/rbtree.h>
#include <linux/spinlock_types.h>
#include <linux/types.h>struct vm_area_struct;/* 定义mm_struct结构体 */
struct mm_struct {struct vm_area_struct *mmap; /* 指向虚拟内存区域链表的指针 */struct rb_root mm_rb; /* 管理虚拟内存区域的红黑树根节点 */spinlock_t mmap_lock; /* 用于保护对mmap链表和mm_rb红黑树的并发访问 */unsigned long task_size; /* 任务的虚拟地址空间大小 */pgd_t * pgd; /* 指向页全局目录(Page Global Directory)的指针 */atomic_t mm_users; /* 使用该地址空间的用户数(包括内核线程) */atomic_t mm_count; /* 地址空间的引用计数 *//* 以下字段用于标识代码段和数据段的地址范围 */unsigned long start_code, end_code, start_data, end_data;/* 堆相关地址 */unsigned long start_brk, brk; /* 堆的起始地址和当前顶部地址 */unsigned long start_stack; /* 栈的起始地址 *//* 用于跟踪内存分配和释放的统计信息 */struct mm_rss_stat rss_stat;/* 内存管理的其他相关字段 */struct list_head mmlist; /* 用于将mm_struct链接到全局内存描述符链表 */struct file *core_file; /* 指向核心转储文件的指针(如果有的话) */struct user_struct *user; /* 指向用户结构体的指针,包含用户相关的资源限制等信息 */struct mm_struct *user_ns_exposed; /* 指向用于用户命名空间暴露的mm_struct */struct anon_vma *root_anon_vma; /* 用于管理匿名虚拟内存区域的根节点 */
};
mm_struct是pcb中管理该进程的宏观结构体。他定义了堆、栈、代码段以及数据段的地址范围等,同时还维护了一个vm_area_struct链表来记录上述区域的细节。
具体如下图所示:
注意这里vm_area_struct是一个链表指针,并非一个mm_struct中只有一个,而是每一个区域都会有,比如代码段、堆、栈、数据段都有自己的vm_area_struct
(2)vm_area_struct
struct vm_area_struct {struct mm_struct *vm_mm; /* 指向所属的mm_struct结构体 */unsigned long vm_start; /* 虚拟内存区域的起始地址 */unsigned long vm_end; /* 虚拟内存区域的结束地址 */unsigned long vm_flags; /* 内存区域的标志,如读写权限、可执行等 */struct rb_node vm_rb; /* 用于将该vma插入到mm_rb红黑树中的节点 */union {struct list_head vm_next_share; /* 用于链接到共享该vma的链表 */struct raw_prio_tree_node prio_tree_node;};struct list_head vm_list; /* 用于链接到mmap链表 */struct file * vm_file; /* 如果该vma映射了文件,指向对应的文件结构体 */void * vm_private_data; /* 私有数据指针,用于特定的内存映射场景 */struct vm_area_struct *vm_next; /* 指向下一个vma(在链表中) */
};
既然已经有了brk、start_stack等为什么还需要vm_area_struct这样一个结构体呢?我们看到在mm_struct中,存放的是一个vm_area_struct的链表,而非单个的vm_area_struct。
这说明每一个区域(如堆、栈,代码段、数据段),都会有一个vm_area_struct。他就是用来精细化管理进程虚拟地址空间的结构体。我们知道不同的区域往往有着不同的属性,如可读、可写权限,以及是否和文件相关。
同时我们可以看到其中有一个成员是rb_node类型的,他是一个将该vm_area_struct结构体插入红黑树的字段,其键值为虚拟内存区域的起始地址,利用红黑树高效的查询效率,内核可以快速定位某个虚拟地址属于哪个vm_area_struct。例如内存访问缺页中断的时候,就利用到了它。
再比如我们平时的非法访问错误。就是在这里产生的,当程序想对一个只读区域尽心修改,内核首先会找到该区域的vm_area_struct,然后获取vm_flags标志位,得到读写权限,如果权限不对则发生错误。