Linux的内存管理机制
linux内存管理卷帙浩繁,本文只能层层递进地带你领略冰山轮廓,通过本文你将了解到以下内容:
- 为什么需要管理内存
- linux段页管理机制
- 内存碎片的产生机理
为什么需要管理内存
老子的著名观点是无为而治,简单说就是不过多干预而充分依靠自觉就可以有条不紊地运作,理想是美好的,现实是残酷的。
在linux系统中如果以一种原始简单的方式管理内存是存在一些问题的,我们来看几个场景。
内存管理的问题
- 进程空间隔离问题 假如现在有ABC三个进程运行在linux的内存空间,设定os给进程A分配的地址空间是0-20M 进程B地址空间30-80M,进程C地址空间90-120M,如图:
在某些时候程序空间的访问可能出现问题,比如进程A访问了属于进程B的空间,进程B访问了属于进程C的空间,甚至修改了空间的值,这样就会造成混乱和错误,所以实际中是不允许这种情况发生的。
- 内存效率和内存不足问题
机器的内存是有限资源,而进程数量是无法确定的,如果在某些时候已经启动的进程占据了所有内存空间,此时就无法启动新进程了,因为没有新内存可分配了,但是我们观察到已经启动的进程有时候是在睡大觉,也就是给了内存也不用,这样效率确实是有点低,所以我们需要一个管理员把不用的内存倒腾出来,另外连续内存实在是很珍贵,很多时候我们没法有效及时地分配连续内存,因此虚拟化和离散化可能会有效提高内存的使用率。
- 程序定位调试和编译运行问题
由于程序运行时的位置时不确定的,我们在定位问题、调试代码、编译执行时都会存在很多问题,我们希望每个进程有一致且完整的地址空间,同样的起始位置放置了堆、栈以及代码段等,从而简化编译和执行过程中的 linker 链接器、loader 加载器的使用。
虚拟地址空间
为了解决上述的一些问题,linux系统引入了虚拟空间的概念,虚拟化的出现和硬件有密不可分的联系,可以说是软硬件组合的结果,虚拟地址空间就是在程序和物理空间所增加的中间层,这也是内存管理的重点。
磁盘 disk 作为一种大容量的存储也作为"内存"的一部分参与程序的运行,内存管理系统会将不常用非活跃内存进行页面换出,可以认为内存是磁盘的缓存,内存中保留了活跃的数据,从而间接扩展了有限的物理内存空间,这部分空间称为虚拟内存是相对于物理内存而言的。
段页管理机制
本文并不深入地将分段管理内存和分页管理内存,因为将这些细节的优秀文章很多,感兴趣的使用搜索引擎一键即达。
段页机制也不是一蹴而就的,经历了单纯物理分段、单纯分页、单纯逻辑分段等阶段,最终演进出来了分段和分页结合的内存管理方式,段页结合获得了分段和分页的优势也避免了单一模式的弊端,是一种比较好的管理模式。
本文对于段页管理机制只想通俗地说明一些概念,段页管理机制是分段式管理和分页式管理的组合,段式管理是逻辑上的管理方式,分页管理是偏物理上的管理方式。
计算机里面的一些技术和实现都可以在现实生活中找到缩影,所谓艺术和科技源自生活大概就是这个意思吧。
举个栗子:
在进行居民户籍管理时都会有区县市的概念,但是实际上并没有这种实体,都是逻辑上的,增加了这些行政单位之后可以让地址管理更加直接。
对于我们居民来说唯一的实体就是自己的房子住所,这是物理上的单位,是真实存在的,这也是最基本的单位。
对比linux段页时管理来说,段是逻辑上的单位相当于区县市的概念,页是物理上的单位相当于小区/房屋的概念,这样就方便很多。
多级页表也很好理解,总的物理内存假如有4GB,页大小为4KB,那么就总共有2^20个页,数量还是非常大的,这样编号来建立索引寻址比较不方便,所以引入多级页表,来减少存储便于管理。
段页机制加持下的逻辑地址和物理地址的映射关系简图,也就是虚拟地址到物理地址的对应关系:
内存管理单元( MMU Memory Management Unit )是硬件层组件,主要提供将虚拟地址映射为物理地址。
MMU 的工作流程:CPU 生成逻辑地址交给分段单元,分段单元进行处理将逻辑地址转换为线性地址,再线性地址交给分页单元,分页单元根据页表映射转换内存物理地址,其中可能出现缺页中断。
缺页中断( Page Fault )是只当软件试图访问一个虚拟地址时,经过段页转换为物理地址之后,此时发现该页并没有在内存中,这时 cpu 就会报出中断,再进行相关虚拟内存的调入工作或者分配工作,如果出现异常也可能直接中断。
物理内存和内存碎片
前面说的段页管理机制算是虚拟空间的部分,然而linux内存管理的另外一个重要部分就是物理内存的管理了,也就是如何分配和回收物理内存,这就涉及到一些内存分配算法和分配器。
物理内存分配器
分配器和分配算法就像公司财务,内存就像公司资金,如何把资金合理使用是财务的本职工作,如何把物理内存合理使用是分配器的分内之事。
内存碎片分类和机理
如果我们不知道内存碎片是什么,试想一下我们常说的碎片化的时间,也就是那些虽然空闲但是没有被利用的时间,其实内存也是如此。
无论是时间还是内存被碎片化之后都无法被有效利用,因此合理管理减少碎片对我们来说是至关重要的,这也是物理内存分配算法和分配器的研究重点。
按照碎片的位置和产生原因,内存碎片分为外部碎片和内部碎片,我们看下这两种碎片的直观展示:
从图中可以知道,外部碎片是进程与进程间未分配的内存空间,外部碎片的出现和进程频繁的分配和释放内存有直接关系,这个很好理解,模拟一下分配不同空间的进程不同时间释放就可以看到外部碎片的产生了。
内部碎片主要因为分配器粒度问题以及一些地址限制导致实际分配的内存大于所需内存,这样在进程内部就会出现内存空洞。
虽然虚拟地址让进程使用的内存在物理内存上是离散的,但是很多时候进程需要一定量连续物理内存,如果大量碎片存在,就会造成无法启动进程的问题,如图Process7需要一块连续的物理内存却无法被分配:
在Linux系统下,监控内存常用的命令是free、top等,下面是一个free命令的执行结果:
要了解Linux的内存管理,首先要明白上例中各个名词的意义:
- total:物理内存的总大小。
- used:已经使用的物理内存多小。
- free:空闲的物理内存值。
- shared:多个进程共享的内存值。
- buffers / cached:用于磁盘缓存的大小(这部分是从物理内存中划出来的)。
- 第二行Mem:代表物理内存使用情况。
- 第三行(-/+ buffers/cached):代表磁盘缓存使用状态。
- 第四行:Swap表示交换空间内存使用状态(这部分实际上是从磁盘上虚拟出来的逻辑内存)。
free命令输出的内存状态,可以从两个角度来看:内核角度、应用层角度。
1. 从内核角度来查看内存的状态:
就是内核目前可以直接分配到,不需要额外的操作,即free命令第二行 Mem 的输出。从上例中可见,41940 + 16360492 = 16402432,也就是说Mem行的 free + used = total,注意,这里的free并不包括buffers和cached。
2. 从应用层角度来查看内存的状态:
也就是Linux上运行的程序可以使用的内存大小,即free命令第三行 -/+ buffers/cache 的输出。再来做一个计算41940+(465404+12714880)=13222224,即Mem行的free + buffers + cached = -/+ buffers/cache行的free,也就是说应用可用的物理内存值是Mem行的free、buffers和cached三者之和,可见-/+ buffers/cache行的free是包括buffers和cached的。
对于应用程序来说,buffers/cached占有的内存是可用的,因为buffers/cached是为了提高文件读取的性能,当应用程序需要用到内存的时候,buffers/cached会很快地被回收,以供应用程序使用。
物理内存和虚拟内存 物理内存就是系统硬件提供的内存大小,是真正的内存。在linux下还有一个虚拟内存的概念,虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为 交换空间(Swap Space) 。
linux的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。而进行这种交换所遵循的依据是“LRU”算法(Least Recently Used,最近最少使用算法)。
最后介绍下Buffers和Cached有什么用
在任何系统中,文件的读写都是一个耗时的操作,当应用程序需要读写文件中的数据时,操作系统先分配一些内存,将数据从磁盘读入到这些内存中,然后应用程序读写这部分内存数据,之后系统再将数据从内存写到磁盘上。如果是大量文件读写甚至重复读写,系统读写性能就变得非常低下,在这种情况下,Linux引入了缓存机制。
buffers与cached都是从物理内存中分离出来的,主要用于实现磁盘缓存,用来保存系统曾经打开过的文件以及文件属性信息,这样当操作系统需要读取某些文件时,会首先在buffers与cached内存区查找,如果找到,直接读出传送给应用程序,否则,才从磁盘读取,通过这种缓存机制,大大降低了对磁盘的IO操作,提高了操作系统的数据访问性能。而这种磁盘高速缓存则是基于两个事实:第一,内存访问速度远远高于磁盘访问速度;第二,数据一旦被访问,就很有可能短期内再次被访问。
另外,buffers与cached缓存的内容也是不同的。buffers是用来缓冲块设备做的,它只记录文件系统的元数据(metadata)以及 tracking in-flight pages,而cached是用来给文件做缓冲。更通俗一点说:buffers主要用来存放目录里面有什么内容,文件的属性以及权限等等。而cached直接用来记忆我们打开过的文件和程序。
为了验证我们的结论是否正确,可以通过vi打开一个非常大的文件,看看cached的变化,然后再次vi这个文件,感觉一下是不是第二次打开的速度明显快于第一次?
接着执行下面的命令:
find /* -name *.conf
看看buffers的值是否变化,然后重复执行find命令,看看两次显示速度有何不同。
Linux内存管理的哲学
Free memory is wasted memory.
Linux的哲学是尽可能多的使用内存,减少磁盘IO,因为内存的速度比磁盘快得多。 Linux总是在力求缓存更多的数据和信息,内存不够时,将一些不经常使用的数据转移到交换分区(Swap Space)中以释放更多可用物理内存,当然,如果交换分区的数据再次被读写时,又会被转移到物理内存中,这种设计思路提高了系统的整体性能。而Windows的处理方式是,内存和虚拟内存一起使用,不是以内存操作为主,结果就是IO的负担比较大,可能拖慢处理速度。
Linux和Windows在内存管理机制上的区别
在Linux系统使用过程中,你会发现,无论你的电脑内存配置多么优越,仍然不时的发生可用内存吃紧的现象,感觉内存不够用了,其实不然。这是Linux内存管理的优秀特性,无论物理内存有多大,Linux都将其充分利用,将一些程序调用过的硬盘数据缓存到内存,利用内存读写的高速性提高系统的数据访问性能。而Window只在需要内存时,才为应用分配内存,不能充分利用大容量的内存空间。 换句话说,每增加一些内存,Linux都能将其利用起来,充分发挥硬件投资带来的好处,而Windows只将其作为摆设。
所以说,一般我们不需要太关注Linux的内存占用情况,而如果Swap占用率一直居高不下的话,就很有可能真的是需要扩展内存了。