linux进程的内存空间映射(段)
Linux进程的内存空间映射
在 Linux 中,每个进程的内存空间是一个虚拟地址空间,操作系统通过内存映射机制(Memory Mapping)将不同的内存区域分配给不同类型的资源和需求。内存空间映射决定了进程如何访问不同类型的内存,包括程序代码、数据、堆栈、共享库、内核提供的虚拟内存等。
1. 进程内存空间结构
Linux 进程的内存空间是按照虚拟内存模型来管理的。虚拟内存将进程的内存空间划分为不同的区域,每个区域有不同的用途和属性。典型的 Linux 进程的内存空间结构从低地址到高地址大致分为以下几部分:
用户空间 (User Space)
- 程序代码段 (Text Segment):
- 存放程序的可执行代码。
- 通常是只读的,以避免代码被修改。
- 在内存映射中,通常标记为
r-xp
(只读可执行)。
aaaadc650000-aaaadc651000 r-xp 00000000 b3:02 521701 /home/guol/app3
- 数据段 (Data Segment):
- 存储程序初始化的全局变量、静态变量等数据。
- 分为只读数据段和可读写数据段。
- 数据段的内容在程序启动时已经初始化,可以修改。
aaaadc660000-aaaadc661000 r--p 00000000 b3:02 521701 /home/guol/app3
aaaadc661000-aaaadc662000 rw-p 00001000 b3:02 521701 /home/guol/app3
- 堆 (Heap):
- 用于动态分配内存,通过
malloc
、free
等函数进行管理。 - 堆的大小在程序运行时动态增长或缩小。
- 通常由
brk
系统调用来管理内存的分配。
aaab080f4000-aaab08115000 rw-p 00000000 00:00 0 [heap]
- 栈 (Stack):
- 用于存放函数调用时的局部变量、返回地址和函数调用时的上下文等。
- 栈的大小通常是有限制的,超过栈的最大空间可能导致栈溢出(stack overflow)。
fffffdc1f000-fffffdc40000 rw-p 00000000 00:00 0 [stack]
- 内存映射文件 (Mapped Files):
- 用于映射共享库(如
libc.so
、libstdc++.so
)和其他需要动态加载的文件(如自己写的库文件libmyCppLib.so
)。 - 共享库的代码段通常是只读可执行的。
ffff8b000000-ffff8b189000 r-xp 00000000 b3:02 57279 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff90c30000-ffff90e3a000 r-xp 00000000 b3:02 54038 /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.30
ffff91020000-ffff91021000 r-xp 00000000 b3:02 1172813 /home/guol/cmaketest/build/libmyCppLib.so
- 共享内存 (Shared Memory):
- 用于不同进程之间的内存共享。
- 共享内存可以由多个进程同时访问,并且可能会出现在进程的映射区域中。
ffff8b198000-ffff8b19c000 r--p 00188000 b3:02 57279 /usr/lib/aarch64-linux-gnu/libc.so.6
ffff8b19c000-ffff8b19e000 rw-p 0018c000 b3:02 57279 /usr/lib/aarch64-linux-gnu/libc.so.6
- 虚拟动态共享对象 (VDSO):
- 这是由内核提供的一种特殊机制,用于提高系统调用的效率,避免频繁的上下文切换。
- 内核将某些常用的系统调用(如
gettimeofday()
)映射到用户空间的 vdso 区域,用户空间直接调用该区域中的代码,减少进入内核的开销。
ffff8b203000-ffff8b204000 r-xp 00000000 00:00 0 [vdso]
- 信号机制中,用户定义的信号处理函数就是用的这块内存空间映射来完成信号处理函数完成后跳转到改地方的函数再进入内核恢复进程上下文的(详细见linux信号机制文章)。
- 虚拟变量区域 (VVAR):
- 存放由内核提供的虚拟变量(如系统启动时间等)供用户进程使用。
- 该区域中的变量由内核管理,用户进程可以直接读取。
ffff8b201000-ffff8b203000 r--p 00000000 00:00 0 [vvar]
- 内存映射区域 (Memory Mapped Area):
- 用于
mmap()
系统调用,通常用于内存映射文件或匿名内存映射(如共享内存区域)。 - 这部分内存的内容通常是动态分配的。
0x7fffffff6000-0x7fffffff7000 rw-p 00000000 00:00 0 [anon]
- 实际分布(链接了自制的动态链接库
libmyCppLib.so
)
内核空间 (Kernel Space)
- 内核空间是操作系统内核使用的内存区域,用户空间无法直接访问。
- 内核地址空间通常位于高地址,用户进程无法直接读取或写入内核空间。内核通过系统调用、陷入和中断等机制与用户空间进行交互。
- 在 64 位架构中,内核空间的虚拟地址通常从
0xffff000000000000
开始,分配给内核使用。
2. 特殊区域
-
[vvar]:虚拟变量区(Virtual Variable Area)。内核将一些常用的内核变量(如系统时间、进程 ID 等)映射到该区域,用户进程可以快速访问,减少对内核的访问。
-
[vdso]:虚拟动态共享对象区(Virtual Dynamic Shared Object)。由内核提供,用户空间可以直接调用其中的一些函数(如
gettimeofday())
来避免进入内核态。
3. 进程内存映射表
每个进程都有一个 /proc/[pid]/maps
文件,记录了该进程的内存映射情况。通过 cat /proc/[pid]/maps
命令可以查看进程的内存映射信息。
该文件包含每个内存区域的以下信息:
- 地址范围(虚拟地址的开始和结束)
- 权限(
r
读、w
写、x
执行、p
私有) - 偏移量
- 设备(设备号)
- inode(文件的索引节点号)
- 区域的名称(如共享库、堆、栈、匿名映射等)
4. 动态链接器的进入
- 在elf文件(linux下的可执行程序)中的
.got
表中存放了动态链接库的入口地址,同样.got
存放的第一个地址一直是0
(undefine),其他空间存放的是其他库函数的入口地址,将实现动态绑定(即运行时动态链接器绑定)。 .got
存放的第一个地址一直是0
,当0
地址作为目的地跳转时则会触发异常,进入内核态,然后内核会将控制权交给动态链接器进行库函数的动态链接(这样就完成了库函数的动态链接绑定)。
- udf 指令(即要跳转的指令地址为
0
)并不是导致程序跳转到地址 0,而是触发了一个异常(未定义指令异常),并将控制权交给动态链接器。