目录
一.深入了解进程地址空间:
单个进程与进程地址空间与物理内存之间的联系图:
多个进程与进程地址空间与物理内存之间的联系图:
二.为什么会存在进程地址空间呢?
作用1:进程地址空间的存在,保证了其他进程无法直接访问内存空间,保护了内存中代码和数据的安全性。
作用2:地址空间的存在,保证了进程与进程之间代码数据的解耦——写实拷贝。保证了进程间的独立性特性。
说作用3之前,再整体概述一下进程运行后与进程地址空间与物理内存的流程:
那么虚拟地址是如何产生的呢?
再举个生活上的例子加深理解:
作用3:通过虚拟地址空间来屏蔽底层内存申请的过程,从而达到进程和OS进行内存管理操作,进行进程调度和内存管理相互解耦的操作。
作用4: 让进程能够以统一的视角看待进程对应的代码数据等各个区域,让编译器也能以统一的视角进行代码的编译。
作用5:进程地址空间的连续化区域划分,可以降低异常越界访问的概率。
之前,我讲述了有关进程地址空间的定义概念,通过列举的几个例子从各方面更加深刻的理解到了进程地址空间:( Linux进程地址空间——上篇_)
接下来让我们继续理解进程地址空间的重要性吧!
一.深入了解进程地址空间:
单个进程与进程地址空间与物理内存之间的联系图:
当磁盘中的可执行程序被加载到内存后,程序变为了进程,进程有了自己的PCB进程控制块(struct task_struct结构体),有了自己独立的进程地址空间(struct mm_struct结构体),该进程中的代码和数据也会被加载到内存中,因为进程地址空间中存放的是代码中各个变量和函数的虚拟地址,物理内存中存放的是各个变量和函数的物理地址,意味着变量和函数同时拥有两套地址,那么我们可以推论出:虚拟地址和物理地址是相互映射的。
既然是相互映射,就好比我们使用汉语字典,通过偏旁部首、拼音等找法可以查找到我们需要的那一个汉字的具体所在页的具体解释。所以页表就担当了此种重任!页表是用来连接物理地址和虚拟地址相互映射的一张表,使得CPU可以通过虚拟地址找到物理地址,从而找到进程所在内存中的代码和数据。
多个进程与进程地址空间与物理内存之间的联系图:
每个进程都认为它自己是独占内存空间的,都认为自己有4GB的物理内存空间(其实是4GB的虚拟地址空间),每个进程并不知道还有和自己一样的存在——操作系统画的大饼!
二.为什么会存在进程地址空间呢?
作用1:进程地址空间的存在,保证了其他进程无法直接访问内存空间,保护了内存中代码和数据的安全性。
根据上图,假如没有进程地址空间的话, 进程1可以直接访问物理内存,进入进程1的代码和数据后,完成相应的操作,但这也会有危险性,进程1可能会访问到进程2的代码数据,万一进程1是个伪装好的病毒进程,能够直接访问内存的话就会窃取到其他进程的数据资源,给用户造成极大的损失。
所以进程地址空间的存在就好比是物理内存空间的保安,你必须经过检验,合法了才能够访问物理内存。
举个例子:抗战时期,上海等地的共产党员前辈们为了能够在敌特后方潜伏下来获取情报,专门为自己建立了一个虚假的身份,目的就是为了更好的做潜伏工作,防止敌人怀疑自己。平常情况下就利用表面身份(虚拟地址)进行伪装,等给同志传递情报的时候,他们就会通过各种口语暗号(页表映射)表明彼此双方的真实身份(物理地址),在某时某地进行相关事宜的见面。
注:检验是页表的事情,页表可不仅仅是用来虚拟地址映射物理地址的,还有检验拦截的作用,对于不合法的进程访问会进行拦截,它就无法访问到内存。
作用2:地址空间的存在,保证了进程与进程之间代码数据的解耦——写实拷贝。保证了进程间的独立性特性。
代码底层设计图解:
gloval作为全局变量,子进程在执行流中修改了全局变量的值,gloval变为了300,但是在父进程的执行流中仍然是gloval=100。
形成情况的主要原因就是:gloval作为全局变量是共享数据,当某个进程修改了被共享的数据时,操作系统会根据被修改的数据重新拷贝一份,重新给其开辟空间,供子进程修改,那么以后子进程访问到gloval就是在内存中新开辟拷贝下的数据地址了,而父进程访问到的仍是原gloval的数据地址不变!
这就体现了进程的独立性,多个进程之间的运行是互不影响的。
任何一方进程想要更改被进程多方共享的数据时,os操作系统都会对该方进程进行数据拷贝,形成新的物理地址,并且更改页表映射,最后让该进程进行修改。这一操作称为写时拷贝。
写实拷贝技术的原因:提高进程创建的效率,有可能多个进程只是读取共享的数据,并不改。 所以当执行到修改共享数据时,操作系统才会开辟新的内存物理地址,拷贝数据到该新物理地址中,供该进程修改。
说作用3之前,再整体概述一下进程运行后与进程地址空间与物理内存的流程:
可执行程序想要被执行,就得从磁盘被加载到内存中成为进程才行。成为进程后,操作系统会根据进程生成其PCB进程控制块,CPU不会执行进程,它只执行进程的PCB,CPU拿到进程PCB后,开始处理该进程的代码和数据。在处理过程中,CPU获取到了代码中的各种函数和变量的虚拟地址,这是不够的,它得去内存中找代码形成的逻辑,找到执行指令才能用继续运行。于是CPU通过寻找PCB的参数指针指向虚拟地址空间,在代码区,数据区或者堆栈区找到这些函数和变量的虚拟地址,将这些虚拟地址再通过页表映射找到在内存的物理地址。于是CPU成功在内存中找到了这些函数,但CPU不认识物理地址,于是让操作系统携带着内存的指令传回CPU去执行。
那么虚拟地址是如何产生的呢?
例:
当我们的my.c文件经过编译的过程中,就会生成虚拟地址,比如全局变量a经过编译,有了0x2222的地址,fun,main函数也有了各自的虚拟地址 ! ! !
cpu在运行该进程的PCB时,会执行进程里面的代码数据,例如用到变量a时,CPU会通过进程地址空间的虚拟地址,通过查找页表,映射到物理地址。在内存中,该变量a会将自己的虚拟地址再传回CPU中执行指令;当cpu用到main函数,fun函数时也是这样,cpu进入地址空间通过页表找到这些函数所在内存物理地址后,这些函数所涉及到的运算指令会进入CPU中执行相应的操作。
注意: CPU执行的是指令,它只认识虚拟地址,通过虚拟地址去执行指令操作从始至终CPU都不会遇到这些函数的物理地址!
再举个生活上的例子加深理解:
当我们高考完准备上大学的时候,收到了录取通知书,有了自己的学籍(录取通知书和学籍就是程序的信息描述),入学当天,我们就好比是可执行程序。入学前我们是磁盘中的可执行程序,入学后就好比是我们被加载到内存成为了进程。而我们的被分配到21号宿舍楼,x层X号宿舍,宿舍楼房间号就是内存(物理地址),而我们的学号就是虚拟地址。辅导员安排班长要求我们打扫教室的时候,说学号尾号为0-3的礼拜一打扫崇学楼,4-6的礼拜二打扫崇学楼....。
注:男生宿舍楼离崇学楼远,女生离崇学楼近。
老师不管我们的宿舍楼离教学楼是否远,只要求按照学号要求进行打扫。而老师就好比是CPU,不管我们住哪里(物理地址),不管离打扫的地方多远,只说学号0-3的,4-6(虚拟地址)的按指定的日期进行打扫。
打扫完的学生按学号登记报表并报回给辅导员(报回就好比是内存中的代码将自己的虚拟地址传回到CPU一一步骤4)。若是有忘记打扫的,辅导员会让班长去通知,例如,小明忘记今天打扫了,班长拿着班名册信息表(页表) 去找宿舍楼xx宿舍号找小明,告知他及时去打扫。
作用3:通过虚拟地址空间来屏蔽底层内存申请的过程,从而达到进程和OS进行内存管理操作,进行进程调度和内存管理相互解耦的操作。
操作系统有四种核心管理:进程管理、内存管理、驱动管理、文件管理。
如果没有进程地址空间,进程直接访问物理内存,当进程退出时,内存管理需要尽快将该进程回收,在这个过程当中必须得保证内存管理得知道某个进程的退出信号,内存管理也得知道某个进程开始的信号,这样操作系统才能给它们及时的分配资源和回收资源,这就意味着内存管理和进程管理模块是强耦合的。
也就是说内存管理和进程管理联系比较大,通过我们上面的理解,如果有了进程地址空间,当一个进程需要资源的时候,通过页表映射去要就可以了,内存管理就只需要知道哪些内存区域(配置)是无效的,哪些是有效的(被页表映射的就是有效的,没有被页表映射的就是无效的),当一个进程退出时,它的映射关系也就没了,此时没有了映射关系,物理内存就将该进程的数据设置称无效的。
所以第二个好处就是将内存管理和进程管理进行解耦,内存管理是怎么知道有效还是无效的呢? 比如说在-块物理内存区域设置一个计数器count,当页表中有映射到这块区域时,count就++,当一个映射去掉时,就将count--,内存管理只需要检测这个count是不是0,如果为0,说明它是没人用的。
作用4: 让进程能够以统一的视角看待进程对应的代码数据等各个区域,让编译器也能以统一的视角进行代码的编译。
作用5:进程地址空间的连续化区域划分,可以降低异常越界访问的概率。