一、进程地址空间的完整组成
进程的地址空间分为 用户空间 和 内核空间 ,两者通过 页表隔离,具体结构如下:
1. 用户空间(User Space)
用户空间是进程可以直接访问的部分,包含以下区域:
-
(1) 代码段(Text Segment)
- 存储内容:程序的可执行指令(机器代码)。
- C/C++ 对应 :函数代码(包括类的成员函数)、常量字符串(如
"Hello"
)。 - 权限:只读(防止程序修改自身指令)。
-
(2) 数据段(Data Segment)
- 初始化数据段(.data) :显式初始化的全局变量和静态变量(如
int x = 10;
)。 - 未初始化数据段(.bss) :未显式初始化的全局变量和静态变量(如
int y;
)。 - C++ 补充:类的静态成员变量也存储在此处。
- 初始化数据段(.data) :显式初始化的全局变量和静态变量(如
-
(3) 堆(Heap)
- 动态分配内存 :通过
malloc
(C)或new
(C++)分配的内存。 - 特点:手动管理(需显式释放),空间较大但可能产生碎片。
- 动态分配内存 :通过
-
(4) 栈(Stack)
- 存储内容:函数调用时的局部变量、参数、返回地址等。
- 特点:自动管理(函数结束时自动释放),空间有限(默认约几 MB)。
- C++ 补充:对象的构造函数和析构函数在此触发。
-
(5) 共享内存与内存映射区(Shared Memory & Mapped Regions)
- 共享内存 :进程间通信(IPC)的共享数据(如
shmget
创建的内存)。 - 内存映射文件 :通过
mmap
映射的文件或动态链接库(如.so
、.dll
)。
- 共享内存 :进程间通信(IPC)的共享数据(如
2. 内核空间(Kernel Space)
- 存储内容:操作系统内核的代码、数据结构(如进程控制块 PCB)、设备驱动等。
- 访问方式 :用户进程无法直接访问,必须通过 系统调用 (如
read
、write
)进入内核态。 - 页表映射:所有进程共享同一份内核空间页表,但通过权限位隔离用户访问。
二、C/C++ 变量的存储位置
1. 全局变量与静态变量
-
全局变量 :存储在数据段(
.data
或.bss
)。cppint global_var = 10; // .data 段 int uninit_global_var; // .bss 段
-
静态变量 :
-
类静态成员变量:存储在数据段。
cppclass MyClass { static int static_member; // .bss 段(需在类外初始化) };
-
局部静态变量:存储在数据段(首次执行时初始化)。
cppvoid func() { static int local_static = 0; // .data 段 }
-
2. 常量
-
字符串常量 :存储在代码段或
.rodata
段(只读)。cppconst char* str = "Hello"; // "Hello" 在 .rodata 段
-
const 变量 :
-
全局
const
变量:存储在.rodata
段。cppconst int kMaxSize = 100; // .rodata 段
-
局部
const
变量:存储在栈中。cppvoid func() { const int local_const = 5; // 栈中 }
-
3. 动态分配的内存
-
堆内存 :通过
new
或malloc
分配。cppint* heap_var = new int(42); // 堆中
4. 函数代码
-
普通函数 :存储在代码段。
cppvoid myFunction() { ... } // 代码段
-
虚函数表(vtable) :C++ 中类的虚函数表存储在只读段(如
.rodata
)。
三、页表如何映射逻辑地址到物理地址
1. 逻辑地址与物理地址的划分
- 逻辑地址(虚拟地址) :进程视角的连续地址空间(如
0x00000000
~0xFFFFFFFF
)。 - 物理地址:实际内存硬件的地址(如 DRAM 的物理地址)。
2. 页表的作用
- 映射关系:记录逻辑页号(Virtual Page Number, VPN)到物理页帧号(Physical Frame Number, PFN)的对应关系。
- 权限控制:标记页的读写权限(如代码段只读、堆可读写)。
3. 多级页表示例(以 32 位系统为例)
-
逻辑地址结构 :
| 10 bits (一级页号) | 10 bits (二级页号) | 12 bits (页内偏移) |
-
转换过程 :
- 通过一级页号在页目录中找到二级页表地址。
- 通过二级页号在二级页表中找到物理页帧号。
- 物理地址 = 物理页帧号 × 页大小(4 KB) + 页内偏移。
4. 内核空间的页表映射
- 共享内核页表:所有进程的页表中,内核空间的映射是相同的(指向同一物理内存)。
- 切换上下文:当进程通过系统调用进入内核态时,CPU 切换到内核页表,访问内核代码和数据。
5. 共享内存的页表映射
- 共享物理页帧:多个进程的逻辑地址映射到同一物理页帧。
- 实现方式 :通过
shmget
或mmap
创建共享内存后,操作系统修改相关进程的页表项。
四、总结与对比表格
存储区域 | 内容 | C/C++ 对应 | 页表映射特点 |
---|---|---|---|
代码段 | 程序指令、常量字符串 | 函数代码、const char* |
只读,直接映射到物理内存 |
数据段(.data) | 初始化的全局/静态变量 | int x = 10; 、类静态成员变量 |
可读写,独占物理页帧 |
数据段(.bss) | 未初始化的全局/静态变量 | int y; |
初始为零页,按需分配物理内存 |
堆 | 动态分配的内存 | new 、malloc |
按需分配,可能跨多个物理页帧 |
栈 | 局部变量、函数参数 | 函数内的 int z; |
自动管理,通常连续分配 |
共享内存/映射区 | IPC 共享数据、内存映射文件 | shmget 、mmap |
多个进程共享同一物理页帧 |
内核空间 | 操作系统内核代码和数据 | 系统调用、驱动代码 | 所有进程共享同一内核页表 |
五、关键问题解答
-
进程是否拥有内核地址空间?
- 每个进程的地址空间包含 用户空间和内核空间的逻辑映射,但内核空间的实际物理内存由所有进程共享。用户进程无法直接访问内核空间(需通过系统调用)。
-
C++ 是否存在"静态区"?
- C++ 的静态变量(全局/局部静态、类静态成员)存储在数据段(
.data
或.bss
),而非独立的"静态区"。术语"静态区"是数据段的别名。
- C++ 的静态变量(全局/局部静态、类静态成员)存储在数据段(
-
共享内存位于进程地址空间的哪个区域?
- 共享内存位于用户空间的 内存映射区域,通过页表映射到同一物理页帧,实现进程间数据共享。