故事:
背景:有一个大富豪,家里的存款有10亿美元,他有三个私生子三个人之间彼此互不相识,只有富豪知道他们的存在。
人物介绍:
有一天富豪分别找他们谈话:
从谈话中我们可以看出,富豪分别对他三个儿子画了个大饼。而三个儿子之间也不知道彼此的存在,所以他们就相信了。
我们类比到今天到学习的内容:
那么饼要不要管理呢?
要!
例子:公司的老板给所有人都画过大饼,这天老板给张三说表现不错下次给你升经理。第二天老板又说张三干的不错,昨天我跟你说过干的好升部长没忘记吧。这时候张三懵了,不是经理吗,怎么又变部长了。
所以我们就会明白饼也是要管理的。
如何管理:
先描述,再组织。
内核中的地址空间,本质将来也一定是一种数据结构。将来一定要和特定的进程关联起来
历史计算机是直接访问物理内存的。
物理内存本身是随时可以被读写的。
而这就导致了一个问题:特别不安全。
如果进程A内的代码要跳转到进程B,这如果进程A崩溃可能也会导致进程B的崩溃,这样进程间就没有独立性了。
现代计算机提出了一下的方式
这是可能就会有人有疑问这不还是要访问物理地址码?
万一虚拟地址是非法的呢?
举故事说明:
背景:过年了张三收到了每个亲戚的压岁钱,加起来一共有五百块。妈妈不放心于是找张三商量。
于是接下来小明有需要的时候,就找妈妈拿钱。
我们可以看到妈妈是可以拒绝我们非法的请求的。
这里我们可以推广到今天讲的内容
张三:虚拟内存
妈妈:映射机制
超市:物理内存
如果地址非法,映射机制禁止映射也变向的保护了物理内存。
虚拟地址究竟是什么?
映射关系的维护究竟是谁做的?
这两个问题之后再讲解。
我们都知道进程地址空间有区域划分,区域被划分为了:代码区、堆区、栈区等等区域,那么如何理解区域划分。
故事理解:
假设小胖和小美是同桌他们共用一张桌子。
而小胖不注意卫生每天脏兮兮的,于是小美就在桌子上画了一条线,让小胖不能越界。
假设桌子长100cm
[0,50]属于小胖
[51,100]属于小美
而小美划分桌子的本质就是在做区域划分。
那么是如何用代码实现区域划分的呢?
每个进程都有地址空间,地址空间是一种内核数据结构,它里面至少要有各个区域的划分。
所以所谓的区域划分本质就是在一个范围里定义start和end,而所谓的范围变化本质就是对end和start进行加或者减一个特定的范围值。
Linux具体的区域划分:
只要做到每个进程的页表,映射的是物理内存的不同区域,就能做到进程直接不会互相干扰,保证进程独立性。
解释初识地址空间博客中的一个现象:
同一地址的g_val有不同的值。
我们可以看到当父进程和子进程分别进行映射的时候,他们各自的页表分别把它们映射到不同的物理地址,所以一个g_val才会有两个值。
而两个进程直接g_val的虚拟地址可以是一样的。
同时也发生了写时拷贝。
这也回答了我们之前学进程的时候fork有两个返回值的问题。
return会被执行两次,return的本质就是对id进行写入,此时就发生了写时拷贝,所以父子进程各自在物理内存中有属于自己的id变量空间。只不过在用户程用同一个变量(虚拟地址)来标识。
当我们程序在编译的形成可执行程序,没有被加载到内存中的时候。
请问我们程序内部有地址吗?
我们看到我们并没有运行可执行程序,就已经有地址VMA了,而VAM就是虚拟地址空间。
所以可执行程序编译的时候内部其实已经有地址了!
所以地址空间不仅仅是OS内部要遵守,其实编译器也要遵守!
即编译器编译代码的时候,就已经给我们形成了各个区域:代码区,数据区等等。并且采用和Linux内核中一样的编址方式,给每个变量,每一行代码都进行了编址。故程序在编译的时候,每一个字段早已经具有了一个虚拟地址!!!
程序内部的地址依旧用的是编译器编译好的虚拟地址。
当程序加载到内存的时候,每行代码,每个变量具有了一个物理内存地址。
当CPU读到指令的时候指令内部有地址,这个地址就是虚拟地址。
这里就有问题地址空间和业表最开始的时候数据是从哪里来的?
当数据要被加载到内存的时候,可执行程序要用编译好的虚拟地址填充进入其中。
每一个函数和变量都有地址->编译器给的
同样的也一定被加载带物理内存中。