校正 Linux 内存空间的划分.

Signed-off-by: chen.yang <chen.yang@yuzhen-iot.com>
This commit is contained in:
chen.yang 2022-04-24 17:33:07 +08:00
parent 1901193c5e
commit 283454e024
2 changed files with 45 additions and 16 deletions

View File

@ -46,15 +46,31 @@ Intel x86/64 体系结构下,对应的 CPU 寄存器、段选择子、GDT、LD
每个进程的用户空间都是完全独立、互不相干的,用户进程各自有不同的页表。而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表,内核的虚拟空间独立于其他程序。
Linux 中 1GB 的内核地址空间又被划分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和系统保留映射区这几个区域。
Linux 中 1GB 的内核地址空间又被划分为:
* 直接映射区/物理内存映射区
* 动态内存映射区
* 永久内存映射区
* 固定内存映射区
* 系统保留映射区
![Linux 内核内存映射](./imgs/8.11_内核内存管理/005.drawio.png)
一般情况下,物理内存映射区最大长度为 896MB系统的物理内存被顺序映射在内核空间的这个区域中。当系统物理内存大于 896MB 时,超过物理内存映射区的那部分内存称为高端内存(而未超过物理内存映射区的内存通常被称为常规内存),内核在存取高端内存时必须将它们映射到高端页面映射区。
### 直接映射区与高端内存
Linux 保留内核空间最顶部 FIXADDR_TOP4GB 的区域作为保留区。
一般情况下,直接映射区最大长度为 896MB系统的物理内存被顺序映射在内核空间的这个区域中。当系统物理内存大于 896MB 时,超过直接映射区的那部分内存称为高端内存(而未超过直接映射区的内存通常被称为常规内存)
紧接着最顶端的保留区以下的一段区域为专用页面映射区FIXADDR_STARTFIXADDR_ TOP它的总尺寸和每一页的用途由 fixed_address 枚举结构在编译时预定义,用 __fix_to_virt(index) 可获取专用区内预定义页面的逻辑地址。其开始地址和结束地址宏定义如下:
内核在存取高端内存时必须将它们映射到高端页面映射区。
引入高端内存映射这样一个概念的主要原因就是我们所安装的内存大于 1G 时,内核的 1G 线性地址空间无法建立一个完全的直接映射来触及整个物理内存空间,而对于 80x86 开启 PAE 的情况下,允许的最大物理内存可达到 64G因此内核将自己的最后 128M 的线性地址空间腾出来,用以完成对高端内存的暂时性映射。而在 64 位的系统上就不存在这样的问题了,因为可用的线性地址空间远大于可安装的内存。
### 保留区
Linux 保留内核空间最顶部 FIXADDR_TOP4GB 的区域作为保留区,一般为 4KB。
### 固定内存映射区
紧接着最顶端的保留区以下的一段区域为固定内存映射区FIXADDR_STARTFIXADDR_TOP它的总尺寸和每一页的用途由 fixed_address 枚举结构在编译时预定义,用 __fix_to_virt(index) 可获取专用区内预定义页面的逻辑地址。其开始地址和结束地址宏定义如下:
```cpp
#define FIXADDR_START (FIXADDR_TOP - __FIXADDR_SIZE)
@ -62,7 +78,14 @@ Linux 保留内核空间最顶部 FIXADDR_TOP4GB 的区域作为保留区。
#define __FIXADDR_TOP 0xfffff000
```
接下来,如果系统配置了高端内存,则位于专用页面映射区之下的就是一段高端内存映射区,其起始地址为 PKMAP_BASE定义如下
固定内存映射区有一部分用于高端内存的临时映射,具有如下特点:
1. 每个 CPU 占用一块空间.
2. 在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是 1 个 page每个小空间用于一个目的这些目的定义在 kmap_types.h 中的 km_type 中。
### 永久内存映射区
接下来,如果系统配置了永久内存映射区,则位于固定内存映射区之下的就是一段永久内存映射区,其范围为 PKMAP_BASEFIXADDR_START定义如下
```cpp
#define PKMAP_BASE ((FIXADDR_BOOT_START - PAGE_SIZE*(LAST_PKMAP + 1)) & PMD_MASK )
@ -75,7 +98,11 @@ Linux 保留内核空间最顶部 FIXADDR_TOP4GB 的区域作为保留区。
#define PMD_SHIFT 21
```
在物理区和高端映射区之间为虚存内存分配区VMALLOC_STARTVMALLOC_END用于 vmalloc()函数它的前部与物理内存映射区有一个隔离带后部与高端映射区也有一个隔离带vmalloc 区域定义如下:
通常情况下,这个空间是 4M 大小,因此仅仅需要一个页表即可,内核通过 pkmap_page_table 来寻找这个页表。通过 kmap(),可以把一个 page 映射到这个空间来。由于这个空间是 4M 大小,最多能同时映射 1024 个 page。因此对于不使用的的 page及应该时从这个空间释放掉也就是解除映射关系通过 kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。
### 动态内存映射区
在物理区和高端映射区之间为动态内存映射区VMALLOC_STARTVMALLOC_END用于 vmalloc() 函数它的前部与直接映射区有一个隔离带后部与高端映射区也有一个隔离带vmalloc() 区域定义如下:
```cpp
#define VMALLOC_OFFSET (8*1024*1024)
@ -88,6 +115,8 @@ Linux 保留内核空间最顶部 FIXADDR_TOP4GB 的区域作为保留区。
#endif // CONFIG_HIGHMEM
```
动态内存映射区由内核函数 vmalloc() 来分配特点是线性空间连续但是对应的物理空间不一定连续。vmalloc() 分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存。
## Linux 内核内存的分配
### page
@ -355,7 +384,7 @@ void *kmalloc(size_t size, gfp_t flags);
**说明:**
kmalloc() 申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,最大只能开辟 128k-16 字节空间16 个字节是被页描述符结构占用了。
kmalloc() 申请的内存位于直接映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,最大只能开辟 128k-16 字节空间16 个字节是被页描述符结构占用了。
**参数:**
@ -419,7 +448,7 @@ addr要释放的内存首地址指针。
### vmalloc/vfree
通过 vmalloc 分配内核空间,通过 vfree 释放。可以分配较大内存。使用 vmalloc 最著名的实例是内核对模块的实现,当模块被动态加载到内核当中时,就把模块装载到由 vmalloc() 分配的内存上。注意vmalloc() 和 vfree() 可以睡眠,因此不能从中断上下文调用。
通过 vmalloc() 分配内核空间,通过 vfree() 释放。可以分配较大内存。使用 vmalloc() 最著名的实例是内核对模块的实现,当模块被动态加载到内核当中时,就把模块装载到由 vmalloc() 分配的内存上。注意vmalloc() 和 vfree() 可以睡眠,因此不能从中断上下文调用。
#### vmalloc
@ -451,7 +480,7 @@ void vfree(const void *addr);
**说明:**
释放由 vmalloc 分配的内核空间。
释放由 vmalloc() 分配的内核空间。
**参数:**
@ -471,13 +500,13 @@ kmalloc()、kzalloc()、vmalloc() 的共同特点是:
kmalloc()、kzalloc()、vmalloc() 的区别是:
1. kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc
2. kmalloc 分配的内存大小有限制128KB而 vmalloc 没有限制;
3. kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;
4. kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC而 vmalloc 分配内存时则可能产生阻塞;
5. kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快。
6. kmalloc 申请一段物理地址和逻辑地址连续的内存空间。
7. vmalloc 申请一段逻辑地址连续、物理地址不连续的内存空间。
1. kzalloc() 是强制清零的 kmalloc() 操作;(以下描述不区分 kmalloc() 和 kzalloc()
2. kmalloc() 分配的内存大小有限制128KB而 vmalloc() 没有限制;
3. kmalloc() 可以保证分配的内存物理地址是连续的,但是 vmalloc() 不能保证;
4. kmalloc() 分配内存的过程可以是原子过程(使用 GFP_ATOMIC而 vmalloc() 分配内存时则可能产生阻塞;
5. kmalloc() 分配内存的开销小,因此 kmalloc() 比 vmalloc() 要快。
6. kmalloc() 申请一段物理地址和逻辑地址连续的内存空间。
7. vmalloc() 申请一段逻辑地址连续、物理地址不连续的内存空间。
一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 68 KiB