1. 前言
由于此Linux系列文章偏向于做
顺序学习手册,所以有些内容在
一篇文章中可能不连贯,敬请谅解!
请先阅读下面的文章的最后一段 了解打印出来的地址并不是物理地址 这一事实逻辑:
本章重点:
本篇文章着重讲解进程中的虚拟地址
和物理地址的关系,了解虚拟地址的
内核本质是什么,以及页表和物理地址
的映射逻辑和写时拷贝的具体体现,
期间我们将回答三个问题:
什么是地址空间?
地址空间是如何设计的?
为什么要有地址空间?
2. 什么是程序地址空间?
在理解地址空间前,我们知道一个事实:
平时看见的地址并不是真实的地址
又叫虚拟地址,真实的地址空间
被称为物理地址(物理内存)
我想通过讲一个故事来帮助理解
到底什么是程序地址空间:
有一个富豪,它有10亿资产,由于
年轻时比较浪,所以他有四个私生子
这四个私生子并不知道彼此的存在,
私生子A是个医生,私生子B是个企业家
私生子C是个街头混混,私生子D是个学生
富豪分别对小A,B,C,D说:
(1),小A啊,你要是努力做个医生,以后我的
10亿美金都是你的了
(2),小B啊,要是你把你的公司运作的很好
以后我的10亿美金就是你的了
(3),小C啊…小D啊…
故事还没结束,有一天,A说:老爸
给我10万美金,我要买医疗器械,
富豪想了想觉得是正事,于是给了
小A十万美金,小C又说:老爸,给我
两千美金吧,我吃不起饭了,富豪一听
就把钱打过去了,所以我们知道,这四个
人都可以用10亿美金以内的钱,但是
永远用不到10亿美金!
现在列出人物和地址的对应关系:
- 富豪对应操作系统
- 10亿美金对应物理地址
- 私生子对应每一个进程
- 富豪画的饼对应地址空间
所以可以得出结论,地址空间可以
理解为操作系统给进程画的饼
它并不是真实的物理地址!
3. 程序地址空间是如何设计的?
既然富豪给每一个私生子都画了饼
所以对应每一个进程都又一个自己的
程序地址空间,这是第一个结论
然而,OS要管理这些空间,一定 要先组织,再管理!所以地址空间 是一个struct结构,**这是第二个结论**
再来看看程序地址空间的图:
程序地址空间无非就是各个
区域的结合,然而各个区域的划分
无非就是两个整数begin和end
一个在区域的开头,一个在区域的结尾这是第三个结论!
如图:
根据上面的推论,我们可以得出结论:
地址空间这个北河数据结构中(struct)
至少包含了各个区域的划分,它的概念
结构如下:
struct addr_room { int code_start;//代码区起始 int code_end;//代码区结束 int init_start;//初始化区起始 int init_end;//初始化区结束 int heap_start;//堆区起始 int heap_end;//堆区结束 ...... 其他属性 };
所谓的区域范围变化
实际上就是对start和end做加减!
4. 虚拟地址和物理地址的关联
当我们理解了什么是虚拟地址后,
现在再谈虚拟地址和物理地址的关系:
本质是一种映射关系!
现代计算机使用以下方法解决问题:
OS为每一个进程配对一个虚拟地址
空间和一张页表
,要访问物理地址时,
需要先在页表进行映射,若访问的是
非法地址,则会在页表层阻止你的访问!
所以为什么一个地址会有两个值?
现在我们就能回答这个问题了:
创建子进程时,子进程的数据和
代码和父进程共享,也就是和父进程
一样,所以此时子进程的页表和程序
地址空间和父进程一样!当子进程尝试
修改变量的值时,操作系统会重新为子进程
开辟一份物理内存,并修改子进程页表的
映射关系,此时映射到物理内存的不同
区域,但是虚拟地址是一样的!
这个过程也叫写时拷贝
5. 页表的结构以及作用
修改常量字符串时编译器会报错,
是在哪个阶段报的错呢?
const char* p = "abcd"; *p = "123";
其实页表不仅仅只有映射关系
表这一个结构,它还附带了物理
内存中每一个区域的读写权限:
很明显,常量字符串对应的物理地址
区域的权限是只可读,所以当虚拟内存
去映射物理内存时,在页表层面发现你的
访问是权限不允许的,就会在页表层驳回
你的请求
,也就是会报错!
所以页表可以起到安全评估
保护物理内存的作用!
这是页表的第一个作用
页表的第二个作用:
将无序变有序
虚拟地址在映射物理内存时
是不是随意映射的?就是说映射
到物理内存后的顺序是全乱的?
结论是当然不是!在页表层映射时
会将不同的数据类型进行划分
使得映射到物理内存后是比较
有序的一种状态!
6. 为什么要存在地址空间?
通过以下三个方面来说明
地址空间的作用:
有效的保护了物理内存
凡是非法的访问或者映射,os都
会识别到,并且终止你这个进程!
地址空间和页表都由OS创建并
进行管理,一切访问都要在OS的监管下!
使OS的耦合度更低
因为有地址空间和页表的存在,
物理内存可以不关心未来数据的
类型,可以直接对它进行加载,这样
物理内存的分配就可以和进程的管理
分开来,做到它们并没有任何关系,
所以内存管理模块和进程管理模块
就完成了解耦合的操作!
保证进程的独立性
因为有地址空间的存在,每一个进程
都认为自己拥有4GB的空间,并且各个
区域是有序的,进而可以通过页表映射
到不同的区域,来实现进程的独立性,
每一个进程不知道也不需要知道其他
进程的存在!!!
7. 总结以及拓展
关于Linux进程基础的内容就讲到
这里,下一章是全新的章节–进程控制
讲了这么久的理论了,怎么控制一个进程?
也就是创建,回收一个进程?有了前面
的基础铺垫后,后面的学习内容也就容易了
拓展阅读:
🔎 下期预告:Linux进程控制 🔍