Linux虚拟地址空间布局

布局

每个用户进程都提供了一个虚拟地址空间,虚拟地址空间其上是内核地址空间。Linux中,线性的虚拟地址空间由一些区域(段)组成,区域的构成是许多连续虚拟页面。
在这里插入图片描述
这并不是一个经典的虚拟地址空间布局,布局的方式特定于体系结构。
虽然有差异,但是他们都有下列的共同成分。

  • 代码段
  • 环境变量和命令行参数
  • 内存映射

Linux里的task_struct中有一个指向mm_struct结构体的指针,mm_struct这个结构体里面保存了进程的内存信息。
其部分成员变量如下:在这里插入图片描述

//定义在文件<mm_types.h>
struct mm_struct
{
……
	unsigned long mmap_base; //mmp区域的基地址
	unsigned long task_size; //进程虚拟地址空间的长度
……
	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;
}

start_codeend_code 标记了虚拟地址空间中,可执行代码所占区域的开始地址和结束地址。
start_dataend_data标记了虚拟地址空间中,已初始化数据所占区域的开始和结束地址。
start_brk标记了当前运行时堆的起始地址,brk标记了当前堆的结束地址,堆是动态分配的,也就是堆区域的长- 度会发生变化,所以brk的值可以改变。【brk读作“break”】
arg_startarg_end标记了虚拟地址空间中,参数列表所占区域的起始地址和结束地址。
env_startenv_end标记了虚拟地址空间中,环境变量所占区域的起始地址和结束地址
参数列表和环境变量所占的区域位于栈中最高的位置,是栈的初始化数据
mmap_base标记了虚拟地址空间中,用于内存映射的起始地址。
task_size标记了进程的地址空间长度

还需要考虑进程标志位PF_RANDOMIZE,如果设置了,内核将不会为栈和内存映射的起点选择固定位置。

栈的起始地址为STACK_TOP,STACK_TOP是一个宏,如果设置了进程标志位PF_RANDOMIZE,则起始地址会减少一个小的随机量,起始地址向下偏移。每个体系结构都必须定义STACK_TOP,多数设置为TASK_SIZE

在代码段的起始地址和最低可用地址之间还有一定的间距,用于捕获NULL指针。

内存映射的区域起始地址为mm_struct->mmap_base,通常设置成TASK_UMMAPPED_BASE,值被定义为TASK_SIZE / 3

地址空间如果要求随机化机制,那么mmap的起始位置可以减去一个随机的偏移量,但是体系结构要求必须确保对齐到物理页面(页帧)。

组织

mm_struct这个结构体还包含了下面的成员。

struct mm_struct
{
	struct vm_area_struct *mmap;
	struct rb_root mm_rb;
	struct vm_area_struct *mmap_cache;
}

vm_area_struct是一个结构体类型,这个结构体描述了一个区域(段)的相关信息。

struct vm_area_struct
{
	struct mm_struct *vm_mm; //
	unsigned long vm_start;
	unsigned long vm_end;
	
	struct vm_area_struct *vm_next;
	pgport_t vm_page_prot;
	unsigned long vm_flags; //
	struct rb_node vm_rb;
}

vm_start:这个区域的起始地址
vm_end:这个区域的结束地址
vm_next:指向单链表的下一个结点。
vm_page_port: 这个区域的所有页的读写执行权限
vm_flags:判断这个页面是和其他进程共享的还是私有的。

每个段都有一个vm_area_struct用于描述的结构体,那么一个进程就有许多的vm_area_struct,所以需要管理,将这些vm_area-struct组织起来。

两种组织的方式:

  • 组织成一个单链表,mm_struct->mmp指向链表的头结点。
  • 组织成一个红黑树,mm_rb是树的根结点。
    在这里插入图片描述

内存映射

内存映射:将虚拟内存的一个区域和一个磁盘上的文件关联起来,用以初始化这段区域的过程,该过程称内存映射

内存映射的文件类型:

  • 1.普通文件。 文件区被划分成页大小的片,每一片包含一个虚拟页的初始内容。如果映射到的虚拟地址的区域大于文件区,那么区域剩下的未能初始化的部分用0来初始化。
  • 2.匿名文件。 匿名文件由内核创建,包含的全是二进制零。映射到匿名文件的区域的虚拟页面可称为请求二进制零的页

在这里插入图片描述

文件数据在硬盘上的存储实际上并不是连续的,这里是为了方便理解才假设是连续的,文件数据在硬盘的存储是分布在若干的区域里。

如果一个虚拟页面被初始化,页面的调动则需要通过交换空间(又可称交换区域、交换文件),交换空间可以限制当前运行的进程能够分配的虚拟页面的数量。

共享对象

一个文件可以称为一个对象。
一个对象映射到虚拟内存中,可以作为共享对象、或者私有对象。

引入共享对象的作用:
每个进程都提供自己私有的虚拟地址空间,可以免受其他进程的影响。然而许多进程有同样的只读文本区域,意味着一份同样的数据要驻留在物理内存多份,这样真的是太过奢侈!内存映射给我们一个清晰的机制可以改善这种浪费——>>许多的进程可以共享对象

在这里插入图片描述
因为文件名是唯一的。所以当进程2要映射这个对象时,内核可以判定出这个对象已经被其他进程映射了,所以进程2直接把页表条目指向对应的物理页面就行。

映射到共享对象的虚拟内存的区域称为共享区域
映射到私有对象的虚拟内存的区域称为私有区域

系统提供了一种叫做写时拷贝的机制,开始生命周期的时候,私有对象的方式基本和共享对象是一样的。因为要维持进程的独立性,当修改数据的时候,会影响到其他的进程,所以只要有一个进程尝试去修改私有区域时,会触发一个保护故障,调用故障处理程序,会在物理内存中创建这个页面的新的拷贝,更新页表条目指向这个新的拷贝,然后恢复这个页面的可写权限。这个过程实际上是在"赌",堵它会不会被修改,即使赌错了也没有任何的损失,大不了拷贝一份就可以。
在这里插入图片描述

Linux缺页中断

当发生缺页时,会触发一个缺页异常,缺页异常使得控制转移至内核中的缺页处理程序。

  • 1.判断虚拟地址是否合法。合法指的是,这个虚拟地址在某个区域内。如果虚拟地址不合法,缺页异常处理程序会触发一个段错误,然后终止该进程。
  • 2.判断权限是否足够。进程是否有读、写、执行这个虚拟地址区域的权限。如果权限不够,缺页异常处理程序会触发一个保护异常,然后终止该进程
  • 3.正常情况,开始处理。
    a.在物理内存中选择一个牺牲页
    b.如果牺牲页在物理内存驻留时被修改过、则需要把牺牲页写回磁盘,以更新牺牲页在磁盘上的副本。
    c.换入新的页面
    d.缺页处理程序返回时,CPU重新启动造成缺页的指令
    e.正常翻译
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小酥诶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值