【Linux】地址空间

【Linux】地址空间_第1张图片
本片博客将重点回答三个问题
什么是地址空间?
地址空间是如何设计的?
为什么要有地址空间?
程序地址空间排布图
在32位下,一个进程的地址空间,取值范围是0x0000 0000~ 0xFFFF FFFF
【Linux】地址空间_第2张图片
回答三个问题之前我们先来证明地址空间排布是按如图所布局的
各个区空间地址验证代码

#include 
#include                                                                                            
#include 

int g_unval; // 未初始化数据
int g_val = 100; // 初始化数据,一般指全局初始化数据
int main(int argc, char* argv[], char* env[]) // 命令行参数,环境变量
{
	// 代码地址打印                          
    printf("code addr: %p\n", main); 
    printf("init global addr: %p\n", &g_val);
    printf("uninit global addr: %p\n", &g_unval);
    
    // 堆区,指针变量本质是变量,也要开辟空间,不过放的内容是地址
    char *heap_mem = (char*)malloc(10);
    printf("heap addr: %p\n", heap_mem);
    
    // 栈区,函数内定义的变量都是在栈上开辟空间
    printf("stack addr: %p\n", &heap_mem); 
     
    int i = 0;
    for (i = 0; i < argc; i++)
    {	
    	// 命令行参数地址	
        printf("argv[%d]: %p\n", i, argv[i]);
    }
    int j = 0;
    for (j  = 0; env[j]; j++)
    {
        printf("env[%d]: %p\n", j, env[j]);
    }
    return 0;
 }

运行结果
【Linux】地址空间_第3张图片
堆、栈之间的两个箭头表示
栈向地址减小的方向增长
堆向地址增大的方向增长
【Linux】地址空间_第4张图片
证明方法也很简单
【Linux】地址空间_第5张图片
运行结果也证明确实是这样
我们会发现堆区之间差了20字节
我们平时申请空间,系统会多给你一些空间
多出的空间用来记录你堆的属性信息
所以平时我们free空间,只要传起始地址
剩下的系统知道要free多长的空间
【Linux】地址空间_第6张图片

我们在系统部分要记的两个口诀
1、先描述在组织
2、堆、栈相对而生

static 修饰局部变量,本质就是将该变量开辟在全局区域

所有的字面常量将来都是要映编码进代码的
在正文代码上其实有一小段是字符常量区
【Linux】地址空间_第7张图片

什么是地址空间以及是如何设计的

我们平时打印各种地址其实就是进程打印,程序运行之后打印的

在解释什么是地址空间之前,我们先来讲一个故事

有一个富豪,他有5亿元家产
他有3个私生子,彼此并不知道对方存在
3个私生子分别叫张三、李四、王五
富豪为了鼓励3个儿子
对张三说你好好念书将来5亿就是你的了
对另外两个儿子也说了同样的话

因为不知道彼此存在
对于这三个儿子,他们都认为是5亿继承人
富豪给他们每一个儿子画了一个大饼

有一天,张三对他爸说要1千买学习资料
李四说我成年了想买一辆两百万的跑车
王五说我创业需要50万
富豪都给了他们需要的钱
只要他们要钱富豪都会给
有一天张三说要1亿,富豪说要这么多干嘛
拒绝了张三,即使被拒绝了张三依旧认为自己是5亿的继承人
我们站在上帝视角知道即使富豪过世了
这三个儿子不可能都拥有5亿
他们每个儿子可以断断续续的要钱
但永远要不到5亿,却依然坚信自己以后能拥有这5亿

对应关系
富豪 ---- 操作系统
儿子 ---- 进程
富豪画的饼 ---- 地址空间

在内存中的地址空间本质是一种数据结构
将来要和一个特定的进程关联起来

以前直接访问物理内存,如果有野指针的问题
可能直接访问到其他进程
内存本身是随时可以被读写
所以在老式的程序里面野指针是会直接改了其他进程的东西
结论:直接使用物理内存不安全

现代计算机的解决方式

每个进程有自己的PCB
操作系统给每个进程一个虚拟的地址空间
通过映射机制映射到物理内存
我们可能会有疑问,最终还是会访问物理内存
万一虚拟地址是一个非法地址呢
其实映射机制有一个检查机制,万一是非法地址
可以不让你映射

【Linux】地址空间_第8张图片

虚拟地址空间究竟是什么?

每个进程都要有地址空间
就好比操作系统要给每个进程画个饼
操作系统要给每个饼做管理
在内存中的地址空间本质是一种内核数据结构
它里面至少有各个区域的划分
【Linux】地址空间_第9张图片
我们把如图结构称为地址空间

区域空间并不是死的,会有一定的变化
所谓的范围变化,本质是对start 或end 标记值 + - 特定的范围即可
【Linux】地址空间_第10张图片
所以一个地址为什么有两个值
到这里就可以回答这个问题了

刚开始创建时只有父进程
然后创建子进程,子进程会继承父进程的属性
所以子进程的页表、地址空间和父进程一样
当子进程尝试修改变量值时
因为要保证进程的独立性
操作系统会重新为子进程,开辟一份物理内存
并修改子进程页表的映射关系
但是虚拟地址并不受影响,还是一样的地址
但映射到物理内存的不同区域
看到的值便不一样
这种策略就叫作写时拷贝

【Linux】地址空间_第11张图片

为什么要存在地址空间

  1. 保护物理内存
    凡是非法的访问或者映射,
    os都会识别到,并且终止你这个进程
    因为地址空间和页表是os创建并维护的
    也就意味着凡是想使用地址空间和页表
    进行映射,也一定要在OS的监管下进行访问
  2. OS耦合度更低
    因为有地址空间的存在
    因为有页表映射的存在
    我们的物理内存就可以
    对未来的数据进行任意位置的加载
    物理内存的分配就可以和
    进程的管理互不关联
    从而使内存管理模块和进程管理模块
    完成解耦合

我们在C、C++语言上new、malloc空间时
本质是在虚拟地址空间申请的
因为有地址空间的存在,所以上层申请空间
物理内存可以甚至一个字节都不给你
当你真正访问物理地址时,才执行
内存相关算法,帮你申请内存,构建
页表映射关系,这样空间使用率为100%
以此提高整机效率

  1. 保证进程的独立性
    因为有地址空间的存在,每一个进程
    都认为自己拥有4GB的空间(32)
    并且各个区域是有序的,进而
    可以通过页表映射到不同的区域
    来实现进程的独立性,每一个进程
    不知道,也不需要知道其他进程的存在

重新理解什么是挂起?

加载的本质就是创建进程,但并不是
非得把所有程序的代码和数据加载到
内存中,并创建内核数据结构建立映射关系
在极端情况下,只有内核结构被创建
此时就叫新建状态

理论上,可以实现对程序的分批加载
既然可以分批加载,自然可以分批换出
一个进程短时间不会被执行,比如阻塞
而使进程的数据和代码被换出就叫挂起

页表不仅仅映射物理内存
磁盘位置也可以映射
所以当代码挂起时,不用把数据
刷新到磁盘里。只要把空间直接释放掉,
在页表重新填上磁盘当中代码和数据的
位置,就可以完成一次基本的挂起

扩展知识

在vim中注释
Ctrl + v 进入视图模式(V-BLOCK)
hjkl 选中需要注释代码
输入大写的i,左下角出现INSERT
输入 // ,再按esc 自动注释选中的代码
取消注释还是上面的操作
选中需要注释的代码,按d删除

✨✨✨✨✨✨✨✨
本篇博客完,感谢阅读
如有错误之处可评论指出
博主会耐心听取每条意见
✨✨✨✨✨✨✨✨

你可能感兴趣的:(Linux,开发语言)