Linux内存管理 | 二、虚拟地址空间布局 #
上一章,我们了解了内存管理的由来以及核心思想,下面我们按照顺序,先来介绍一下Linux
虚拟内存空间的管理。
同样,我们知道Linux
内核抽象出来虚拟内存空间,主要是为了让每个进程都独享该空间,那虚拟内存空间是如何布局的呢?
前提:针对于不同位数的
CPU
,寻址能力不同,抽象出来的虚拟内存空间大小也不同,我们以常见的32
位的CPU
为例。
1、虚拟内存空间布局 #
对于32
位的CPU
,寻址范围为0~2^32
,也就是0x00000000-0xFFFFFFFF
,即最多抽象出来4G
的虚拟内存空间。
这4GB
的内存空间,在Linux
中,又分为用户空间和内核空间,其中0x0000000-0xBFFFFFFF
,共3G
为用户空间,0xC00000000-0xFFFFFFFF
,共1G
为内核空间,如下:
无论内核空间还是用户空间,其仍然是在虚拟内存空间基础之上进行划分的,其直接访问的依旧都是虚拟地址,而非物理地址!
我们编写代码后,所生成的可执行程序,运行之后就成为一个系统进程,我们在"虚"的角度来看,每个进程都是独享这4G
虚拟地址空间的,
2、用户态空间布局 #
如上所述,用户空间在虚拟内存中分布在0x0000000-0xBFFFFFFF
,大小为3G
。
每一个用户进程,按照访问属性一致的地址空间存放在一起的原则,划分成5个不同的内存区域(访问属性一致指的是:可读,可写,可执行):
- 代码段:
Text Segment
,也就是我们的二进制程序,代码段需要防止在运行时被非法修改,所以该段为只读。 - 数据段:
Data Segment
,主要存放初始化了的变量,主要包括:静态变量和全局变量,该段为读写。 BSS
段:BSS Segment
,主要存放未初始化的全局变量,在内存中bss
段全部置零,该段为读写。- 堆段:
Heap Segment
,主要存放进程运行过程中动态分配的内存段,大小不固定,可动态扩张和缩减,通常使用malloc
和free
来分配释放,并且堆的增长方向是向上的。 - 文件映射和匿名映射段:
Memory Mapping Segment
,主要存放进程使用到的文件或者依赖的动态库,从低地址向上增长。 - 栈段:
Stack Segment
,主要存放进程临时创建的局部变量,函数调用上下文信息等,栈向下增长。
一个可执行程序,可以通过size
命令,查看编译出来的可执行文件大小,其中包括了代码段,数据段等数据信息,如下:
donge@Donge:$ size Donge-Demo
text data bss dec hex filename
12538 1916 43632 58086 e2e6 Donge-Demo
text
:代码段大小data
:数据段大小bss
:bss
段大小dec
:十进制表示的可执行文件大小hex
:十六进制表示的可执行文件大小
运行该程序后,可以通过cat /proc/PID/maps
命令,或者pmap PID
命令,来查看该进程在虚拟内存空间中的分配情况,其中PID为进程的PID号,如下:
donge@Donge:$ cat /proc/16508/maps
55976ff9e000-55976ffa0000 r--p 00000000 08:10 184922 /home/donge/WorkSpace/Program/Donge_Programs/Donge_Demo/build/Donge-Demo
55976ffa0000-55976ffa2000 r-xp 00002000 08:10 184922 /home/donge/WorkSpace/Program/Donge_Programs/Donge_Demo/build/Donge-Demo
55976ffa2000-55976ffa3000 r--p 00004000 08:10 184922 /home/donge/WorkSpace/Program/Donge_Programs/Donge_Demo/build/Donge-Demo
55976ffa3000-55976ffa4000 r--p 00004000 08:10 184922 /home/donge/WorkSpace/Program/Donge_Programs/Donge_Demo/build/Donge-Demo
55976ffa4000-55976ffa5000 rw-p 00005000 08:10 184922 /home/donge/WorkSpace/Program/Donge_Programs/Donge_Demo/build/Donge-Demo
55976ffa5000-55976ffaf000 rw-p 00000000 00:00 0
559771d91000-559771db2000 rw-p 00000000 00:00 0 [heap]
7fec1ad84000-7fec1ad87000 rw-p 00000000 00:00 0
7fec1ad87000-7fec1adaf000 r--p 00000000 08:10 22282 /usr/lib/x86_64-linux-gnu/libc.so.6
7fec1adaf000-7fec1af44000 r-xp 00028000 08:10 22282 /usr/lib/x86_64-linux-gnu/libc.so.6
7fec1af44000-7fec1af9c000 r--p 001bd000 08:10 22282 /usr/lib/x86_64-linux-gnu/libc.so.6
7fec1af9c000-7fec1afa0000 r--p 00214000 08:10 22282 /usr/lib/x86_64-linux-gnu/libc.so.6
7fec1afa0000-7fec1afa2000 rw-p 00218000 08:10 22282 /usr/lib/x86_64-linux-gnu/libc.so.6
7fec1afa2000-7fec1afaf000 rw-p 00000000 00:00 0
7fec1afb5000-7fec1afb7000 rw-p 00000000 00:00 0
7fec1afb7000-7fec1afb9000 r--p 00000000 08:10 22068 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fec1afb9000-7fec1afe3000 r-xp 00002000 08:10 22068 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fec1afe3000-7fec1afee000 r--p 0002c000 08:10 22068 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fec1afef000-7fec1aff1000 r--p 00037000 08:10 22068 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fec1aff1000-7fec1aff3000 rw-p 00039000 08:10 22068 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffce385d000-7ffce387e000 rw-p 00000000 00:00 0 [stack]
7ffce394e000-7ffce3952000 r--p 00000000 00:00 0 [vvar]
7ffce3952000-7ffce3953000 r-xp 00000000 00:00 0 [vdso]
上面能大致看出该进程的代码段、堆、文件映射段,栈的内存分布等情况,以上就是我们的可执行程序被加载进入内存之后,在用户态虚拟内存空间的布局情况。
顺便介绍一下 我的圈子:高级工程师聚集地,期待大家的加入。
3、内核态空间布局 #
下面我们来看一下内核态的虚拟空间布局,首先我们要知道:
- 在
Linux
系统中,用户进程通常只能访问用户空间的虚拟地址,只有在执行内陷操作或系统调用时才能访问内核空间。 - 所有的进程通过系统调用进入内核态之后,看到的虚拟地址空间都是一样的,他们是共享内核态虚拟内存空间的。
32位的内核态虚拟空间在虚拟内存中分布在0xC00000000-0xFFFFFFFF
上,大小为1G
,其要分为以下几个区:
- 直接映射区(
Direct Memory Region
):顾名思义,直接映射区就是直接与物理内存建立一一映射关系。从内核空间起始地址开始,到896M
的内核空间地址区间,为直接内存映射区,该区域线性地址和分配的物理地址都是连续的。
896M
以上的内核地址空间,又称为高端内存区域。
安全保护区:也成为内存空洞,大小为
8M
,其主要目的是为了避免 非连续区的非法访问,动态映射区:也就是
vmalloc Region
,该区域由Vmalloc
函数分配,特点是:虚拟地址空间连续,但是物理地址空间不一定连续。永久映射区(
Persistent Kernel Mapping Region
):该区域主要用于访问高端内存,通过alloc_page (_GFP_HIGHMEM)
接口分配高端内存页,可以使用kmap
函数将分配到的高端内存映射到该区域。固定映射区(
Fixing kernel Mapping Region
):该区域虚拟内存地址可以自由映射到物理内存的高端地址上,“固定”表现在“虚拟内存空间地址是固定的”,被映射的物理地址是可变的。
为什么会有固定映射这个概念呢 ?
比如:在内核的启动过程中,有些模块需要使用虚拟内存并映射到指定的物理地址上,而且这些模块也没有办法等待完整的内存管理模块初始化之后再进行地址映射。因此,内核固定分配了一些虚拟地址,这些地址有固定的用途,使用该地址的模块在初始化的时候,将这些固定分配的虚拟地址映射到指定的物理地址上去。
4、总结 #
以上就是整个虚拟地址空间的划分,总结如下: