内存布局
本篇笔记讨论x86结构体系的内存布局
概述
Linux以分段加分页的方式管理和访问内存,通过分段将逻辑地址翻译成虚拟地址,再由分页将虚拟地址翻译成物理地址,从而访问内存。
一般概念
-
逻辑地址
包含在机器语言指令中,用来指定一个操作数或指令的地址。编译器把程序分成多段,每个逻辑地址由一个段和偏移量组成,偏移量指明从段开始的地方到实际地址之间的距离。 -
虚拟地址
虚拟地址也称线性地址,是一个32位无符号整数,可以表示4GB的地址。它从0开始寻址,每下一个数字表示下一个字节的地址。 -
物理地址
物理地址是使用物理地址总线中的位表示的地址。物理地址可能与逻辑地址不同,内存管理单元可以将逻辑地址转换成物理地址。 -
页
虽然可以以字节进行内存寻址,但由于内存管理单元通常以固定长度的虚拟地址空间为组进行到物理地址空间的连续映射,所以内核也将虚拟地址空间换分为固定长度的基本单元进行管理,该基本单元就是页。大多数32位系统上每一页的大小是4kb。 -
页框
内存管理单元将内存分为刚好容纳页的物理单元,称为页框。同一个页可以放置在不同的页框或磁盘中,所以才可以完成页的交换和动态映射。 -
区
由于硬件的限制,位于特定物理地址上的页不能做特定的任务,内核再次将物理内存上的页划分为不同的区
,以对相似特性的页进行逻辑分组。
内核必须解决这种缺陷引起的寻址问题:
- 某些硬件只能用特定区域的物理内存来执行
DMA
(直接内存访问)。 - 物理寻址比虚拟寻址范围大的情况下,一些内存不能永久的映射到内核空间上。
物理地址空间分布
- 以区划分,Linux一般把物理空间分为3个逻辑区:
ZONE_DMA
,这个区的页可以直接进行内存访问,其中的虚拟地址也物理地址一一映射。ZONE_NORMAL
,这个区的页都是能正常映射的,其中的虚拟地址也物理地址一一映射。ZONE_HIGHEM
,这个区的页不能永久的映射到内核空间,称为高端内存。
- 以4G物理内存为例,分布图如下:
----+===================+---- 4GB
^ | ......... |
| |-------------------|
| | ......... |
ZONE_HIGHEM |-------------------|
| | ......... |
| |-------------------|
v | ......... |
----|===================|---- 896MB
^ | ......... |
| |-------------------|
ZONE_NORMAL | ......... |
| |-------------------|
v | ......... |
----|===================|---- 16MB
^ | ......... |
ZONE_DMA | |-------------------|
v | ......... |
----+===================+---- 0
虚拟地址空间分布
-
虚拟地址空间分为
用户空间
和内核空间
。由于开启了分页机制,内核想要访问物理地址空间的话,必须先建立映射关系,然后通过虚拟地址来访问。为了能够访问所有的物理地址空间,就要将全部物理地址空间映射到1G的内核线性空间中,这显然不可能。于是,内核将0~896M的物理地址空间一对一映射到自己的虚拟地址空间中,这样它便可以随时访问ZONE_DMA
和ZONE_NORMAL
里的物理页面;此时内核剩下的128M虚拟地址空间不足以完全映射所有的ZONE_HIGHMEM
,Linux采取了动态映射的方法,即按需的将ZONE_HIGHMEM
里的物理页面映射到内核空间的最后128M虚拟地址空间里,使用完之后释放映射关系,以供其它物理页面映射。 -
以4G物理内存为例,分布图如下:
虚拟地址空间 物理地址空间
4GB ----+-------------+ +-------------+---- 4GB
^ | ......... |\ /| ......... |
| |-------------| \/ |-------------|
| | ......... | /\ | ......... |
| |-------------|/ \|-------------|
| | ......... | | ......... |
| |-------------| |-------------| Dynamic mapped
KERNEL_SPACE | | ......... | | ......... |
| | ......... | |-------------|
3GB+896MB---|-------------+\ | ......... |
| | ......... ++\ |-------------|
| | ......... +++\ | ......... |
| |-------------++++\|-------------|---- 896MB
v | ......... ++++++ ......... |
3GB ----|=============+\++++-------------|
^ | ......... | \+++ ......... | Liner mapped
USER_SPACE | |-------------| \++-------------|
v | ......... | \+ ......... |
0 ----+-------------+ +-------------+---- 0
内核空间布局
-
在
kernel image
下面有16M的内核空间用于DMA操作。位于内核空间高端的128M地址主要由3部分组成,分别为vmalloc area
,持久化内核映射区
,临时内核映射区
。由于ZONE_NORMAL
物理内存空间和内核虚拟空间存在直接映射关系,所以内核会将频繁使用的数据如kernel代码、GDT
、IDT
、PGD
、mem_map
数组等放在ZONE_NORMAL
里。而将用户数据、页表(PT)等不常用数据放在ZONE_ HIGHMEM
里,只在要访问这些数据时才建立映射关系(kmap()
)。比如,当内核要访问I/O设备存储空间时,就使用ioremap()
将位于物理地址高端的mmio区内存映射到内核空间的vmalloc area
中,在使用完之后便断开映射关系。 -
以4G物理内存为例,分布图如下:
----+-------------------+---- 4GB
^ | atomic mapping |
| |===================|----> FIXADDR_START
| | ... |
| |===================|
| | fixed mapping |
| |===================|----> PKMAP_BASE
| | ... |
| |===================|----> VMALLOC_END
| | vmalloc area |
| |-------------------|
| | ioremap area |
| |===================|----> VMALLOC_START
| | ... |
kernel_space|-------------------|---- 3GB + 896MB
| | ... |
| |-------------------|
| | ... |
| |-------------------|
| | mem_map |
| |-------------------|
| | kernel image |
| |-------------------|---- 3GB + 16MB
v | DMA |
----|-------------------|---- 3GB
^ | ... |
user_space| |-------------------|
v | ... |
----+-------------------+---- 0
用户空间布局
- 当RAM足够多时,内核会将用户数据保存在
ZONE_ HIGHMEM
,从而为内核腾出内存空间。用户进程的代码区一般从虚拟地址空间的0x08048000
开始,这是为了便于检查空指针。用户空间各个段的说明:
text
段(代码段),存放运行的指令数据。data
段(数据段),存放初始化非0的全局变量bss
段,存放未初始化的和初始化为0的全局变量- 堆段,由用户申请和释放。申请时至少分配虚存,当真正写数据时才分配对应的实存,释放时也并不是马上释放实存,而是可能被反复利用。
- 栈段,由系统负责申请释放,其操作方式类似数据结构的栈,用于存储參数变量及局部变量,函数的运行也是栈的方式,所以才有了递归。
- 参数和全局环境变量区,存储程序运行时从shell中传入的参数和环境变量。
- 以4G物理内存为例,分布图如下:
----+-------------------+---- 4GB
^ | ......... |
kernel_space| |-------------------|
v | ......... |
----|-------------------|---- 3GB
^ | env/param |
| |-------------------|
| | stack |---- +
| |-------------------| |
| | ......... | v 栈向低地址扩展
| |-------------------|
| | ......... | ^
| |-------------------| | 内存映射地址向高地址扩展
| | mmap..... |---- +
user_space | |-------------------|
| | ......... | ^
| |-------------------| | 堆地址向高地址扩展
| | heap..... |---- +
| |-------------------|
| | bss...... |
| |-------------------|
| | data..... |
| |-------------------|
| | text.... |
| |-------------------|---- 0x08048000
| | ......... |
| |-------------------|
v | ......... |
----+-------------------+---- 0
压缩内核与用户的虚拟地址空间
压缩内核虚拟地址空间
- 目标
缩内核空间的直接映射段和非连续页映射段.直接映射段从64T变为16T,非连续页映射段从32T变为8T.
- 修改源码
参考上述宏的位置修改源码。将 __PAGE_OFFSET_BASE
修改为 0xffffd00000000000
。再将 MAX_PHYSMEM_BITS
修改为44。将 __VMALLOC_BASE
修改为 0xffffe10000000000
。再将 VMALLOC_SIZE_TB
修改为 _AC(8, UL)
。
压缩用户虚拟地址空间
- 目标
压缩整个用户空间,将原来128T的空间压缩到2T.
- 修改源码
参考上述宏的位置修改源码。将 TASK_SIZE_MAX
修改为 ((1UL << 47) – (126UL << 40))
编译内核
- 修改配置
在 .config
文件中添加 CONFIG_X86_PTDUMP_CORE=y
、 CONFIG_X86_PTDUMP=y
和 CONFIG_EFI_PGT_DUMP=y
配置项。开启这些选项后可以使用 cat /sys/kernel/debug/kernel_page_table
命令查看内核空间的布局。
- 编译内核
查看布局 cat /sys/kernel/debug/kernel_page_table
输出如下:
---[ User Space ]---
0x0000000000000000-0xffff800000000000 16777088T pgd
---[ Kernel Space ]---
0xffff800000000000-0xffffd00000000000 80T pgd
---[ Low Kernel Mapping ]---
0xffffd00000000000-0xffffd00000010000 64K RW GLB NX pte
0xffffd00000010000-0xffffd00000080000 448K ro NX pte
...
0xffffd00240000000-0xffffd08000000000 503G pud
0xffffd08000000000-0xffffe10000000000 16896G pgd
---[ vmalloc() Area ]---
0xffffe10000000000-0xffffe10000001000 4K RW PCD GLB NX pte
...
0xffffe8ffffcc6000-0xffffe8ffffe00000 1256K pte
0xffffe8ffffe00000-0xffffe90000000000 2M pmd
0xffffe90000000000-0xffffea0000000000 1T pgd
---[ Vmemmap ]---
0xffffea0000000000-0xffffea0003000000 48M RW PSE GLB NX pmd
0xffffea0003000000-0xffffea0004000000 16M pmd
...
0xffffea0040000000-0xffffea8000000000 511G pud
0xffffea8000000000-0xffffff0000000000 20992G pgd
---[ ESPfix Area ]---
0xffffff0000000000-0xffffff4a00000000 296G pud
...
0xffffff4a00069000-0xffffff4a0006a000 4K ro GLB NX pte
0xffffff4a0006a000-0xffffff4a00079000 60K pte
... 131059 entries skipped ...
---[ EFI Runtime Services ]---
0xffffffef00000000-0xffffffff80000000 66G pud
---[ High Kernel Mapping ]---
0xffffffff80000000-0xffffffff81000000 16M pmd
...
0xffffffff82000000-0xffffffff82400000 4M RW PSE GLB NX pmd
0xffffffff82400000-0xffffffffa0000000 476M pmd
---[ Modules ]---
0xffffffffa0000000-0xffffffffa0010000 64K ro GLB x pte
0xffffffffa0010000-0xffffffffa0015000 20K ro GLB NX pte
...
0xffffffffa04f7000-0xffffffffa04f9000 8K RW GLB NX pte
0xffffffffa04f9000-0xffffffffa0a00000 5148K pte
0xffffffffa0a00000-0xffffffffff000000 1510M pmd
---[ End Modules ]---
0xffffffffff000000-0xffffffffff200000 2M pmd
0xffffffffff200000-0xffffffffff57b000 3564K pte
...
0xffffffffff600000-0xffffffffff601000 4K USR ro GLB NX pte
0xffffffffff601000-0xffffffffff800000 2044K pte
0xffffffffff800000-0x0000000000000000 8M pmd