内存管理是从单板上电运行uboot启动引导linux并完成文件系统挂载(文件系统管理Nandflash)过程前两个环节都需要完成的重要工作,并且随着程序推进的内存管理也逐渐完善起来。如果一步到位直接编写一个非常完整的内存管理系统,这个过程是相当麻烦且低效的。u-boot做为启动引导程序,其核心功能就是引导内核镜像,所以其内存管理功能并不用像Linux内核中的内存管理一样功能齐全。u-boot中没有内存分配、回收、缓存等功能,内存管理其实只做一件事:虚实地址映射,而且是固定映射。内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射、页面分配、页面回收、页面交换、冷热页面、紧急页面、页面碎片管理、页面缓存、页面统计等,而且对性能也有很高的要求。为实现内核上层的这些功能,linux到目前共有bootmem、memblock、buddy system和slab四种内存管理模型。其中bootmem与memblock是在内存启动阶段使用,buddy system和slab在系统运行过程中使用。
内存管理子系统的职能:1、虚拟内存地址与物理内存地址之间的映射
2、物理内存的分配
u-boot内存管理文章:U-Boot内存管理_生活需要深度的博客-优快云博客。
A 区:虚实转换; B 区:Buddy伙伴系统和slab系统; C 区:内核空间分类; D 区:单个进程的内存管理 task_struct 说明
页表就是索引,伙伴系统就是内存池.
other: 内核用struct page结构体表示每个物理页,struct page结构体占40个字节.假定系统物理页大小为4KB,对于4GB物理内存,1M个页面,故所有的页面page结构体共占有内存大小为40MB.
用户空间对应进程,所以当进程切换,用户空间也会跟着变化;
内核空间是由内核负责映射,不会跟着进程变化;内核空间地址有自己对应的页表,用户进程各自有不同额页表
0. 内存管理历史与由来
内存管理原因详细见博客Linux I/O 原理和零拷贝( zero-copy )技术原理详解_生活需要深度的博客-优快云博客
QA1:为什么需要内存管理
内存容量虽然在增大,但没有办法满足讲所有用户的所有程序装入内存的需求,需要内存管理,同时为了实现多进程隔离和方便用户在不需要关心内存管理管理的情况下进行程序开发。
1. RAM、MMU、Cache与DMA
内存管理是基于一定硬件基础上搭建起来的管理系统,分别包含各种存储介质,如寄存器、Cache、SRAM、DRAM等;MMU、CPU和DMA等硬件功能模块。CPU根据PC指针位置依次运行位于各种RAM空间上的程序,当然指令执行也是存在多种不同的水线保证命令的告诉执行。CPU运行指令设计的数据都是首先要加载内部寄存器指令中才可以完成基本的运算,之后再存储回存储空间当中。整个存储体系的和CPU之间的距离、存储器读写时间、存储器大小存储器和存储器价格关联金字塔形状如下。
内存管理单元MMU实现内存地址的全局编址管理,基于TLB等方式实现内存的虚拟化工作;Cache是为了进一步加快CPU运行和介于内存和CPU之间基本软件无法触及的一个硬件管理模块;为了进一步释放CPU完成高效数据处理,引入DMA实现数据高效的数据搬运工作。
1.1 RAM
UMA(Uniform Memory Access)和NUMA(Non-Uniform Memory Access)是两种不同的内存访问架构。
UMA是一种对称多处理(SMP)系统的内存访问架构,它指的是所有处理器对内存的访问具有相同的延迟。在UMA系统中,所有处理器共享同一总线或交叉点,可以直接访问共享内存,这意味着无论哪个处理器访问内存,延迟都是相同的,UMA适用于小规模的多处理器系统。
NUMA是一种非对称多处理(NUMA)系统的内存访问架构,它指的是不同处理器对内存的访问延迟可能不同。在NUMA系统中,每个处理器都有自己的本地内存和本地内存控制器,可以更快地访问本地内存。
-
不管是UMA和NUMA架构,物理内存的物理地址都是唯一的。UMA架构可以理解为NUMA架构的一个节点,是NUMA架构的一种特殊情况。
物理地址是物理内存中每个字节的身份信息,我们要对物理内存进行读写必须要知道物理地址,每块物理内存区域通过起始地址和大小表示,每个物理地址的用途从硬件诞生就完成统一编址确定下来。Linux内存管理可以简化为RAM物理内存节点物理地址管理。
页机制的核心是页(page),page为物理内存管理的最小单位,避免每个物理地址花费大量精力去管理。struct page实际大小大概为40B,并没有4KB大小,所以struct page只是物理页的映射和描述,并不是实际的物理页。
每一个物理页都有一个对应的页号,称为PFN,PFN和物理地址一样,同样是唯一的,通过PFN我们可以定位到物理页的首地址。
(1)PFN和物理地址相互转换
PFN和物理地址遵循一定的转换关系,转换公式如下:
#define PFN_PHYS(x) ((phys_addr_t)(x) << PAGE_SHIFT)
#define PHYS_PFN(x) ((uisigned long)(x) >> PAGE_SHIFT))
-
PFN转物理地址
一个PFN对应4KB的内存,4KB为4096 Byte,PFN * 4096为PFN对应的物理页的首地址(物理地址),PFN * 4096 = PFN << 12。
-
物理地址转PFN
PFN = 物理地址 / 4096 = 物理地址 >> 12。
(2)page和PFN相互转换
平坦内存模式采用struct pglist_data指针数组表示,每个数组元素都是一个struct pglist_data对象,struct pglist_data对象表示一个内存节点,内存节点就是我们前面讲到的NUMA架构内存节点,struct pglist_data对象有一个node_mem_map成员,该成员是一个struct page指针,指向一个页表。
平坦内存模型page和PFN转换:
平坦内存模式只有一个节点0,平坦内存模型物理起始地址偏移为0。
-
PFN转page
PFN转page首先得找到页表起始地址,计算方式如下:
struct page *mem_map = node_data[0]->node_mem_map;
PFN和ARCH_PFN_OFFSET的差值为数组索引值,页表起始地址+数组索引值得到page地址。
-
page转PFN
page地址和mem_map地址的差值为数组索引值,数组索引值+ARCH_PFN_OFFSET为PFN值。
page和PFN相互转换需要结合内存模型讨论。
不连续内存模型是指内存空间被分割成多个不连续的区域,每个区域具有不同的物理地址范围。
不连续内存模型可以提供更灵活的内存管理方式,但也增加了程序设计和实现的复杂性。
非连续内存模型和平坦内存模型都是采用struct pglist_data指针数组来实现,非连续内存模型通常来说有多个节点,每个节点之间的物理内存的物理地址不连续,平坦内存模型可以理解为一个节点的非连续内存模型。
非连续内存模型和平坦内存模型page和PFN转换方式是一样的。
非连续内存模型的mem_map和ARCH_PFN_OFFSET每个节点都不一样。
稀疏内存模型是一种用于管理大容量内存的机制,它允许系统在物理内存不连续的情况下有效地使用内存空间。稀疏内存模型中,内存被划分为多个区域,每个区域可以包含不同数量的页框。
稀疏内存模型的主要目的是解决内存碎片化的问题。在传统的连续内存模型中,当系统中有大量空闲页框但它们不连续时,无法有效利用这些内存空间,而稀疏内存模型通过将内存划分为多个区域,可以更灵活地管理内存碎片,从而提高内存利用率。
稀疏内存模型的管理单位为mem_section,定义如下:
section_mem_map为页表起始地址,一个struct mem_section代表一块连续的物理内存区域,每个mem_section都有一个对应的编号,通过section编号可以定位到对应的mem_section。
稀疏内存模型page和PFN转换方式:
-
page转PFN
struct page结构体flags成员记录了page的section编号,通过flags解析出section编号,再通过section编号定位到mem_section对象,mem_section对象成员section_mem_map为页表地址,page地址和section_mem_map之间的差值为PFN。
-
PFN转page
通过PFN获取到section编号,通过section编号定位到mem_section对象,section_mem_map + PFN获取到page地址。
(3)page和物理地址转换
page和物理地址转换,需要借助PFN完成,page和物理地址不能直接转换,需要先转换成PFN,再通过PFN完成转换。
水线:对每一个NODE内存空间大小划分low,high,根据实际的内存使用大小和这个值的判定确定当前内存紧张程度,进而确定对应的内存回收方式。
1.2 MMU TLB多级页表管理映射逻辑
Linux 操作系统是采用段页式内存管理方式,页式存储管理能有效地提高内存利用率(解决内存碎片),而分段存储管理能反映程序的逻辑结构并有利于段的共享。将这两种存储管理方法结合起来,就形成了段页式存储管理方式。虚拟内存是指程序使用的逻辑地址,物理地址是真实RAM空间地址排布。每个进程4G。所有进程共享物理内存4G,所以逻辑地址和物理地址不是一一对应,需要地址转换。页表由3部分组成:页目录,页面,页内偏移。32bit只有3级 0 -11位:页内偏移OFFSET 12-21位:页面表偏移PT(PTE 页表项.指向一张具体的物理内存页) 22-31位:页面目录偏移PGD。
寻址过程如下:
1)操作系统从寄存器CR3获得当前页面目录指针(基地址);
2)基地址+页面目录偏移->页面表指针(基地址);
3)页面表指针+页面表偏移->内存页基址;
4)内存页基址+页内偏移->具体物理内存单元。
页号=逻辑地址/页面大小;页面偏移量=逻辑地址%页面大小 。
页目录保存页表项的地址,页表项保存物理地址,最后1项保存4k页内偏移。进程页表长这样子:
举例说明地址映射:比如要访问线性地址0xBFC0 4FFE(1byte),2进制形式
1).mmu先取线性地址高10位(31-22)=767,x4=3068=0xBFC,+cr3=0x3800 0bfc,即页目录项在内存的地址,此地址的内容0x392f f000即页表首地址(页对齐最后12位清0)
2).mmu取线性地址下面10位数(21-12)=4,x4=16=0x10,+0x392f f000=0x392f f010,即页表项在内存的地址,此地址的内容0x3080 4000即物理页面的首地址(页对齐最后12位清0)
3).mmu取线性地址最后12位数(11-0)=4094=0xffe作为低12位,0x3080 4000的前20位作为高20位,组成一个新的32位数0x3080 4ffe即线性地址0xBFC0 4FFE对应的物理地址
2.2 内核页表和进程页表的区别和联系?
进程页表访问:虚拟内存与物理内存的对应。 内核页表:建立物理内存和disk的对应address_space。
mmap的时候,vmalloc分配内核内存,更改内核页表,然后拷贝内核页表到进程页表.
无论进程页表还是内核页表都在内核中运行,都由内核修改.
经典问题:两个进程虚拟地址相同,物理地址不同。本质就是进程页表的内容不同。
1.3 Cache多级处理结构
Cache同大核数据一致性。对于现在常见的多核架构,多核共享数据与地址总线,为了实现多核之间同步,必须通过相应的总线机制保证各核数据一致性。
Cache与DMA数据一致性。DMA和CPU核是异构多核,两者都可以访问数据总线,为了保证访问数据的一致性,常见的方式是分配的对应内存通过硬件方式保持一致性或者是两者公用的内存区域不适用Cache。
Cache异构核数据一致性。当前核写数据到内存给另外一个和进行数据访问时,在写结束需要write_cache保证数据刷新到DDR;另外一个核进行数据读取时,首先invalid_cache保证数据从内存中被读取出来。在cache刷新中不要使用全部刷的方式,这样会导致数据混乱误刷,使用精确控制起始地址和长度的刷新函数。
1.4 DMA数据传输与Linux内存分配方式
1.5 内存硬件读写流程
对于读写内存,硬件设计了3条优化路径。
- 首先L1 cache支持虚拟地址寻址,保证CPU出来的虚拟地址(VA)不需要转换成物理地址(PA)就可以用来直接查找L1 cache,提高cache查找效率。当然用VA查找cache,有安全等缺陷,这需要CPU做一些特别的设计来进行弥补。
- 如果L1 cache没有命中,这就需要进行地址转换,把VA转换成PA。linux的内存映射管理是通过页表来实现的,但是页表是放在内存中的,如果每次地址转换过程都需要访问一次内存,其效率是十分低下的。这里CPU通过TLB硬件单元来加速地址转换。
- 获得PA后,在L2 cache中再查找缓存数据。L2 cache一般比L1 cache大一个数量级,其查找命中率也更高。如果命中获得数据,则可避免去访问内存,提高访问效率。
1.6 内存屏障与volatile
内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。
volatile 只作用于编译阶段,对运行阶段没有任何影响。
2. 内存管理初始化
从u-boot过度到linux内核初期,使用__create_page_tables创建最简单的一个物理地址到虚拟地址的映射,并在这个基础之上完成bootmem或者memblock的初始化工作。在系统初始化阶段会先启用一个bootmem分配器和memblock分配器,此分配器是专门用于启动阶段的,一个bootmem分配器管理着一个node结点的所有内存,也就是在numa架构中多个node有多个bootmem,他们被链入bdata_list链表中保存。收集了整个内存布局的信息,memblock模块中已经保存了所有需要管理memory region的信息,同时,系统也为所有的内存(reserved除外)创建了地址映射。
虽然整个内存管理系统没有ready,但是通过memblock模块已经可以在随后的初始化过程中进行动态内存的分配。 有了这些基础,随后就是伙伴系统的初始化将bootmem管理的所有物理页框释放到伙伴系统中去。
3. 内存管理算法
内存管理主要包含功能有:
- 内存空间的分配与回收。有操作系统屏蔽硬件复杂功能帮助开发人员完成内存的分配和回收
- 地址转换。在多道程序中,程序逻辑地址与内存中的物理地址不可能一致,需要存储管理提供地址转换功能
- 内存空间扩充。
- 内存共享。
- 存储保护。保证各道程序各自的存储空间内运行,互不干扰
其内部分为2个体系:页管理和对象管理。页管理体系是一个两级的层次结构,对象管理体系是一个三级的层次结构,分配成本和操作对CPU cache和TLB的负面影响,从上而下逐渐升高。
- 页管理层次结构:由冷热缓存、伙伴系统组成的两级结构。负责内存页的缓存、分配、回收。
- 对象管理层次结构:由per-cpu高速缓存、slab缓存、伙伴系统组成的三级结构。负责对象的缓存、分配、回收。这里的对象指小于一页大小的内存块。
框图中包含伙伴系统、slab分配器和per-cpu(冷热)缓存三个模块。他们的对比分析如下。
3.1. bootmem
bootmem是启动时物理内存的分配器和配置器,在还没有构建完整的页分配机制(page allocator)之前,可以用来实现简单的内存分配和释放操作,之后完整的内存管理系统也是基于bootmem构建的。bootmem是基于首次适应的分配器,它的本质是位图(bitmap),一个比特位代表一个页框,如果比特位置1则表示该页被分配出去了;反之,如果置0表示该页空闲。Linux内核直接管理的内存是1G,所以这个位图需要2^32/4096/8=128KB。
3.2. memblock
那么,既然已经有了bootmem机制为什么还需要memblock机制呢?那是因为bootmem机制还存在很多问题。bootmem的主要缺点是位图的初始化。要创建这个位图,就必须要了解物理内存配置。位图的正确大小是多少?哪个存储库具有足够的连续物理内存来存储位图?当然,随着内存大小的增加,bootmem位图也随之增加。对于具有32GB的RAM系统来说,位图将需要1MB的内存。而memblock可以被立即使用因为基于静态数组足够大,至少可以容纳最初的内存注册和分配。如果一个添加或者保留更多内存的请求会使得memblock数组溢出,则该数组的大小将增加一倍。有一个基本的假设,即到这种情况发生的时候,足够的内存将添加到内存块中,来维持新数组的分配。
所以目前为了减轻从bootmem到memblock过度的痛苦,引入了一个名为nobootmem的兼容层。nobootmem提供(大多数)与bootmem相同的接口,但是它不是使用位图标记繁忙页面的,而是依靠内存块保留。从v4.17开始,在24种架构中,只有5种仍使用bootmem作为唯一的早期内存分配器。14种将memblock与nobootmem一起使用。其余5种同时使用memblock和bootmem。
3.3. buddy system
伙伴系统主要思想是通过将物理内存划分成多个连续的块,然后以“块”作为基本单位进行分配。这些“块”的都是由一个或者多个连续的物理页组成,物理页(4K)的数量是2的n次幂(0(4K) <= n <= 10(4M))。在操作内存时,经常将这些内存块分成大小相等的两个块,分成的两个内存块被称为伙伴块,采用一位二进制数来表示它们的伙伴关系。当这个位为1,表示其中一块在使用;当这个位为0,表示两个页面块都空闲或者都在使用。系统根据该位为0或位为1来决定是否使用或者分配该页面块。系统每次分配和回收伙伴块时都要对它们的伙伴位跟1进行异或运算。所谓异或是指刚开始时,两个伙伴块都空闲,它们的伙伴位为0,如果其中一块被使用,异或后得1;如果另一块也被使用,异或后得0;如果前面一块回收了异或后得1;如果另一块也回收了异或后得0。
在 NUMA 架构下存在多个 Memory 和 CPU 节点,不同 CPU 访问不同 Memory 节点的速度是不一样的,使用 Node 的形式把各个 Memory 节点的内存管理起来。某些外设只能访问低 16M 的物理地址,某些外设只能访问低 4G 的物理地址,32bit 的内核空间只能直接映射低 896M 物理地址。根据这些地址空间的限制,把同一个 node 内的内存再划分成多个 zone。
伙伴系统分配的最小单位是页(4k). 伙伴系统是一个内存池。它把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续的页框.
把空闲的页以2的n次方为单位进行拆分or合并.分配从链表上获取和归还。比如:k 链表(32)没有空闲块,需要在k+1链表(64)查找,如果仍然没有。在k+2链表(128)查找,找到以后分成1个64,2个32发给k和k+1链表。链表回收过程相反,临近的空闲块组合,连接到空闲链表上.
例如:假设ZONE_NORMAL 有16页内存,此时有人申请一页内存,剩下15页.Buddy算法会把剩下的15页拆分成8+4+2+1,放到不同的链表中去。
在buddy system系统建立基本的内存管理,内存管理在模型基础之上进行不同类型分配器的适配。常见的分配器:
- 连续物理内存管理buddy allocator
- 非连续物理内存管理vmalloc allocator
- 小块物理内存管理slab allocator
- 高端物理内存管理kmapper
3.4. slab管理算法
伙伴系统(buddy system)是以页为单位管理和分配内存,但实际代码开发过程中并不是每次都需要这么大的内存,引入slab是以byte为单位分配内存管理算法。Buddy解决外部内存碎片,slab 解决内部内存碎片。伙伴系统管理页单位大小的内存池,slab是从伙伴系统批发的内存建立byte小内存池。
slab分配器是基于对象类型进行内存管理的,每一种对象被划分为一类使用kmem_cache,例如索引节点对象是一类,进程描述符又是一类,等等 slab分成2种cache,普通cache和专用cache。 每种cache分成3种链表full,paritition,empty。其中kmem_cache_cpu中freelist首先被分配出来,内存使用完毕后kmem_cache_cpu中的partial开始分配,使用完毕以后最后从kmem_cache_node中获取内存。当这些使用完毕以后从budd子系统中获取内存添加到当前分配类中进行后续的内存分配。
4. 进程内存管理
linux内存总体布局:内存分成用户态和内核态。
4.1 内存整体布局
上面讲到的用户空间的四个部分在映射到物理地址时有一定的区别:
1、直接映射区:物理内存中896M以下的位置称为低端内存,以上的区域称为高端内存,在直接映射区中,若虚拟地址为:3G+128,则物理地址就是128。直接映射区虚拟地址=3G+物理地址。
2、vmalloc区:即可访问低端物理地址,也可访问高端地址,没有线性的映射特性。
3、永久映射区:固定用来访问高端物理内存区域。
4、固定映射区:与特殊的寄存器建立映射关系。
-
Linux操作系统和驱动程序运行在内核空间,应用程序运行在用户空间。两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中。用户空间的内存映射采用段页式,而内核空间有自己的规则;本文旨在探讨内核空间的地址映射。
-
os分配给每个进程一个独立的、连续的、虚拟内存空间。该大小一般是4G(32位操作系统,即2的32次方),其中将高地址值的内存空间分配给os占用,linux os占用1G,window os占用2G;其余内存地址空间分配给进程使用。
-
通常32位Linux内核虚拟地址空间划分0~3G为用户空间,3~4G为内核空间(注意,内核可以使用的线性地址只有1G)。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的。
- 进程寻址空间0~4G
- 进程在用户态只能访问0~3G,只有进入内核态才能访问3G~4G
- 每个进程虚拟空间的3G~4G部分是相同的
- 进程从用户态进入内核态不会引起CR3的改变但会引起堆栈的改变
ZONE_DMA:范围是0 ~ 16M,该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA
A 区:虚实转换; B 区:Buddy伙伴系统和slab系统; C 区:内核空间分类; D 区:单个进程的内存管理 task_struc
页表就是索引,伙伴系统就是内存池
other: 内核用struct page结构体表示每个物理页,struct page结构体占40个字节.假定系统物理页大小为4KB,对于4GB物理内存,1M个页面,故所有的页面page结构体共占有内存大小为40MB
用户空间对应进程,所以当进程切换,用户空间也会跟着变化
内核空间是由内核负责映射,不会跟着进程变化;内核空间地址有自己对应的页表,用户进程各自有不同额页
若系统为32位系统,则对应的虚拟内存的大小为4G
地址映射管理包括两部分:
1、虚拟内存空间分布
2、虚拟地址转化为物理地址
虚拟内存的空间分布:
(地址:0G-3G)用户空间,运行应用程序
(地址:3G-4G)内核空间
内核空间包括:
1、3G – 3G+896MB 直接映射区
2、vmalloc区
3、永久映射区
4、固定映射区
4.2 内存系统分配接口
内存分配就是向伙伴系统申请内存,然后更改页表形成地址转换.
缺页中断:malloc分配内存即产生缺页中断,缺页中断有两个情况,一种有足够内存,直接分配。另一种情况没有内存,需要先置换后分配.
(1) 进程分配malloc
malloc 分配的最小单位是页.小于128k用brk.malloc 大于128k用mmap.
1): brk原理:brk是堆顶指针,会产生内存空洞.比如:先分配256k,再分配128k,释放前面的256k,因为brk指向的栈顶128k没有回收,所以释放的256k页不会归还os.
brk释放:当brk释放空间少于128k则不会归还os,而是malloc采用链表管理这些空闲内存.
比如:malloc(1) 分配1个字节,os也会分配4k。剩下的4k-1字节全由malloc的空闲内存管理链表管理.
2): mmap
堆和栈之间,独立分配,直接释放. 采用匿名映射分配内存. 缺点在堆栈之间造成内存空洞。
写拷贝:malloc分配内存仅仅分配虚拟内存并没有分配物理内存,根据内存的写拷贝原则,只有访问内存的时候才正式分配物理内存。即malloc分配虚拟内存,memset的时候才真正分配物理内存.
(2) 内核分配内存
1 ) vmalloc
适用场景:在内核中不需要连续的物理地址,而仅仅需要内核空间里连续的虚拟地址的内存块. 比如:将文件读入内核内存.
适用区域: vmalloc 机制则在高端内存映射区分配物理内存.
通过更改内核页表实现内核内存和物理地址的地址转换. vmalloc是从伙伴系统分配内存.
2 ) kmalloc
kmalloc是实地址映射,不需要地址转换.
kmalloc基于slab分配器,slab缓冲区建立在一个连续的物理地址的大块内存之上,所以缓冲对象也是物理地址连续的。
3 ) malloc,vmalloc,kmallc 分配流程
物理内存的分配
Linux使用虚拟内存,因此程序中分配的都是虚拟地址,只有访问虚拟地址时,才会为其分配物理内存。当调用malloc函数时,只是分配了一个虚拟地址,当向其中写入数据时才会分配物理内存。
当使用 kmalloc 函数时,不仅为其分配虚拟地址,同时还会为其分配物理内存地址。
根据不同的内存使用方式和使用场景需要,内核把内存映射地址空间划分成多个部分,每个划分空间都有自己的起止地址、分配接口和使用场景。下图是一个常见的32位地址空间划分结构。
- DMA内存动态分配地址空间:一些DMA设备因为其自身寻址能力的限制,不能访问所有内存空间。如早期的ISA设备只能在24位地址空间执行DMA,即只能访问前16MB内存。所以需要划分出DMA内存动态分配空间,即DMA zone。其分配通过加上GFP_ATOMIC控制符的kmalloc接口来申请。
- 直接内存动态分配地址空间:因为访问效率等原因,内核对内存采用简单的线性映射,但是因为32位CPU的寻址能力(4G大小)和内核地址空间起始的设置(3G开始),会导致内核的地址空间资源不足,当内存大于1GB时,就无法直接映射所有内存。无法直接映射的地址空间部分,即highmem zone。在DMA zone和highmem zone中间的区域即normal zone,主要用于内核的动态内存分配。其分配通过kmalloc接口来申请。
- 高端内存动态分配地址空间:高端内存分配的内存是虚拟地址连续而物理地址不连续的内存,一般用于内核动态加载的模块和驱动,因为内核可能运行了很久,内存页面碎片情况严重,如果要申请大的连续地址的内存页会比较困难,容易导致分配失败。根据应用需要,高端内存分配提供多个接口:
- vmalloc:指定分配大小,page位置和虚拟地址隐式分配;
- vmap:指定page位置数组,虚拟地址隐式分配;
- ioremap:指定物理地址和大小,虚拟地址隐式分配。
- 持久映射地址空间:内核上下文切换会伴随着TLB刷新,这会导致性能下降。但一些使用高端内存的模块对性能也有很高要求。持久映射空间在内核上下文切换时,其TLB不刷新,所以它们映射的高端地址空间寻址效率较高。其分配通过kmap接口来申请。kmap与vmap的区别是:vmap可以映射一组page,即page不连续,但虚拟地址连续,而kmap只能映射一个page到虚拟地址空间。kmap主要用于fs、net等对高端内存访问有较高性能要求的模块中。
- 固定映射地址空间:持久映射的问题是可能会休眠,在中断上下文、自旋锁临界区等不能阻塞的场景中不可用。为了解决这个问题,内核又划分出固定映射,其接口不会休眠。固定映射空间通过kmap_atomic接口来映射。kmap_atomic的使用场景与kmap较为相似,主要用于mm、fs、net等对高端内存访问有较高性能要求而且不能休眠的模块中。
因为64位CPU一般都不需要高端内存(当然也可以支持),在地址空间划分上与32位CPU的差异较大,下图(大图)是一个MIPS64 CPU的内核地址空间划分示例。
常见问题汇总
- 用户进程没有高端内存概念。只有在内核空间才存在高端内存。用户进程最多只可以访问3G物理内存,而内核进程可以访问所有物理内存。
- 64位内核中有高端内存吗?
- 目前现实中,64位Linux内核不存在高端内存,因为64位内核可以支持超过512GB内存。若机器安装的物理内存超过内核地址空间范围,就会存在高端内存。
- 用户进程能访问多少物理内存?内核代码能访问多少物理内存?
- 32位系统用户进程最大可以访问3GB,内核代码可以访问所有物理内存。
- 64位系统用户进程最大可以访问超过512GB,内核代码可以访问所有物理内存。
-
什么事高端内存?高端内存和物理地址、逻辑地址、线性地址的关系?
高端内存只和逻辑地址有关系,和逻辑地址、物理地址没有直接关系。 - 多级页表管理解决了什么问题?又带来了什么问题?
QA1:
多级页表解决了当逻辑空间地址过大时,页表的长度会大大增加的问题。而采用多级页表时,一次访问盘需要多次访问内存甚至磁盘,会大大增加一次访存的时间。
无论是短时管理、页式管理还是段页式管理,读者都只需要掌握下面三个关键问题即可搞清楚几种存储结构的管理方式:1 逻辑地质结构,2 表象结构,3 寻址过程。
QA2:为什么引入虚拟内存?
多道程序同时执行内存不够使用,需要一种方式来在逻辑上扩充内存
QA3:虚拟内存空间大小由什么决定?
虚拟内存容量满足两个条件:
- 虚拟内存实际容量小于等于内存容量和外存容量之和
- 虚拟内存的最大容量小于等于计算机位数能容纳的最大容量
QA3:虚拟内存是怎么解决问题的?会带来什么问题?
虚拟内存通过一定的换入换出算法使得整个系统在逻辑上能够使用一个远远超过其物理内存大小的内存容量。因为虚拟内存进行页面置换时需要使用外部内存,会导致平均访问时间增加,若使用不合理的替换算法,则会大大降低系统性能。
1、用户空间(进程)是否有高端内存概念?
用户进程没有高端内存概念。只有在内核空间才存在高端内存。用户进程最多只可以访问3G物理内存,而内核进程可以访问所有物理内存。
2、64位内核中有高端内存吗?
目前现实中,64位Linux内核不存在高端内存,因为64位内核可以支持超过512GB内存。若机器安装的物理内存超过内核地址空间范围,就会存在高端内存。
3、用户进程能访问多少物理内存?内核代码能访问多少物理内存?
32位系统用户进程最大可以访问3GB,内核代码可以访问所有物理内存。
64位系统用户进程最大可以访问超过512GB,内核代码可以访问所有物理内存。
4、高端内存和物理地址、逻辑地址、线性地址的关系?
高端内存只和逻辑地址有关系,和逻辑地址、物理地址没有直接关系。
5、为什么不把所有的地址空间都分配给内核?
若把所有地址空间都给内存,那么用户进程怎么使用内存?怎么保证内核使用内存和用户进程不起冲突?
参考:
- Linux内存管理、伙伴系统(buddy system)等知识点_伙伴系统速度快且内存利用率高-优快云博客
- https://zhuanlan.zhihu.com/p/488123510?utm_id=0
- https://zhuanlan.zhihu.com/p/602907488
- https://blog.youkuaiyun.com/csbmww/article/details/124274452
- https://blog.51cto.com/u_15797962/5927910
- ARM32内存空间分配_arm32高端地址-优快云博客
- https://zhuanlan.zhihu.com/p/491584136
- https://www.cnblogs.com/gqtcgq/p/7247028.html
- https://www.cnblogs.com/zlcxbb/p/5841417.html
4. 等待删除
地址映射管理 模型图: