linux内核虚拟地址空间( 二 )


0x800FFFFF


当内核访问完 0x80000000 ~ 0x800FFFFF 物理内存后,就将 0xF8700000 ~ 0xF87FFFFF内核线性空间释放 。这样其他进程或代码也可以使用 0xF8700000 ~ 0xF87FFFFF 这段地址访问其他物理内存 。


从上面的描述,我们可以知道高端内存的最基本思想:借一段地址空间,建立临时地址映射,用完后释放,达到这段地址空间可以循环使用,访问所有物理内存 。


看到这里,不禁有人会问:万一有内核进程或模块一直占用某段逻辑地址空间不释放,怎么办?若真的出现的这种情况,则内核的高端内存地址空间越来越紧张,若都被占用不释放,则没有建立映射到物理内存都无法访问了 。


高端内存分布
在内核的虚拟地址空间的高端内存区中又分为三个区,分别是:非连续内存区、永久内核映射区、固定映射区 。

  • 非连续内存区是为系统硬件中断处理和内核模块生产空间一次性准备用的 。
  • 永久映射区是给系统底层空间分区和硬件及驱动准备的 。
  • 固定映射区是为用户配置和应用软件运行提供可用空间准备的 。

linux内核虚拟地址空间

文章插图
在图中,high_memory是高端内存区( ZONE_HIGHMEM )起始地址,VMALLOC 是非连续内存区 。


在直接映射的物理页帧末尾与第一个内存区 VMALLOC_START 之间插入了一个 8MB(VMALLOC_OFFSET)的区间,这是一个安全区,目的是为了“捕获”对非连续区的非法访问 。出于同样的理由,在其他非连续的内存区之间也插入了 4KB 大小的安全区 。每个非连续内存区的大小都是 4096 的倍数 。


在内核中,永久内核映射区和固定映射区大小一般都为 4MB,也就是分别用一个页表可以囊括其所包含地址范围,其他都给非连续内存区使用 。不过如果物理内存大小小于 896MB 的情况下,内核并不会生成高端内存区,只会有 ZONE_DMA 和 ZONE_NORMAL 两个区 。


我们知道,内核可使用的线性地址就只有1G大小( 0xC0000000 ~ 0xFFFFFFFF ),而用于 ZONE_DMA 和 ZONE_NORMAL 这两个区的映射已经花掉了 896MB 的线性地址空间,最后只剩下 128MB 用于映射高端内存,如果内存大于 1G,比如 2G(2048M)的情况下,高端内存区大小就为 1152MB,这个 128MB 大小的线性地址空间是完全不够直接映射高端内存的,所以对于高端内存的处理,linux 并不会直接映射,而是在需要的时候才进行映射,不需要的时候就释放映射,回收线性地址 。


在初始化页表时,会对永久内核映射区和固定映射区分别进行初始化,但是都不会对他们进行映射处理,只有在需要使用时才会分配 。


以上是虚拟内存中高端内存(3G+896M~4G)的分布情况,那么 ZONE_DMA 和 ZONE_NORMAL (3G~3G+896M)区域内存布局是什么样的呢?


内核启动后内核区域内存布局
一般的,内核启动会被加载到内存的 1MB 开始处,而普通配置的内核大小一般小于3MB,也就是说,内核镜像被加载内存 1MB~4MB 的地方,而为什么0MB~1MB 的内存内核不使用,因为这段内存一般是由 BIOS 使用和做一些硬件映射的 。如下图:
linux内核虚拟地址空间

文章插图
在里面我们值得注意的就是 _end,它在代码里表明了内核镜像在内存中的结束地址,页表的初始化会先初始化未被内核使用的区域,最后再初始化内核使用的区域 。


linux内核虚拟地址空间

文章插图


符号 _text 对应物理地址 0x00100000,表示内核代码的第一个字节的地址 。内核代码的结束位置用另一个类似的符号 _etext 表示 。内核数据被分为两组:初始化过的数据和未初始化过的数据 。初始化过的数据在 _etext 后开始,在 _edata 处结束,紧接着是未初始化过的数据,其结束符号为 _end,这也是整个内核映像的结束符号 。

推荐阅读