操作系统中的内存管理
一、虚拟内存和物理内存
1、为什么要引入虚拟内存?
如果程序可以直接访问物理内存,在多个程序同时运行的时候,如果操作了同一块物理内存,那么后面的程序对内存的操作会修改前面程序对内存的操作,这是错误的。所以引入了虚拟内存,程序直接操作的是虚拟内存,由CPU的内存管理单元进行虚拟内存和物理内存之间的映射。当程序访问虚拟地址的时候,由操作系统转换成对应的物理地址,保证不同进程运行的时候使用的是不同的物理地址。
2、物理地址和虚拟地址
程序运行的时候直接访问的是虚拟内存,然后由内存管理单元映射到对应的物理内存进行访问。程序使用的内存地址是虚拟内存地址,实际在硬件里面的空间地址是物理地址。
二、内存分段和内存分页
1、内存分段
段基地址和段内偏移量一起得到物理内存地址。段选择子保存在段寄存器里面,段选择子里面有段号,用做段的索引,段表里面保存段的基地址、段的界限等。
分段机制把虚拟内存分为四段,每个段在段表里面有一个项,在这一项里面找到段的基地址,再加上偏移量,就可以找到对应的在物理内存里面的地址。
问题:
内存碎片问题:类似于java里面的标记清除算法的内存碎片问题,在进行内存分配与释放的时候,会有不连续的空闲内存产生,这个时候如果有程序需要稍微大一点的内存的时候,就没有办法进行分配。
外部内存碎片:程序之外,物理内存里面,不连续的内存碎片。解决办法:内存交换,把其中一个程序占用的内存写到磁盘里面,然后再从磁盘里面进行读取,读取的时候要紧邻着之前已经使用的内存,最大程度上让空闲内存连续。
内部内存碎片:程序里面会有不常用的内存,也会导致内存的浪费。
2、内存分页
因为分段的时候会产生内存碎片,为了减少内存碎片的产生,在操作的内存的大小变小,也就是内存分页。Linux中每页是4KB(这里类似于MySQL中的InnoDB引擎,InnoDB引擎中一页是16KB)。
内存空间不够的时候,操作系统会使用页置换算法,把符合置换条件的页释放掉,也就是换出;需要的时候再加载进来,称为换入。这里页面置换算法是以页为单位的,一页或者几页;但是在分段内存里面,是会把应用程序占用的全部物理内存进行换入和换出,所以分页在内存交换的时候效率要高。
内存分页的时候虚拟地址和物理地址之间的映射关系:虚拟地址分为:页号和页偏移量,其中页号是在页表里面的索引,页表包含物理页每页对应的物理内存的基地址,基地址加页内偏移量形成物理地址。
缺点:因为页表也需要占用空间,并且每个进程都会维护一个页表,那么如果进程很多的情况下,就会占用一定的空间。
解决办法:多级页表(类似于MySQL底层使用的数据结构:B+树),使用多级页表来减少空间的占用。
页在需要的时候进行加载,不需要的时候不加载。这就会存在某些热点的数据被频繁的使用,所以会使用缓存来缓存热点页(所以MySQL中InnoDB引擎会有BufferPool来进行缓存,MyBatis里面有一级缓存和二级缓存,CPU里面有一级缓存、二级缓存和三级缓存,这些都是一样的道理,为了提高读取数据的速度,保证程序的执行效率)
文章中部分图来自于小林coding的图解操作系统