进程地址空间

avatar
作者
筋斗云
阅读量:0

一、什么是地址空间

先来看一段代码

下面在代码或是画的图都是以32平台的环境下展示。

可以看到一个奇怪的现象,我们知道子进程可以访问到父进程的数据,两个进程的g_val的地址也是相同的,但在子进程修改g_val时父进程的g_val没有发生改变,两个进程的变量明明地址是一样的,可里面存在值却不一样

其实我们c/c++运行代码看到的地址全部不是真正的物理地址(内存地址),是虚拟地址。OS会将这些虚拟地址变为物理地址,用户是不可见的。

虚拟地址空间

虚拟地址空间就是这篇要谈的进程地址空间,它是以一个结构体的方式存储在当前进程的test_struct内(每个进程都有),mm_struct

虚拟地址空间在理论上的构建是仿造物理内存,它假设自己也有4G的地址,和物理内存一样使用0x00000000~0xFFFFFFFF来表示所有地址,然后每个区域都使用两个变量来划分(静态区、栈、堆、代码区等等)。

页表

当程序创建对象时会在对应区域开辟空间(给它一个虚拟地址),伴随虚拟地址空间的创建还会创建一个页表,页表是对虚拟地址和物理地址做一种映射。进程创建了一个虚拟地址,系统也会在物理地址上开辟相对应的空间大小地址,然后将虚拟地址和开辟的物理地址存放到页表内做映射。在进程的角度修改对象或拿到对象的值时,就是使用它的虚拟地址到页表内拿到物理地址在去进行修改。

这个时再来看上面的代码,父进程和子进程的g_val地址相同,其实是它们的虚拟地址相同,实际的物理地址是肯定不同的,才导致修改子进程不会影响父进程。

二、为什么存在地址空间

一、先来说没有地址空间,那么进程就会直接去访问物理地址,物理地址每次开辟的大小都基本是不同的,就会导致地址特别分散访问和开辟新的空间都会被影响。有了地址空间就会将无序变为有序,进程得以统一的看待物理内存以及自己的各个区域,访问就只是多了层映射,物理内存的管理全部交给了bash,这同时让进程管理模块和内存管理模块进行了解耦(互相独立)

二、对访问和修改数据做了检查

访问

int arr[10]; arr[11] = 100;

上面这段代码很明显是越界了,那我们从进程的角度看看如何处理。

        在修改时arr[11]进程需要先找到它,先将arr[11]拆解为*(arr+11),从arr的虚拟地址向后走11个整形大小拿到arr[11]的虚拟地址,然后在页表内查找对应的物理地址,可是页表中根本没有这个地址,因为没有为arr[11]开辟空间,这时系统就会拦截进程对物理地址的访问,返回错误信息。

修改数据

char* ch = "abcd"; ch = "hello";

在语法在角度我们会理解为,ch指向的“abcd”是在常量区,是不可修改的。进程上找到ch指向的虚拟地址,在通过页表拿到物理地址时,因为页表会标记当前物理地址的读写状态,所以会进行判断。“abcd”字符常量显然是只有读状态,系统会拦截这次请求。

写时拷贝

再回到最开始的代码,父进程创建子进程,子进程会拷贝父进程的task_struct和数据,代码是共享的,其中task_struct内的部分内容是需要子进程自己填写的,如pid、ppid。

全局变量g_val刚开始父子进程不只虚拟地址是相同的,所映射的物理地址也是相同的,物理地址发生改变是在子进程修改g_val时,页表会标记当前物理地址被几个虚拟地址映射了,如果大于1那么系统会为虚拟地址重新开辟一块物理地址做映射,进行使用。

写时拷贝在进程上确保了进程的独立性,在内存上减少了资源的不必要消耗。

页表作用的补充:

页表除了写时拷贝外还在挂起状态时减省的内存资源,挂起是在内存不足时将暂时不使用的资源存放到硬盘。如果不理解挂起状态可以看看我前面的进程状态的文章。

场景:现在系统内存不足,有一个进程它的代码已经执行了一半,OS就可以让这个进程已执行的一半代码挂起,页表内代码的虚拟地址不变,只修改物理地址,物理地址指向未执行代码。这时就处于一种状态,进程还是会认为自己的代码还在内存中,OS则减少了内存的资源,并且还不会影响进程运行。

虚拟地址是从哪里来:
可执行程序在磁盘时就已经存在虚拟地址了,我们在VS内写入任意代码将代码转汇编时就能看到地址的存在。

这时调用函数就是跳转到对应的地址,所以编译器没有地址就无法进行编译,编译器需要自行生成虚拟地址,在程序加载到内存时,进程是直接将数据中的虚拟地址填入到页表中。这里注意编译器是不会开辟栈和堆的虚拟地址,因为栈和堆是需要在运行过程中OS为其开辟空间。

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!