进程地址空间
进程地址空间是指每个进程在计算机内存中所占用的地址空间。地址空间是指能被访问的内存地址范围,它由若干个连续的内存块组成。每个进程都有自己的地址空间,这意味着每个进程都有自己的内存地址范围,不会与其他进程冲突。进程地址空间通常被划分为几个部分,包括代码段、数据段、堆和栈等,它是一种特定的数据结构,进程task_struct内部指向其结构,次结构在系统中叫做mm_struct。具体的结构分布如下图:
下面是在Linux下运用代码演示其地址:
[zhujunhao@bogon code]$ cat test.cpp #include <iostream> #include <unistd.h> #include <cstdio> using namespace std; int main() { char* s = new char[5]; cout << "&s: " << &s << endl; int n = 8; cout << "&n: " << &n << endl; return 0; } [zhujunhao@bogon code]$ ./test.exe //可看出堆区地址高,对应上面进程地址空间 &s: 0x7fff23280dc8 &n: 0x7fff23280dc4
在vs下的测试可能还不一样,数据可能会出现异常,在Linux下可以测试出。这时因为Windows下出于安全的考虑,可能会对齐做出调整,Linux下也会做出安全考虑,只不过比Windows较为“ 优雅 ”。
物理地址与虚拟地址
物理地址是系统内存中真正意义上存储数据所对应的一种地址,虚拟地址是模拟来的一种地址,通常与物理地址建立联系,然后从物理地址中对数据做出改变。我们平常使用C/C++中的地址都是虚拟地址。我们先来观察以下代码。
[zhujunhao@bogon code]$ cat code.cpp #include <iostream> #include <cstdio> #include <unistd.h> using namespace std; int a = 100; int main() { pid_t p = fork(); if (p == 0) { cout << "Child:" << endl; a = 200; cout << " a = " << a << " " << "&a = " << &a << endl; } else { cout << "Father:" << endl; cout << " a = " << a << " " << "&a = " << &a << endl; } return 0; } [zhujunhao@bogon code]$ g++ -o code.exe code.cpp [zhujunhao@bogon code]$ ./code.exe //发现地址一样,但数据不一样 Father: a = 100 &a = 0x601074 Child: a = 200 &a = 0x601074
注意,由于数据明显不一样,所以以上的地址不是物理地址,是虚拟地址/线性地址。也就是说,我们所用到的所有地址,全都不是物理地址,是虚拟或线性地址,而物理地址,用户一概看不到,由OS统一管理,其中,OS必须负责将虚拟地址转化成物理地址。
在以上代码中,当子进程修改变量时,系统将会找到从子进程所对应的进程地址空间中的虚拟地址对应的物理地址,在物理地址内部开辟一块自己存储数据的空间,修改数据时将会修改从虚拟地址到物理地址的眏射表,眏射表将会修改那块存储数据的空间中的数据并重新建立两地址转换间的眏射关系,原本的数据不会被影响,所以,父子进程虽然虚拟地址一样,但数据不一样。
系统之所以这样设计原因有以下三点:
1,将物理内存从无序变有序。如果让让进程直接访问内存的话,那么每个进程存在内存中的位置都是不一样的,将会增加操作系统管理的成本。
2,将内存管理和进程管理进行耦合。虚拟地址负责管理进程,物理地址负责管理内存。
3,虚拟地址的设计间接保护了内存安全。如若直接访问物理地址,当用户胡乱操作时,将会造成不可逆转的损坏,虚拟地址的使用对其做出了拦截,保证了系统安全。