Linux:进程地址空间
https://i-blog.csdnimg.cn/direct/6a3f7126dc78453ba861540283345f50.gif一、C语言内存管理基础
引入:从前我们知道一个指针指向的如果是一个常量字符串,那么这个就是指向的常量区,只读不可被修改,因此下面的程序会瓦解。
https://i-blog.csdnimg.cn/direct/1382ef93f5754cda9a0878db56058afa.png
1、在我们C语言内存管理机制内里线性地址是有区域划分的。
https://i-blog.csdnimg.cn/direct/80a7132d677941228f4061c4eceafd24.png
我们怎样验证这个区域的划分是否正确呢?可以通过代码的方式在差异的区域创建变量然厥后取地址获取再进行比较
https://i-blog.csdnimg.cn/direct/133f390fbd63491aa6d29cf4705b9a96.png
https://i-blog.csdnimg.cn/direct/8692b42342194359abc791506db2e839.png
2、栈区和堆区是相对而行的!!
验证栈区:地址在变低
https://i-blog.csdnimg.cn/direct/48b4de645a194dc3a6c78ead8c16154f.png
https://i-blog.csdnimg.cn/direct/ea3b89023931498d9a2064b2253ce7e0.png
验证堆区:地址在变高
https://i-blog.csdnimg.cn/direct/8146db29ab1a4ab69ade202f8ce6ea04.png
https://i-blog.csdnimg.cn/direct/e3e409a6fd274c5cbb3d6fac7f492087.png
3、静态变量会被定义在全局区 只不过只会在作用域里使用。
https://i-blog.csdnimg.cn/direct/c5b498e9eb804d60a02fa2fb44307166.png
https://i-blog.csdnimg.cn/direct/6a9b6ebec47f4366956ec80e3f78fb9a.png
二、fork遗留问题
历史遗留问题:为什么一个变量可以同时即是0又同时>0 ??
https://i-blog.csdnimg.cn/direct/ae6d06ea4e444418a2a811783f547a97.png
实行:https://i-blog.csdnimg.cn/direct/c817b1b609964108a53f69156ac87dd9.png
https://i-blog.csdnimg.cn/direct/25627bf012c2451cb524f8eee28cce45.png
我们会发现同一个地址竟然读到了差异的内容!! 如果变量的地址是一个物理地址,是绝对不可能出现这种环境的,因此我们的变量地址必然是不是物理地址!!
——>结论:我们平时C/C++内里使用的地址全都不是物理地址,而是虚拟地址! 用户是看不到物理地址的,而OS必须要负责将我们所看到的虚拟地址转化成物理地址!
三、进程地址空间
https://i-blog.csdnimg.cn/direct/f7cdbec918a84e06b1a5c6a526cc4d4c.png
实在我们的之前所学的线性地址,并不是真正的物理内存,而是在PCB内部有一个指针指向了一块进程地址空间,然后虚拟地址会通过页表来映射到详细的物理地址。 ——>所以当我们创建出一个子进程后,他会拷贝一份和父进程一样的地址空间,然后当子进程想要修改对应的数据时,此时就会发生写时拷贝(由操纵系统自动完成),也就是重新开辟空间,在这个过程当中只有页表对应的物理地址发生了变革,左边的地址空间不会有任何的感知。
3.1 什么叫做地址空间
在32位的呆板中,有32位的地址和数据总线,所以每一根地址总线有0或1,实在从本质上来说盘算机能够识别是高低电频而并非二进制,所以1代表的是高电频,0代表的是低电频。——>这个过程就是CPU通过像内存充电的形式告诉内存我需要哪个地址,然后内存就能够通过识别高低电频,形成一个物理数据,将地址对应的数据以同样的方式交给CPU。
https://i-blog.csdnimg.cn/direct/6898525b13444ac98b13f7ad5e2571c1.png
所以地址空间就是地址总线分列组合形成的地址的范围【0,2^32】
3.2 怎样理解地址空间的区域划分?
举个例子:比方说当前的桌子有100cm长,坐着小胖和小美,但是小胖经常骚扰小美,所以就在桌子中心画了一个三八线。 一人只有50cm的空间。所以从结构上就可以如下划分:
https://i-blog.csdnimg.cn/direct/fed755649d9c4d8c8e9f60e64172c1ff.png
区域划分就是通过结构体内部的start和end去做划分
怎样理解区域的变大大概变小呢??——>修改对应结构体内部的start和end即可
https://i-blog.csdnimg.cn/direct/ccae87060ecf42d8885d937cf1249e6a.png
我们不但要看到地址空间的范围,我们要知道在范围内连续的空间中,每一个最小单元都可以有地址,这个地址可以被直接使用!!
3.3 什么是进程地址空间
所谓进程地址空间,本质上就是一个形貌进程可视化范围的地址空间内存在各种区域划分,对线性地址进行start、end即可 。本质上实在就是一个内核数据结构,和PCB一样,地址空间也是需要被操纵系统管理的:先形貌再组织。 而每一个进程都有本身的进程地址空间,PCB内部有一个指针指向这块空间!
https://i-blog.csdnimg.cn/direct/641fe91b5f6341cd938526d1d2af37f1.png
四、页表
共识:当代操纵系统中,险些不做浪费空间和时间的事情!
4.1 写时拷贝、缺页中断、惰性加载
页表详细有哪些内容呢??——>虚拟地址、物理地址、读写权限、标记位(对应的代码和数据是否被加载到内存中)
https://i-blog.csdnimg.cn/direct/ef8f0e2bc802496e80b5421d51dec494.png
读写权限就可以帮助我们做检查,比方说当前是常量字符区但是你却想修改,就会被操纵系统拦截,该非法请求就不会被发送到物理内存。
标记位就是帮助们判断进程的代码和数据是否被加载到内存中,因为我们知道我们的进程对应的代码和数据是有可能处于挂起状态的(还没加载到内存)。
惰性加载:实在就是需要多少就加载多少。操纵系统对大文件是可以实现分批加载,也就是说当前的进程可能只有PCB在内存中,但是代码和数据可能还没立刻加载进来。
缺页中断:在执行进程的时候如果发现标记位表现当前代码和数据没有加载起来,就会发生缺页中断,也就是临时中断这个进程,然后等代码和数据加载进来之后,再规复原来的状态继续运行。
问题:一次加载进去不是更快吗,为什么需要检测了之后才通过缺页中断加载进去??
——> 一方面是因为可能这个文件特别大,所以没办法一次加载进去,就算是可以一次加载进去,但是你用不也是一点点去用么?? 所以缺页中断办理的是初步局部性加载的问题,能够更公道的去使用内存!!
写时拷贝:数据区的数据是按道理是可写的,但是一开始权限会被设置成只读(意思就是当前父子进程共享),一旦父子进程任意一方实验做修改的时候,发现当前的数据是只读的(但是这里不做异常处理,而是转而发生写时拷贝),然后开辟一块新的物理内存,修改页表的映射
https://i-blog.csdnimg.cn/direct/0e57085c5f4f46af88b651c5751b4a20.png
4.2 进程地址空间是怎样切换的
https://i-blog.csdnimg.cn/direct/9dd629ff7ef34849b505024ccc9b2511.png
进程PCB结构体里有对应的进程地址空间指针,所以进程切换就以为这进程空间地址空间被切换,而页表会被存储在CPU的cr3寄存器中,这实在属于进程的上下文信息,在进程切换的时候会被进程带走,背面再规复过来!!
4.3 进程创建的详细过程分析
进程被创建的时候,优先加载的是PCB结构体以及内里临应的进程地址空间结构体,然后他的代码和数据可能不会立刻被加载进来。
4.4 再次理解进程具有独立性
1、在内核数据结构上是独立的
2、物理内存中加载的代码和数据,只需要再页表上去表现。虚拟地址可以一样,但是通过页表映射差异的物理地址,就可以让父子进程解耦,一旦发生了任何异常,你释放你的我释放我的。
3、通过页表的虚拟地址映射物理地址,可以随便取地址,乃至是乱序。但是虚拟地址可以将一个线性的地址呈现给进程。
五、为什么要有进程地址空间?(重点)
所以我们可以对进程进行一个再总结:
https://i-blog.csdnimg.cn/direct/d7045ecf6e194a2ba696cf3c42fdd54b.png
讲个故事1:
一个豪富翁(操纵系统)有10亿美金,而他有四个私生子,但是四个私生子(进程)都并不知道对方的存在,所以他们都认为豪富翁只有他唯一一个儿子,而豪富翁告诉他们一旦本身去世了,就把所有的产业留给他,所以每个儿子也都信了,所以豪富翁实在给每个私生子都画了一个大饼(进程地址空间)。每个人都认为本身有十亿产业。 但现实上是这些私生子要多少才会给多少(进程需要多少空间操纵系统就给多少空间)
https://i-blog.csdnimg.cn/direct/7a6e47a9305e41d1870205f36dbec023.png
结论1:让进程以统一的视角看待内存 如许我进程就不需要关心说详细应该放在物理内存的什么位置,也不需要关心当前这个物理内存是否会影响别人的数据,这些工作都由操纵系统去完成。
故事2:
你过年的时候经常有压岁钱,但是你还小所以你经常会买到一些没有用的东西,于是你的妈妈就让你把钱交给他保管,等你需要买什么的时候,他再把钱给你,比如说当你想要买个一块钱的橡皮时,你妈妈就给了你一块钱,但如果你想花100块钱买个游戏机的时候,你的妈妈就不给你买,所以这个过程实在妈妈的作用就是会阻止你做一些不太适合的事情。
结论2:增长虚拟地址空间,可以让我们访问的时候增长一个转换的过程,在这个转化的过程中我们可以对我们的寻址进行审查,所以一旦异常访问,直接拦截,该请求就不会到达物理内存,从而保护物理内存
https://i-blog.csdnimg.cn/direct/ad08d0dcab1b448b9c447dd47af62c65.png
结论3:因为有地址空间和页表的存在,将进程管理模块和内存模块进行解耦合 !
申请物理内存的哪一块?优先加载可执行程序的哪一部门??又大概页表填写到什么地方??这是有Linux的内存模块去管理的,进程并不需要关心。
结论4:实在变量名在定义的时候就已经被转化成一个个虚拟地址了,而我们之所以有a和&a,本质上是为了区分想获取的是变量的值照旧地址。
结论5:从前我们所学习的C内存管理,实在本质上是进程地址空间,而内存管理是由Linux替我们完成的,我们上层语言并不需要关心详细的细节,只需要正常去通过对应的线性地址去使用就行了。
六、下令行参数和环境变量在栈的上面
https://i-blog.csdnimg.cn/direct/651079da506e4af89cf09fab93b27297.png
https://i-blog.csdnimg.cn/direct/cfe1efc9c2534b73a75fe56d5b8579fd.png
所以环境变量和下令行参数是在栈之上的一个独立空间。 所以为什么子进程可以继承父进程的环境变量,因为子进程启动时,父进程已经把对应的环境变量信息加载进去了, 他也是地址空间的一部门,所以他必然有页表去帮助我们建立虚拟地址和物理地址的映射,而当子进程创建的时候,子进程也会将父进程虚拟地址空间当中的环境变量的相关参数也给我们建立了一份映射,所以纵然你传参数,子进程也还是能够获得父进程的环境变量信息。
https://i-blog.csdnimg.cn/direct/7334cb639a9d4671872c37bda491c9f1.png
我们目前所关注的是用户空间。
https://i-blog.csdnimg.cn/direct/036373911c1740fa8d186fbf8d33cda0.pnghttps://i-blog.csdnimg.cn/direct/57849ce11df44b10a0e4723daab3174b.jpeg
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]