虚拟地址空间【详解】 虚拟地址空间是什么 | 为什么要有虚拟地址空间
目录
一、什么是虚拟地址空间 / 虚拟地址空间是如何被设计的
1.先看一下linux空间分布
I.示意图:
II.验证:
2. 在已知Linux内存分布之后,我们来看一个奇怪的现象
I.代码 :
II. 输出:
III.思考,引出--虚拟内存(虚拟地址空间):
IV.虚拟内存(虚拟地址空间)
V.用 虚拟内存(虚拟地址空间) 来 解释 刚才 fork()产生的 一个变量两个值 的问题
二、为什么要有地址空间
一、什么是虚拟地址空间 / 虚拟地址空间是如何被设计的
1.先看一下linux空间分布
I.示意图:
II.验证:
myhello:myhello.cgcc -o myhello myhello.c//linux小技能://myhello:hello1.c hello2.c hello3.c hello4.c//gcc -o $@ $^//可以把 hello1.c hello2.c hello3.c hello4.c一次性全部gcc 成可执行程序
.PHNOY: clean
clean:rm hello1.c hello2.c hello3.c hello4.c
//myhello.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_unval;
int g_val = 100;int main(int argc, char* argv[], char* env[])
//int argc是命令行选项的个数,
//char *argv[]是用来存 该可执行程序的文件名和其对应的操作选项的,如ls -a -l
//char *env[]是用来接收环境变量的指针数组,因为环境变量基本是字符串,而且有多个,所以用指针数组存{// int a = 10;//字面常量const char* str = "helloworld";// 10;// 'a';printf("code addr: %p\n", main);printf("init global addr: %p\n", &g_val);printf("uninit global addr: %p\n", &g_unval);static int test = 10;char* heap_mem = (char*)malloc(10);char* heap_mem1 = (char*)malloc(10);char* heap_mem2 = (char*)malloc(10);char* heap_mem3 = (char*)malloc(10);printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)printf("heap addr: %p\n", heap_mem2); //heap_mem(0), &heap_mem(1)printf("heap addr: %p\n", heap_mem3); //heap_mem(0), &heap_mem(1)printf("test stack addr: %p\n", &test); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem2); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem3); //heap_mem(0), &heap_mem(1)printf("read only string addr: %p\n", str);for (int i = 0; i < argc; i++){printf("argv[%d]: %p\n", i, argv[i]);}for (int i = 0; env[i]; i++){printf("env[%d]: %p\n", i, env[i]);}return 0;
}
ps:
1.堆是向上增长的
2.栈是向下增长的
2. 在已知Linux内存分布之后,我们来看一个奇怪的现象
I.代码 :
//makefile
mytest:test.cgcc test.c -o mytest
.PHONY: clean
clean:rm -f mytest
//test.c1 #include <stdio.h> 2 #include <unistd.h>3 int a=100;4 int main()5 {6 pid_t pid =fork();7 if(pid<0)8 {9 sleep(1);10 while(1)11 {12 printf("error\n");13 14 }15 }16 if(pid==0)17 {18 19 while(1)20 {21 sleep(1);22 a=200;
E> 23 pintf("i am child,pid:%d,ppid:%d,&a:%p,a:%d\n",getpid(),getppid(),&a,a);24 }25 }26 if(pid>0) 27 {28 while(1)29 {30 sleep(1);31 a=500;32 printf("i am father,pid:%d,ppid:%d,&a:%p,a:%d\n",getpid(),getppid(),&a,a);33 }34 }35 return 0;36 }
II. 输出:
III.思考,引出--虚拟内存(虚拟地址空间):
疑问:为什么一个变量会有两个值呢?我们理解的物理内存上的同一个变量绝对不可能一个变量有两个值的。那么只有一个可能,这个变量并不是在我们所理解的物理内存上存储的。
答案 :是的,不仅是Linux上这个变量a,而且以往,我们所有在C/C++中所学的变量,基本都是不是存储在物理内存上的,而是存储在 虚拟内存 上。----所以刚才我们展示的Linux内存分布图,其实也是其 虚拟内存 的分布图
Linux 的虚拟内存的分布图
IV.虚拟内存(虚拟地址空间)
接下来浅浅看下图,了解 虚拟内存 大致的位置
图文解释:
1.TCB--进程结构体,负责存储进程的属性信息等;
2.struct address_room--虚拟地址空间内核结构体;
3.页表--用于映射的结构体
(以上三种是操作系统中常见的三种结构体,其中struct address_room 和 页表 共同形成一个 具有访问条件判断 的 映射)
过程:
TCB结构体在进入CPU的运行队列之前(在源程序编译阶段就已经存在了虚拟地址,因为编译阶段需要调用各种函数)就已经将对应的 虚拟地址 和 虚拟地址的的映射关系 存在了虚拟地址空间 、页表 、和 物理内存 中,形成了对应的映射,并且这种映射 是有访问的条件 判断的。每当要合法的访问某个变量时,系统都会去虚拟内存里找对应的映射关系去访问物理内存。
V.用 虚拟内存(虚拟地址空间) 来 解释 刚才 fork()产生的 一个变量两个值 的问题
需知
我们要知道的是,在操作系统中, 页表 和 虚拟地址空间 是不止一对的,因为进程很多,每个进程都可以有 页表 和虚拟地址空间。
初步解释
所以刚才的 由于fork();之后所产生的一个变量两个值的问题,其实是由于父进程和子进程都有自己的 页表 和 虚拟地址 空间的问题
在深入理解之前,我们先简单了解一下 写时拷贝
写时拷贝的作用/优点:
1.写时拷贝使得两个指向同一块空间的指针或迭代器或寄存器 得以彻底分离,保证了进程的独立性
2.是一周延时申请空间的技术,因为只有在真正需要用的时候才会去开辟空间,减少了开辟空间不使用的情况,所以--可以提高整机的使用率
深入解释
图文解释:
pid:1.因为fork() 结束之后,return 回的值给pid_t pid前,子进程的所有属性和信息都是仿照了父进程复制过去的,所以父进程中 a的虚拟地址 也和 子进程中 a的虚拟地址 是一样的,所以通过页表在 物理内存中的映射的a也是同一个;2.在fork()结束后,return回值给pid_t pid时,pid_t pid会因为写时拷贝,父进程中的pid和子进程中的pid会指向不同的物理内存,使得其对应的pid是独立的,也使得其对应的pid的值可以是不同的。
a: 同理,a在未被修改值之前,父进程和子进程的a的虚拟地址是相同的,也是指向同一块物理内存的;a在被修改值之后,父进程和子进程之后的a的虚拟地址虽然相同,但是子进程中的a已经因为写时拷贝的原因,重新开辟了一个空间了,使得父进程中和父进程中的a是独立的不同的两个a,所以其两个a的值也可以是不同的。
写时拷贝之后
OK,通过以上的 图解 和 例子,我们已经对虚拟地址空间有了较深的理解,
那么虚拟地址空间到底是什么呢?总结:是一种看待内存的方法,是一种数据结构。
那么虚拟地址空间是如何设计的呢? 总结: 将物理地址 在 虚拟地址空间中 描述起来,再用虚拟地址空间和页表来管理。
二、为什么要有地址空间
a. 当进程 非法的 访问或者映射, OS都会识别到并且终止该进程。
详解:比如所有的进程崩溃,就是进程的退出 ———》实质上是操作系统管理进程,将进程杀掉,让进程退出——》对用户的非法访问进行了有效的拦截,有效的保护了物理内存——》保护了物理内存中所有合法的数据-包括 各个进程,以及内核的相关有效数据(因为地址空间和页表是 OS 创建的,所以想使用虚拟地址空间和页表进行映射,也一定要在OS的监督之下来进行访问)
b.任意位置处,都可以用虚拟地址+页表解耦用来提高其他进程之间的独立性
详解:
1.任意位置处加载:我们的物理内存中 ,可以对未来的数据进行任意位置加载。只要映射能找到,物理内存的 就可以 进程管理 做到没有关系。(额外一个小知识:malloc在开辟空间时会预留“饼干空间”来储存开辟的空间的属性,映射就是通过“饼干空间”中的开辟内存的属性来进行对应的映射的)
2.解耦:因为虚拟地址+页表可以做到任意位置处加载,使得 内存管理模块 和进 程管理模块 无关联 就完成了解耦合。(解耦合-位置关联性降低,在映射的时候不需要对应的映射区域,减少模块和模块之间的关联性。耦合度越低,维护成本越低。和模块化函数之后方便维护是一个道理)
c.地址空间中有写时拷贝,可以延迟分配,用来提高整机效率
详解:
写时拷贝,延迟分配,可以做到,以足够内存的视角来执行进程,并且在使用时再开辟,减少了开辟了不及时使用,而增加系统内耗,浪费系统资源的可能。
d.可以让进程以统一的独占整个内存(实际上不是,只是以这种视角)的视角,映射到任意不同的物理内存的位置,来完成进程独立性的实现
虚拟地址空间【详解】 虚拟地址空间是什么 | 为什么要有虚拟地址空间相关推荐
- Android 虚拟分区详解(二) 虚拟分区布局
文章目录 0. 导读 1. Android 传统 A/B 分区和动态分区布局 2. Android 虚拟分区布局 3. 虚拟分区的思考 2.1 分区只有一套,如何实现 A/B 系统特性? 2.2 部分 ...
- 国产处理器龙芯地址空间详解
国产处理器龙芯地址空间详解 原创程序猿的未来人生2020-06-13 09:04:07 MIPS基本逻辑地址空间 MIPS64架构下包含一个64位地址空间和一个32位地址空间,32位地址空间是64位地 ...
- IBM p5服务器上的虚拟 分享,IBMp5服务器系统虚拟技术详解
<IBMp5服务器系统虚拟技术详解>由会员分享,可在线阅读,更多相关<IBMp5服务器系统虚拟技术详解(10页珍藏版)>请在人人文库网上搜索. 1.IBM p5 服务器系统虚拟 ...
- Android 虚拟分区详解(三) 分区状态变化
Android Virtual A/B 系统简称 VAB,我将其称为虚拟分区. 本系列文章基于 Android R(11) 进行分析,如果没有特别说明,均基于代码版本 android-11.0.0_r ...
- 【Cinemachine】VirtualCamera虚拟相机详解(一)
摘要:VirtualCamera虚拟相机是Cinemachine系统中的核心组成部分,咱们一起来看看虚拟相机是怎么用的吧. 你好,我是跟着大智学Unity的萌新,我叫小新,这是我本周的学习总结报告哦. ...
- Android 虚拟分区详解(一) 参考资料推荐
文章目录 0. 导读 1. Android 官方 VAB 文档 1.1 公开文档 1.2 半公开文档 2. Device Mapper 文档 2.1 device mapper 文档 2.2 dmse ...
- Linux 虚拟网络设备详解之 Bridge 网桥
Bridge 是什么 同 tap/tun.veth-pair 一样,Bridge 也是一种虚拟网络设备,所以具备虚拟网络设备的所有特性,比如可以配置 IP.MAC 等. 除此之外,Bridge 还是一 ...
- linux 虚拟网络设备详解(四)
Linux 抽象网络设备简介 和磁盘设备类似,Linux 用户想要使用网络功能,不能通过直接操作硬件完成,而需要直接或间接的操作一个 Linux 为我们抽象出来的设备,既通用的 Linux 网络设备来 ...
- 龙芯2h芯片不能进入pmon_国产处理器龙芯地址空间详解
MIPS基本逻辑地址空间 MIPS64架构下包含一个64位地址空间和一个32位地址空间,32位地址空间是64位地址空间的子集.32位地址空间被分成四段,即常说的kuseg,kseg0~2如下图.其64 ...
- 设置ngxin服务器虚拟主机,详解Nginx 虚拟主机配置的三种方式(基于端口)
Nginx配置虚拟主机支持3种方式:基于IP的虚拟主机配置,基于端口的虚拟主机配置,基于域名的虚拟主机配置. 2.Nginx基于端口的虚拟主机配置 如一台服务器只有一个IP或需要通过不同的端口访问不同 ...
最新文章
- linux 内核 出错-HP 方案
- 如何在管理员页面查看知识星球活跃度和更多明细
- vue jsx 使用 自定义组件
- 【网络搜索】学习资料
- 互联网日报 | 美团门票单日入园人次破500万;蔚来用户累计换电百万次;2020诺贝尔生理学或医学奖揭晓...
- [论文阅读] Cascaded Partial Decoder for Fast and Accurate Salient Object Detection
- lazy-list C 代码详解
- java为什么使用TypeReference
- “英伦配”收视牛过本山,网管软件如何配奇兵
- linux中mysql忘记密码
- mysql 嵌套查询优化
- 什么是人工智能(AI)数据平台?
- 网络爬虫排除协议robots.txt介绍及写法详解.
- ninja ripper新版教程
- 你的收藏夹里,有哪些神奇有趣的小网站?
- 所需即所获:IDE = _plugins_ + vim
- 【Verilog】消息码转AMI码的具体Verilog代码实践以及经验分享
- python入门代码示例
- 2014中国信用卡报告
- 极客爱情 2.0.1| 从你的编程世界路过
热门文章
- 2018.3版本 CLion的激活码
- 博林格林大学计算机排名,美国传媒研究生排名简述
- Java集合(十一)TreeSet解读
- C++primer学习(13.拷贝控制)
- 如何将mp4转换成gif?教你一招实现视频转gif高清转换
- python marshal loads failed_Python模块学习:marshal 对象的序列化
- c语言减肥系统软件,给win7系统减减肥
- 步进电机的抖动和噪音从何而来 如何使步进电机完全静音
- jsp和serverlet的区别
- 《DRM 专栏》| 彻底入门 DRM 驱动