内存管理小解

本文深入探讨了Linux内核在内存中的位置及其管理,包括内核初始化、内存分区以及地址空间的划分。在32位系统中,内核地址空间通常为1GB,而64位系统则支持256TB的地址空间。内核使用不同的内存分配函数,如kmalloc、kzalloc和vmalloc,以适应不同场景的需求。内核页表和进程页表分别用于内核和进程的地址映射,即使在64位系统中,内核页表仍然不可或缺,用于配合MMU进行地址转换。
摘要由CSDN通过智能技术生成

内核在内存中的位置

在默认情况下,Linux内核被装载到一个内存中固定的位置,该位置在编译时确定,配置选项PHYSICAL_START用于确定内核在内存中的位置。

Linux内核的初始化

start_kernel—>setup_arch—>…

内存相关的初始化工作:

  1. 创建列表,包括系统占据的内存区以及空闲内存区。
  2. 通过setup_memory确定每个结点可用的物理内存页的数目,初始化bootm内存分配器(采用位图来管理内存页),分配各种内存区等。
  3. 通过page_init初始化内核页表(用户空间无法访问)并启动内存分页机制。
  4. 通过zone_sizes_init初始化系统中所有结点的pgdat_t实例(保存结点内存数量以及内存在各个内存域的分配信息)。

内核地址空间的划分:

32位系统(IA-32):
IA-32架构的内核地址空间划分示意图

​ IA-32架构的内核地址空间划分示意图

32位系统的地址空间一般按照3:1的比例划分,内核地址空间一般为1G,进程地址空间一般为3G,所有进程共享一个内核地址空间。当然3:1的比例也不是绝对的,我们可以根据自己的需要对该比例进行调整。

IA-32最大支持4GB的物理内存,但是内核只能将其896MB直接映射(地址的线性平移)到内核地址空间,剩余的128MB(高端内存区)的用法如下:

  1. 虚拟地址连续但物理地址不连续的内存区,可以在vmalloc区域分配(主要用于设备和声音驱动)。
  2. 持久映射。将高端内存域中的非持久页映射到内核中。
  3. 固定映射。将与物理地址空间中的固定页与虚拟地址空间关联。

64位系统(AMD64):

AMD64的系统为避免地址空间跨度太大采用48位的地址字寻址256TB的地址空间。但是虽然物理地址字位被限制为48位,但是在寻址虚拟地址空间时仍采用了64位的指针,这会导致部分的物理地址空间无法寻址。设计师通过符号扩展的方法避免了这种问题:

img

如图所示,高16位全0是用户空间地址,高16位全1是内核空间地址。在64位系统中,物理页可以一致映射到从PAGE_OFFSET开始的64TB的内核地址空间中

内核空间申请内存的函数

我们都知道在用户空间申请内存使用的是malloc(),那么在内核空间申请内存使用的是哪些函数呢?

  • vmalloc()用于为内核分配虚拟地址连续但物理地址不连续的空间。

  • kmalloc()申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB

  • kzalloc() 函数与 kmalloc() 非常相似,参数及返回值是一样的,可以说是前者是后者的一个变种,因为 kzalloc() 实际上只是额外附加了 __GFP_ZERO 标志。所以它除了申请内核内存外,还会对申请到的内存内容清零

kmalloc()、kzalloc()、vmalloc() 的共同特点是:

  1. 用于申请内核空间的内存;
  2. 内存以字节为单位进行分配;
  3. 所分配的内存虚拟地址上连续;

kmalloc()、kzalloc()、vmalloc() 的区别是:

  1. kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc)
  2. kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;
  3. kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;
  4. kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞;
  5. kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快;

一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc()而只有在需要获得大块内存时才使用 vmalloc()。例如,当模块被动态加载到内核当中时,就把模块装载到由 vmalloc() 分配的内存上。

内核页表和进程页表

内核和进程使用不同的地址空间,因此也需要不同的页表来管理自己的地址空间。那么内核页表和进程页表都是什么呢?

内核页表:即书上说的主内核页表,在内核中其实就是一段内存,存放在主内核页全局目录init_mm.pgd(swapper_pg_dir)中,硬件并不直接使用。
进程页表:每个进程自己的页表,放在进程自身的页目录task_struct.pgd中。
在保护模式下,从硬件角度看,其运行的基本对象为“进程”(或线程),而寻址则依赖于“进程页表”,在进程调度而进行上下文切换时,会进行页表的切换:即将新进程的pgd(页目录)加载到CR3寄存器中。从这个角度看,其实是完全没有用到“内核页表”的,那么“内核页表”有什么用呢?跟“进程页表”有什么关系呢?

1、内核页表中的内容为所有进程共享,每个进程都有自己的“进程页表”,“进程页表”中映射的线性地址包括两部分:
用户态
内核态
其中,内核态地址对应的相关页表项,对于所有进程来说都是相同的(因为内核空间对所有进程来说都是共享的),而这部分页表内容其实就来源于“内核页表”,即每个进程的“进程页表”中内核态地址相关的页表项都是“内核页表”的一个拷贝。
2、“内核页表”由内核自己维护并更新,在vmalloc区发生page fault时,将“内核页表”同步到“进程页表”中。以32位系统为例,内核页表主要包含两部分:
线性映射区
vmalloc区
其中,线性映射区即通过TASK_SIZE偏移进行映射的区域,对32系统来说就是0-896M这部分区域,映射对应的虚拟地址区域为TASK_SIZE-TASK_SIZE+896M。这部分区域在内核初始化时就已经完成映射,并创建好相应的页表,即这部分虚拟内存区域不会发生page fault。

vmalloc区,为896M-896M+128M,这部分区域用于映射高端内存,有三种映射方式:vmalloc、固定映射、持久映射,这里就不像述了。。
以vmalloc为例(最常使用),这部分区域对应的线性地址在内核使用vmalloc分配内存时,其实就已经分配了相应的物理内存,并做了相应的映射,建立了相应的页表项,但相关页表项仅写入了“内核页表”,并没有实时更新到“进程页表中”,内核在这里使用了**“延迟更新”**的策略,将“进程页表”真正更新推迟到第一次访问相关线性地址,发生page fault时,此时在page fault的处理流程中进行“进程页表”的更新。

问题:64位的系统的内核地址空间为256TB,远远大于实际的物理内存,因而没有高端内存的概念。同时,因为地址空间巨大,物理内存一致性映射到内核地址空间的大小是2^46=64TB,因而其实不需页表只用线性偏移即可完成VA和PA的转换,那为什么这里还是要内核页表呢?

答:因为现代计算机上都存在名为MMU的硬件机制,可以直接完成虚拟地址到物理地址的转换,内核也无法绕过该机制,所以该有的页表还是要有。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值