程序地址空间可能大家第一眼看到会觉得是表示的是内存,其实它并不是内存,不能保存数据,实际上它是一块虚拟地址,用结构体描述。
我们研究的背景是在kernel2.6.32,32位平台上。
先写一段代码验证程序虚拟地址
运行结果:
从输出结果可以看出:父子进程输出的变量值和地址是一样的,很好理解,因为子进程继承了父进程,父子并没有对变量进行任何修改。但是,如果我们将代码稍微改动一下:子进程先运行,将子进程中的g_val变量进行修改,完成之后,父进程再读取。
我们发现,父子进程输出的地址是一样的,但是内容却不一样。所以我们能得出如下结论:
- 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量。
- 地址值是一样的,说明该地址绝对不是物理地址。在Linux地址下,这种地址叫做虚拟地址。
- 我们在C/C++语言中所看到的的地址,其实全是虚拟地址。物理地址,用户一概看不到,由操作系统统一管理,操作系统负责将虚拟地址转化成物理地址。
程序地址空间
之前所说的“程序的地址空间”是不准确的,准确的应该说是“进程地址空间”。我们来看图理解。
从上图可以说明前面的问题:同一变量,地址相同,其实是虚拟地址相同(虚拟地址可以相同是因为进程认为自己独占4G的内存),内容不同是映射到了不同的物理地址。我们的编译器通常编译的是虚拟地址。
对于每个程序而言,每个进程都有一个独立的地址空间。虚拟地址都是从0开始到某个上限结束。这是因为某个程序在何种硬件设备的机器上运行并不能事先确定,因为写出来的程序一般会在很多计算机上运行,而每台计算机的内存占用情况事先无法知晓,因此,指令里面的地址是程序空间的虚拟地址(程序地址)。所以当程序真正运行起来的时候,每个虚拟地址必然要对应一个物理地址(物理地址是实际存在的地址,而虚拟地址是用一个结构体维护起来的)。那么,如何从虚拟地址到物理地址进行一一对应呢?这里就需要一种机制去实现---分页内存管理机制。
分页内存管理机制
分页内存管理机制将虚拟内存和物理内存都分成大小相等的部分,我们称之为页,然后按页进行内存分配。页表的大小一般为4个字节,划分成两个页表,一级页表和二级页表,其对应关系如图:
页表的概念:
- 页表属于软件,放在内存空间的页表区,是由操作系统维护的,页表的基地址存于CP15的C2寄存器中,表中每一行叫做一个地址变换条目entry,对应于虚拟存储空间的一页,这个entry包含虚拟内存块和物理块地址的对应关系、该页的权限、该页的缓冲特性。
- 每一个进程都有一个页表,PCB表中有指针指向页表。
- 当进程访问某个虚拟地址,去查页表的时候如果发现对应的数据不在物理内存中,则发送缺页异常。
- 缺页异常处理过程:将进程需要的数据从磁盘中拷贝到物理内存里,如果物理内存已经满了,没有空地方了,那就找一个页覆盖,当然如果被覆盖的页曾经被修改过,就需将此页写回到磁盘。
页表的状态:
- 如果页表的有效位置为1,那么就说明虚拟地址存储的内存存储在物理页当中。
- 如果页表的有效位置为0,那么就说明虚拟地址存储的内存没有存储在物理页中,发生了缺页异常,需要处理这个异常。
页表的工作原理:
- 当CPU想访问父进程的g_val的值的时候,先根据其虚拟地址找到虚拟页,根据页表,找出页表中g_val对应的位置,查看该页表的表项是否有效,有效则为1。DRMA命中,根据物理页号找到物理页中的内容,返回。
- 若为无效(0),则参数缺页异常,调用内核缺页异常处理程序,内核会选择一个物理页作为牺牲页,将该页的内容刷新到磁盘空间,然后将g_val映射到该物理页上面,然后页表中的该表项有效位置1,第二位存放对应物理内存页的地址内容
- 缺页处理完成后,返回中断前的指令,重新指向,此时缓存命中,执行1。
- 将找到的内容映射到高速缓存当中,CPU从高速缓存中获取该值。
MMU
MMU是Memory Management Unit的缩写,中文名是内存管理单元,它是中央处理器(CPU)中用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权,多用户多进程操作系统。
不同进程4G的虚拟地址空间如何切换
MMU进行虚拟地址到物理地址转换时需要页表的,那么不同进程都有各自的4G虚拟地址空间,那么这些不同的区间如何划分和切换呢?实际上针对不同的进程,内核会维护各自进程的内存页表,我们内核代码是通过配置不同进程的内存页表来完成不同进程虚拟地址切换的。当我们的CPU在进行进程调度的时候,有一个节点就是要重新设置对应进程的页表。这样切换到不同的进程运行不用的程序时,就能保证各自空间的独立性。
为什么需要通过虚拟地址空间进行映射
采用虚拟地址,所有的进程都认为自己独占系统资源,所有的进程都可以以相同的方式来看待每个数据分段,实现了进程之间的独立性。同时,能让我们的物理内存更加安全,因为我们只需要对虚拟地址空间进行操作,而不需要直接动物理内存。