进程是如何使用内存的?

程序运行概述linux

程序(咱们这里只讨论单进程状况,存在多进程的程序如淘宝微信等不展开讨论)镜像存在磁盘中,运行时将镜像加载至内存RAM中,而后开始执行。算法

先来看一下CPU的多级存储结构,CPU通用寄存器访问速度最快,其次是Cache,再次是内存,磁盘访问速度最慢。缓存

CPU的多级存储结构微信

对于进程而言,可以使用的地址空间为2^32=4G,那么对于只有2G内存甚至只有256M内存的嵌入式设备怎么办?这个时候就须要MMU负责将进程的虚拟地址转换为内存的真实物理地址。如何管理这种映射关系呢?内核中为每一个进程分配了进程页表。对应下图能够看到,只有正在使用的虚拟地址空间才会真正分配到物理页框;同时对于内核空间地址映射对于不一样进程物理地址同样。app

                                                 进程地址映射url

TLB工做原理spa

上图中不一样进程映射到物理内存使用到了TLB表(地址变换高速缓存),流程为:CPU获取数据或指令:获取进程页表,拿到物理地址;访问内存物理地址拿到真实数据。.net

每次执行指令,先从TLB表中获取,若是未命中,则从内存中获取,同时根据LRU方法更新TLB表。这里提一下,LRU更新方法在不少地方都用获得,像CDN页面缓存、CPU cache缓存等,如何消除cache颠簸的影响,是另外一个话题。设计

局部性原理:时间及空间局部性,即CPU访问某个逻辑地址的数据时,大几率会继续访问该虚拟地址相邻的地址。所以为了保证cache的命中率,通常CPU采用多级流水线设计,将预取指令及相邻的地址空间存放至TLB表中。code

每一个核都有本身的TLB,由MMU内存管理单元模块执行,流程为:CPU发送执行进程的虚拟地址给MMU模块,MMU对应的硬件电路获取TLB表物理地址并访问数据。

若是进程使用了4G虚拟地址,那么所须要的页表项条目为:

4G/4K=1M

每一个页表项为4Byte,所以进程页表占据了4M物理内存空间。这种状态下可使用多级页表减小内存占用,且能够离散存储。

 

从malloc提及

使用malloc分配16k空间,这16k不会当即占用16k真实内存,而是采用写时复制的方式使用。若是物理内存已经写满数据怎么办(可能被多个进程占用)?这个时候就继续使用上面提到的LRU方式进行页表置换。置换过程当中会将换出的页表真实写入磁盘中,通常由daemon守护进程完成。

前面使用malloc分配空间以后,linux内核并未真正给该进程分配物理页,若是对该地址进行写动做,会由MMU触发缺页中断,这时进入内核终端处理程序,将数据从磁盘加载至内存。

 

                                                    CPU寻址流程

下面总结一下CPU寻址流程:CPU将进程虚拟地址经过地址总线发送给MMU,由MMU硬件电路转换成物理地址,而后经过数据总线访问内存获取数据。

CPU获取物理地址流程以下:

一、CPU发送虚拟地址,MMU查询自身TLB表,若是命中:根据物理地址访问数据页;若是不命中,经过cr3寄存器取出进程页表物理地址访问进程页表。

       二、这里访问进程页表先是从Cache中获取缓存页框,若是Cache命中:从Cache中获取获得物理地址,更新TLB表。若是不命中,直接访问内存页表。

三、进程页表保存了进程使用过程当中全部的虚拟地址对应的表项,所以经过页表地址偏移可直接获取到页表项,并更新至Cache中。

四、继续第一步,MMU从Cache中获取页表项,并查看虚拟地址是否已分配物理页框。若分配,则使用LRU算法更新TLB表并经过内存物理地址访问数据;若没分配物理页框,则触发Page Fault缺页中断,进入并执行中断接管程序。

五、中断处理程序为发送的虚拟地址分配真实物理页框,若是内存数据已满,根据LRU算法淘汰最久未用的页面,置换磁盘的进程映像至物理页框,并更新进程页表。(用户空间的缺页中断还会判断是否非法访问等权限校验,这里不展开)

六、中断处理程序返回,CPU获取执行权,继续执行指令。

获取到了物理地址,根据物理地址获取实际数据流程为:MMU经过物理地址查询Cache,若是缓存命中,CPU直接获取Cache中数据并继续执行;若是不命中,那么根据物理地址获取内存中的数据,硬件电路将物理页框存入Cache中,CPU从Cache中获取数据。

 

CPU获取数据流程

理想状态下,当进程局部性较高时,如执行while循环,MMU获取TLB表命中拿到物理地址,经过物理地址访问Cache命中拿到真实数据。

 

具体示例代码分析代码数据流及执行流

char* ptr =malloc(1*1024*1024); //一、分配内存memcpy(ptr, 'a',10); //二、写入数据ptr[100] = 'b'; //三、赋值


第一行:char* ptr =malloc(1*1024*1024);

假定malloc分配的虚拟地址是0x00000040,表示【0x00000040-0x00100040】这1M进程虚拟空间被分配成功。接下来咱们看一下进程页表状况。

0x00000040  ->  64

0x00100040  ->  1048640

也就是这段虚拟空间占了1048576块页表项。这里注意,若是是一级页表,无论有没有执行malloc,这些页表项都存在,但若是是多级页表,只有真实访问数据的时候这些页表才会占用物理内存空间。接下来计算页号和页内偏移:

起始地址虚拟页号:64/(4*1024)= 0,页内偏移为64%(4*1024)= 0x40;

结束地址虚拟页号:1048640/(4*1024)= 256,页内偏移为1048640%(4*1024)= 0x40;

第二行:memcpy (ptr,'g', 10);

根据ptr虚拟地址找到页表项,发现并无分配物理页框,触发缺页中断后分配获得第100页框。而后CPU将ptr对应虚拟地址日后的10字节空间写为‘a’。这里说一下,虽然进程页号对应的物理页框序号不必定相同,由于页框大小为4k,因此虚拟地址在进程页号中的偏移等于映射的物理地址在物理页框中的偏移

CPU根据虚拟地址按照上一章的流程找到对应物理地址,将第100物理页框加载至Cache中,CPU将10字节’a’写入Cache。

第三行:ptr[100] = 'b';

根据局部性原理,这里访问的就是同一个虚拟地址附近的数据,所以TLB和Cache都命中。

    能够看到,进程在执行malloc时是将物理页框直接分配给虚拟地址,里面的初始数据有内存的电气特性决定,是随机值,所以maoolc出来的空间使用前须要赋值或者执行初始化操做。


本文分享自微信公众号 - 机械猿(on_ourway)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索