Linux内存管理 | 二、虚拟地址空间布局

img
我的圈子: 高级工程师聚集地
我是董哥,高级嵌入式软件开发工程师,从事嵌入式Linux驱动开发和系统开发,曾就职于世界500强企业!
创作理念:专注分享高质量嵌入式文章,让大家读有所得!
img
img

上一章,我们了解了内存管理的由来以及核心思想,下面我们按照顺序,先来介绍一下Linux虚拟内存空间的管理。

同样,我们知道Linux内核抽象出来虚拟内存空间,主要是为了让每个进程都独享该空间,那虚拟内存空间是如何布局的呢?

前提:针对于不同位数的CPU,寻址能力不同,抽象出来的虚拟内存空间大小也不同,我们以常见的32位的CPU为例。

1、虚拟内存空间布局

对于32位的CPU,寻址范围为0~2^32,也就是0x00000000-0xFFFFFFFF,即最多抽象出来4G的虚拟内存空间。

4GB的内存空间,在Linux中,又分为用户空间和内核空间,其中0x0000000-0xBFFFFFFF,共3G为用户空间,0xC00000000-0xFFFFFFFF,共1G为内核空间,如下:

image-20230924173124939

无论内核空间还是用户空间,其仍然是在虚拟内存空间基础之上进行划分的,其直接访问的依旧都是虚拟地址,而非物理地址!

我们编写代码后,所生成的可执行程序,运行之后就成为一个系统进程,我们在"虚"的角度来看,每个进程都是独享这4G虚拟地址空间的,

扫描二维码关注公众号,回复: 17135162 查看本文章

2、用户态空间布局

如上所述,用户空间在虚拟内存中分布在0x0000000-0xBFFFFFFF,大小为3G

每一个用户进程,按照访问属性一致的地址空间存放在一起的原则,划分成5个不同的内存区域(访问属性一致指的是:可读,可写,可执行):

  • 代码段Text Segment,也就是我们的二进制程序,代码段需要防止在运行时被非法修改,所以该段为只读
  • 数据段Data Segment,主要存放初始化了的变量,主要包括:静态变量和全局变量,该段为读写
  • BSSBSS Segment,主要存放未初始化的全局变量,在内存中 bss 段全部置零,该段为读写
  • 堆段Heap Segment,主要存放进程运行过程中动态分配的内存段,大小不固定,可动态扩张和缩减,通常使用mallocfree来分配释放,并且堆的增长方向是向上的。
  • 文件映射和匿名映射段Memory Mapping Segment,主要存放进程使用到的文件或者依赖的动态库,从低地址向上增长。
  • 栈段Stack Segment,主要存放进程临时创建的局部变量,函数调用上下文信息等,栈向下增长。

image-20231005160139650

一个可执行程序,可以通过size命令,查看编译出来的可执行文件大小,其中包括了代码段,数据段等数据信息,如下:

donge@Donge:$ size Donge-Demo
   text    data     bss     dec     hex filename
  12538    1916   43632   58086    e2e6 Donge-Demo
  • text:代码段大小
  • data:数据段大小
  • bssbss段大小
  • 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、内核态空间布局

下面我们来看一下内核态的虚拟空间布局,首先我们要知道:

  1. Linux系统中,用户进程通常只能访问用户空间的虚拟地址,只有在执行内陷操作或系统调用时才能访问内核空间。
  2. 所有的进程通过系统调用进入内核态之后,看到的虚拟地址空间都是一样的,他们是共享内核态虚拟内存空间的。

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:该区域虚拟内存地址可以自由映射到物理内存的高端地址上,“固定”表现在“虚拟内存空间地址是固定的”,被映射的物理地址是可变的。

为什么会有固定映射这个概念呢 ?

比如:在内核的启动过程中,有些模块需要使用虚拟内存并映射到指定的物理地址上,而且这些模块也没有办法等待完整的内存管理模块初始化之后再进行地址映射。因此,内核固定分配了一些虚拟地址,这些地址有固定的用途,使用该地址的模块在初始化的时候,将这些固定分配的虚拟地址映射到指定的物理地址上去。

image-20231005155942462

4、总结

以上就是整个虚拟地址空间的划分,总结如下:

image-20231005160802093

点赞+关注,永远不迷路

img
欢迎关注 公号&星球【嵌入式艺术】,董哥原创!

猜你喜欢

转载自blog.csdn.net/dong__ge/article/details/133782500