内存管理浅析

本来我想不针对于任何具体的操作系统来谈内存管理,但是又觉得不接地气、言之无物。所以我决定在阐述概念的同时,还针对IA32平台Linux下的内存管理做简要的介绍,并且以实验来证明结论。以下内容分拆为几个大标题和小节,内容前后承接。

物理地址空间

首先,什么是物理地址空间?我们知道CPU与外部进行信息传递的公用通道就是总线,一般而言,CPU有三大总线:控制总线、数据总线、地址总线。这三类总线在一定程度上决定了CPU对外部设备的控制和数据传送能力。其中地址总线决定了CPU能向外部输出的地址宽度,也就是CPU的寻址能力。

通过/proc/cpuinfo可以查看具体的数据:

本文只需要关心红色框内的信息即可,我的CPU拥有36位地址总线,其寻址范围是2^36=64G. 那么其物理上理论能编址的上限就是这么大了。关于48 bits virtual的相关信息以及64 bit的现有实现,则可以参考这里

《Intel? 64 and IA-32 Architectures Software Developer’ s Manual》

?

?

重点标记的区域请认真阅读,物理地址空间一定就是内存(DRAM)么?显然不是,文档里也指出了物理地址空间可以映射到read-write memory、read-only memory和memory
mapped I/O.

也就是说,除了常见的内存(DRAM),还有主板上的ROM和EEPROM(BIOS、显存等等)也在这个地址空间里。常见的编址方案有IO独立编址和IO统一编址,具体的讨论可以参考这里。下面就假定读者接受了这一基本事实。

?

启动时的内存信息获取

?

?

BIOS的中断调用

上一节说到,物理地址空间被映射到了主存储器,主板上芯片的存储区域等位置。那么,操作系统如何得知这一映射关系,显然不同品牌的机器和主板不可能完全一致么。答案就在BIOS(Basic Input/Output System)了,那BIOS又如何得知呢?嗯,我查到的资料是……探测。具体说来,BIOS其实是一个通称,显卡、网卡、键盘接口电路等外设上都会有一块ROM芯片用于其初始化检测和功能调用。按照规范,这个部分前两个字节必须是0x55和0xAA(和可启动存储介质的第一个扇区结尾字符一样,注意区别),第三个字节是其ROM以512为单位的代码长度,之后就是代码了。从物理地址A0000~FFFFF之间的区域就是保留给外围设备的,如果外设存在,其自带的ROM就会被映射到这个区域。主板BIOS在机器加电后,会以2KB为单位在C0000~E0000之间检索0x55和0xAA并校验长度,执行ROM的代码。有兴趣你可以去读《BIOS研发技术剖析》类似的书,这里我个人不怎么了解,就不敢再多说了。

啰嗦了这么多,其实只需要明白最初的地址映射表是由BIOS检测并提供给操作系统的就好。而操作系统获取这个表的方法自然就是BIOS的中断调用了。e820调用即可获取这些信息,终端下使用dmesg命令打印内核日志就可以看到内核打印出的物理内存布局了。

简要解释下上面的输出:

Usable:是已经被映射到物理内存(DRAM)的物理地址。

Reserved:这些区间是没有被映射到任何地方,不能当作内存来使用。(内核可以修改这些映射,/proc/iomem文件描述了具体的映射)

ACPI data:映射到用来存放ACPI数据的RAM空间,ACPI Table应读入到这个区间内。

ACPI NVS:映射到存放ACPI数据的空间,操作系统不能使用。

至于具体的e820调用怎么用,就不展开说了。这段之后的信息有兴趣的同学可以接着去读,内核对内存的映射和统计信息也会随后打印出来。

Shadow RAM

?

物理内存管理

?

通过前文的描述,我想大家已经知道了操作系统终归是拿到了一张物理地址空间的映射表了。那么所谓的内存管理,最主要的部分就是如何来管理主存也就是DRAM的空间了。此处主要的挑战就是实现具体的数据结构和算法,使得内存分配的时候高效的分配内存,并且在内存释放时进行相邻内存块的合并回收以避免内存外部碎片的产生。

?

Linux内核采用的伙伴内存分配算法就是用来解决这一问题的。 关于Linux的内存管理有无数的好文章和好书在描述了,本文的定位就是梳理脉络,所以我只给出链接,请大家自行去了解。

?

《物理内存管理中的基本数据结构》

《Linux物理内存管理概述???

伙伴算法的实现自然不只有一种思路,看看下面的文章也是一种启发:

《伙伴分配器的一个极简实现》

?

虚拟内存管理

接下来就是虚拟内存管理了,[保护模式汇编系列之四] 段页式内存管理,请先看看我以前写得这篇文章了解下虚拟内存出现的原因和解决的问题。我不想太纠结于细节,以免这篇文章过于冗长,但是有些细节不交代清楚有没有办法继续下去。只有自己真正理解掌握了所有细节,才敢站在较高的层次上俯视整个知识脉络,这里的Linux虚拟内存管理容我自己理解深入之后再行补充。

大家可以先参考下武特学长的博文,然后就可以进到下一个章节。

?

Linux进程的内存布局

又要偷懒贴文章了,之前有写过一篇《进程眼中的线性地址空间》,这里描述的即是虚拟地址空间里的4G线性地址的映射:

学过操作系统的同学一定知道PCB(Process Control Block,进程控制块)吧。

用课本上的话说,进程控制块是用来描述进程的当前状态,本身特性的数据结构,是进程中组成的最关键部分,其中含有描述进程信息和控制信息,是进程的集中特性反映,是操作系统对进程具体进行识别和控制的依据。

具体到Linux内核中,所谓的PCB其实就是task_struct这个结构体了。既然PCB描述了进程的信息,自然也就包括了进程内存空间的相关描述信息了。内存相关信息在struct mm_struct *mm, *active_mm字段,mm指向进程所拥有的内存描述符,而active_mm指向进程运行时所使用的内存描述符。mm_struct里的pgd_t * pgd字段即指向进程的页目录。struct vm_area_struct

?

探究malloc的效率与写时映射

?

开始描述前,写点代码玩玩先。通过上文的描述,我想大家已经了解了32bit下linux进程拥有的4G线性地址空间只有3G是属于进程所有的。那么我们容易想到,malloc函数从堆里获取到的内存最多也不会超出这个范畴。而程序代码和链接库部分也占据了一定的空间,所以可以申请到的内存的总数应该略与3G这个数字(视进程本身代码和数据占据大小而定)。代码如下:

?

?
#include<stdio.h>#include<stdlib.h>int main(int argc,char*argv[]){int count =0;while(malloc(1*1024*1024)!= NULL){ count++;} printf("count is %f G\n", count /1024.0f); sleep(10000);return EXIT_SUCCESS;}

?

编译运行,结果如下:

?

?

这基本也证实了我们的猜想。接着我们去掉sleep语句用time命令记录内存申请时间可以看到:

?

?

以1MB为单位分配接近3G的内存也太快了点吧,就是逐一建立页表也没有这般迅速吧?
更神奇的还在后面,如果我们让程序sleep,在另一个终端执行free命令查看内存占用的话,会看到神奇的结果:

?

?

used那里居然几乎没有增加,这不科学!

?

Linux系统调用提供的内存获取的函数是brk/sbrk和mmap,而且时以页(通常4K)为单位进行内存的分配的。而Glibc实现的malloc/free是建立在系统调用之上的内存“批发后零售”的函数。问题肯定出在brk/sbrk等系统调用上。这两个系统调用是增加程序可用的堆区的空间,其实内核仅仅只是修改里进程PCB里vm_area_struct链表中堆那个节点的结束位置(许可地址范围),并没有真正去获取物理内存并建立页表的映射。为什么要这么做呢?原因大家可以自己去思考,比如进程申请的内存不一定立即就会完全用到,可以延迟到使用的时候再去分配,以暂时节省物理内存。那么具体流程是怎样的呢?

?

其实,CPU在分页开启后,对于给出的线性地址(此处略过分段)会由MMU进行页表的查阅来翻译为最终的物理地址,如果在页表中查阅不到或者该页不存在呢?此时CPU会产生一个内部异常: 14? #PF 页故障。此时处理流程会转入到内核为该异常创建的对应异常处理函数去执行,内核此处的代码首先会遍历当前进程的vm_area_struct链表,检查该地址是否在许可的地址范围内,如果是为其申请物理内存并建立映射。之后异常返回到触发了异常的代码出继续执行,所以程序接着运行下去。如果发现该地址是非法的地址,内核为给进程发信号SIGSEGV,该信号的默认处理函数即会打印出段错误,然后结束进程。流程即是:
? ? ? ?缺页异常->异常处理函数->task_struct、mm_struct、vm_area_struct->页分配->中断返回

时间: 2024-10-04 18:45:14

内存管理浅析的相关文章

linux内存管理浅析

[地址映射](图:左中)linux内核使用页式内存管理,应用程序给出的内存地址是虚拟地址,它需要经过若干级页表一级一级的变换,才变成真正的物理地址.想一下,地址映射还是一件很恐怖的事情.当访问一个由虚拟地址表示的内存空间时,需要先经过若干次的内存访问,得到每一级页表中用于转换的页表项(页表是存放在内存里面的),才能完成映射.也就是说,要实现一次内存访问,实际上内存被访问了N+1次(N=页表级数),并且还需要做N次加法运算.所以,地址映射必须要有硬件支持,mmu(内存管理单元)就是这个硬件.并且需

内存管理 浅析 内存管理/内存优化技巧

内存管理 浅析 下列行为都会增加一个app的内存占用: 1.创建一个OC对象: 2.定义一个变量: 3.调用一个函数或者方法. 如果app占用内存过大,系统可能会强制关闭app,造成闪退现象,影响用户体验.如何让回收那些不再使用的对象呢?本文着重介绍OC中的内存管理. 所谓内存管理,就是对内存进行管理,涉及的操作有: 1.分配内存:比如创建一个对象,会增加内存占用: 2.清除内存:比如销毁一个对象,会减少内存占用. 内存管理的管理范围: 1.任何继承了NSObject的对象: 2.对其他非对象类

java内存管理浅析

首先感谢强大的网络资源,本博文是根据网络上的各种资源进行整合,然后加入自己的理解而成,可能会与其它网络资源有重复,望其他作者多多包涵.由于初学java,如有不准确的描述还请读者指正.下面正式切入正题: 众所周知,java和C++都是面向对象的编程语言,但是与C++相比,java上手比较容易,而且使用方便.小弟对c++了解不是很多,但是有一点是C++初学者最为头痛的问题,那就是内存管理,这也正是C++和java之间很大的一个区别.在C++中,内存是依靠程序员自己来管理的,编写程序过程中稍有不慎就会

Objective-C 内存管理浅析与循环强引用举例

理解 变量 作用域 变量废弃 持有对象 释放对象 对象所有者(引用计数) 对象废弃 变量所有权修饰符对对象的影响 对象相互持有导致循环强引用,举例如反复执行任务的NSTimer对象的目标对象 又保留了计时器对象,若该NSTimer对象被目标对象持有,便必定会发生循环强引用,因为NSTimer对象会持有目标, 而该NSTimer对象又是目标对象的成员变量也就是目标对象又持有该NSTimer对象 此循环强引用会一直持续到目标对象调用NSTimer对象的invalidate方法释放该NSTimer对象

内存管理 &amp; 内存优化技巧 浅析

内存管理 浅析 下列行为都会增加一个app的内存占用: 1.创建一个OC对象: 2.定义一个变量: 3.调用一个函数或者方法. 如果app占用内存过大,系统可能会强制关闭app,造成闪退现象,影响用户体验.如何让回收那些不再使用的对象呢?本文着重介绍OC中的内存管理. 所谓内存管理,就是对内存进行管理,涉及的操作有: 1.分配内存:比如创建一个对象,会增加内存占用: 2.清除内存:比如销毁一个对象,会减少内存占用. 内存管理的管理范围: 1.任何继承了NSObject的对象: 2.对其他非对象类

转 Linux内存管理原理

Linux内存管理原理 在用户态,内核态逻辑地址专指下文说的线性偏移前的地址Linux内核虚拟3.伙伴算法和slab分配器 16个页面RAM因为最大连续内存大小为16个页面 页面最多16个页面,所以16/2order(0)bimap有8个bit位两个页框page1 与page2组成与两个页框page3 与page4组成,这两个块之间有一个bit位 order(1)bimap有4个bit位order(2)bimap有4个bit位的2个页面分配过程 当我们需要order(1)的空闲页面块时,orde

Windows内存管理和linux内存管理

windows内存管理 windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或者页面,然后把页式虚拟地址与内存地址建立一一对应的页表:并用相应的硬件地址变换机构来解决离散地址变换问题.页式管理采用请求调页或预调页技术来实现内外存存储器的统一管理.其优点是没有外碎片,每个内碎片不超过页的大小.缺点是,程序全部装入内存,要求有相应的硬件支持.例如地址变换机构缺页中断的产生和选择淘

C#编程(七十三)----------浅析C#中内存管理

浅析C#中内存管理 前言:个人觉得C#吸收了各种语言的优点,可谓集大成者,但是不知但,这种集所有语言于一身的情况是好是坏.C#编程的一个优点就是程序员不需要关心具体的内存管理,尤其是垃圾收集器会处理所有的内存清理工作.虽然不比手工管理内存,但是如果要编写高质量的代码,还是要理解后台发生的情况,理解C#的内存管理. 用户可以得到像C++语言那样的效率,而不需要考虑像在C++中那样内存管理工作的复杂性. 注意:这一章的许多内容,可以说全部,都是没有经过事实验证的.您应把这一届看做是一般规则的简化向导

Java内存管理文章合集

http://www.cnblogs.com/springsource/archive/2013/01/11/2856968.html 这个是360图书馆中的那篇!介绍堆内存老年代和新生代很详细 http://www.360doc.com/content/13/1001/14/15643_318381948.shtml http://www.cnblogs.com/xwdreamer/archive/2012/04/01/2428857.html  Java中堆内存与栈内存分配浅析 http:/