东莞市南城装饰工程东莞网站建设,娱乐网站 建站软件,快餐小吃加盟方案,网站建设维护与网页设计#x1f34e;作者#xff1a;阿润菜菜 #x1f4d6;专栏#xff1a;Linux系统编程 我们在学习C语言的时候#xff0c;都学过内存区域的划分如栈、堆、代码区、数据区这些。但我们其实并不真正理解内存 — 我们之前一直说的内存是物理上的内存吗#xff1f;
前言
我们… 作者阿润菜菜 专栏Linux系统编程 我们在学习C语言的时候都学过内存区域的划分如栈、堆、代码区、数据区这些。但我们其实并不真正理解内存 — 我们之前一直说的内存是物理上的内存吗
前言
我们先看一段测试代码
#include stdio.h
#include assert.h
#include unistd.hint g_value 100; //全局变量int main()
{// fork在返回的时候父子都有了return两次id是不是pid_t类型定义的变量呢返回的本质就是写入// 谁先返回谁就让OS发生写时拷贝pid_t id fork();assert(id 0);if(id 0){//childwhile(1){printf(我是子进程, 我的id是: %d, 我的父进程是: %d, g_value: %d, g_value : %p\n,\getpid(), getppid(), g_value, g_value);sleep(1);g_value200; // 只有子进程会进行修改}}else{//fatherwhile(1){printf(我是父进程, 我的id是: %d, 我的父进程是: %d, g_value: %d, g_value : %p\n,\getpid(), getppid(), g_value, g_value);sleep(1);}}
}运行结果 我们可以注意到子进程的变量值内容发生了改变而父进程的变量值内容一直没有发生改变并且两个进程的全局变量打印出来的地址值是一样的。 那这地址到底是不是真的物理地址 当然不能是如果是同一个物理地址不可能读取同一个变量会读取到不同的数值 。
从上面结果我们可以得出 子进程对全局变量数据修改不影响父进程 - — 进程具有独立性 也就是说我们在语言层面用的地址不是物理地址。
所以我们之前说‘程序的地址空间’是不准确的那准确的说这是什么地址呢 我们一般叫虚拟地址或者线性地址 我们在用C/C语言所看到的地址全部都是虚拟地址物理地址用户一概看不到由OS统一管理。OS必须负责将 虚拟地址 转化成 物理地址 。
理解进程地址空间
通过故事引入
我们知道操作系统会帮助我们用于管理计算机硬件和软件资源.假设操作系统是个大富翁手底下有10个亿的内存大富翁同时有四个私生子四个分别从事不同的活动彼此不知道互相的存在对应进程的独立性在大富翁老去时会将自己的财产继承给私生子那么由于每个私生子彼此不知道存在大富翁让每个私生子都会以为自己可以继承10个亿的财产画的大饼是孩子就总会有给老爹要钱的时候那么为了管理自己的10个亿财产大富翁很有必要将其描述组织起来以供自己四个彼此独立的私生子使用。 那么大富翁操作系统将画的大饼先描述在组织其实就是管理进程地址空间的过程 ---- 本质就是一个内核数据结构struct mm_struct{ } 现在我们知道了地址空间就是内核数据结构 ---- 那它是怎么对应物理内存的 先来看一下地址空间是怎么划分的
代码区、数据区、堆区等这些区域如何理解
在我们小学的时候可能会遇到课桌上有一道“线”的情况是什么线三八线。那当时画三八线的本质就是区域划分 — 地址空间就是线性区域 同样在Linux内核数据结构中也存在区域划分类似下面的这种代码用来管理内存空间
struct area
{
int start;
int end;
}同时我们对线性区域进行指定start和end即可完成区域划分 ---- 类似于这样
struct area owner_1 (1,50};
struct area owner_2 (50,100};如果限定了区域那区域之间的数据是什么以一个4GB的内存为例大概是这样的
struct mm_struct //4GB
{
long code_start;
long code_end;
long init_start;
long init_end;
//.....
long stack_start;
long stack_end;
}通过上述我们可以知道地址空间区域是可以进行动态调整大小的 ---- 即更改 start或者end 同时虚拟地址是经过页表MMU集成在cpu中映射到物理地址 ---- 像是我们大学生会被学号编号进行确认 同时根据上述知识可以知道同一个变量地址相同其实是虚拟地址相同内容不同其实是被映射到了不同的物理地址 找到地址不是目的而是一种手段页表目的是该地址对应的内容
到这里还能回答原始问题子进程的mm_struct继承父进程 ---- 即虚拟地址一样 进程独立映射到不同的物理地址
深入扩展
地址空间为什么要存在 ---- 比如野指针越界问题破坏了进程独立性进程的数据会遭到破坏影响到进程运行
防止地址随意访问保护物理内存与其他进程运行 同时页表具有读写权限控制属性解释了为什么代码段只是可读的为什么有些变量不能赋值 将进程管理和内存管理进行解耦合 通过malloc本质讲解可以让进程以统一视角看待自己的代码和数据
malloc 本质
作为一款优秀的操作系统不能允许任何的浪费或者不高效的存在。 所以操作系统使用缺页中断方式来管理内存 ---- 即先在虚拟地址空间申请虚拟内存。 然后通过页表映射 在实际物理内存上开辟空间 同时不需要关心数据放在物理内存哪个位置 因为通过页表映射都能找到 重新理解地址空间 我们的程序在被编译的时候没有被加载到内存那么我们的程序内部有没有地址呢 答案是有的。源代码被编译的时候就已经按照虚拟地址的方式进行了代码和数据的编址使用ELF格式划分数据区域 所以虚拟地址这样的策略不只是影响OS我们的编译器同样遵守这样的规则 进程的代码和数据必须一直在内存中吗 答不是。OS会将暂时不用的进程代码、数据和部分进程控制块通过我们的页表技术交换至磁盘中存储。