文章目录
C语言地址空间回顾
我们在讲C语言的时候,老师给大家画过这样的空间布局图
C/C++内存分布简图:
+------------------+ 高地址
| |
| 栈 | <- 本地变量,函数调用信息等
| |
+------------------+
| |
| 堆 | <- 动态分配的变量(malloc/new)
| |
+------------------+
| |
| 未初始化数据段 |
| (BSS 段) | <- 未初始化的静态和全局变量
| |
+------------------+
| |
| 已初始化数据段 | <- 已初始化的静态和全局变量
| (数据段) |
| |
+------------------+
| |
| 常量数据段 | <- 字面量,常量字符串等
| |
+------------------+
| |
| 代码段 | <- 程序代码(虚函数和普通函数)
| |
+------------------+ 低地址
进程地址空间概念
实际上操作系统会给每一个进程都创建一个独立的虚拟地址空间,然后通过页表将虚拟地址空间与物理内存一一对应 (映射),我们用户只能得到虚拟地址空间中的虚拟地址,当我们修改虚拟地址中的数据时,操作系统会先通过页表找到对应的物理内存,然后修改物理内存中的数据。
Linux是如何管理每个进程的地址空间?
所以,和管理进程一样,操作系统会使用一种内核数据结构来对地址空间进行管理,Linux中用于 管理地址空间的内核数据结构叫做 mm_struct,操作系统会为每个进程创建一个 mm_struct 对象,然后通过管理结构体对象来间接管理进程地址空间。
查看mm_struct的定义:
struct mm_struct
{
// ...
unsigned long total_vm, locked_vm, shared_vm, exec_vm;
unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
// ...
};
total_vm
:可能表示虚拟内存的总量。locked_vm
:可能表示被锁定,不能被换出的虚拟内存的数量。shared_vm
:可能表示被多个进程共享的虚拟内存的数量。exec_vm
:可能表示可执行的虚拟内存的数量。stack_vm
:可能表示栈内存的虚拟内存的数量。reserved_vm
:可能表示已预留但尚未使用的虚拟内存的数量。def_flags
:可能表示默认的内存区域标志。nr_ptes
:可能表示页表条目的数量。start_code
,end_code
:可能表示代码段的开始和结束位置。start_data
,end_data
:可能表示数据段的开始和结束位置。start_brk
,brk
:可能表示堆的开始位置和当前位置。start_stack
:可能表示栈的开始位置。arg_start
,arg_end
:可能表示命令行参数的开始和结束位置。env_start
,env_end
:可能表示环境变量的开始和结束位置。
可以看到,进程地址空间其实也是进程属性的一种,我们可以通过进程的 task_struct
来找到/管理进程对应的地址空间。
为什么要有进程地址空间和页表?
- 因为有了进程地址空间和页表,物理内存空间上不连续、无序的空间就可以通过页表这一映射关系联系在一起,让进程以统一的视角看待内存。
- 有了进程地址空间和页表后,每个进程都认为自己在独占内存,这样能更好的保障进程的独立性以及合理使用内存空间(当实际需要使用内存空间的时候再在内存进行开辟),并能将进程管理与内存管理进行解耦合。
- 地址空间+页表的设计是保护内存安全的重要手段!
页表的细节
页表其实不光存放虚拟地址和物理内存地址,还有其他的属性,比如会存放权限属性。