个人主页道尔柯南❄专栏传送门《C语言》《C》《Linux操作系统》昙花一现却等待了整个白昼蝉鸣一夏却蛰伏了好几个四季。文章目录1-环境变量1.1 环境变量的基本概念1.2 常见环境变量1.3 查看环境变量方法1.4 和环境变量相关的命令1.5 环境变量的组织方式2-程序地址空间2.1 虚拟地址2.2 进程地址空间2.3 虚拟内存管理2.4 为什么要有虚拟地址空间结尾1-环境变量1.1 环境变量的基本概念环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。环境变量通常具有某些特殊用途还有在系统当中通常具有全局特性。如我们在编写C/C代码的时候在链接的时候从来不知道我们的所链接的动态静态库在哪里但是照样可以链接成功生成可执行程序原因就是有相关环境变量帮助编译器进行查找。1.2 常见环境变量PATH指定命令的搜索路径HOME指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)SHELL当前Shell,它的值通常是/bin/bash。1.3 查看环境变量方法echo $NAME//NAME:你的环境变量名称1.4 和环境变量相关的命令echo: 显示某个环境变量值export: 设置一个新的环境变量env: 显示所有环境变量unset: 清除环境变量set: 显示本地定义的shell变量和环境变量1.5 环境变量的组织方式每个程序都会收到一张环境表环境表是一个字符指针数组每个指针指向一个以‘\0’结尾的环境字符串2-程序地址空间2.1 虚拟地址我相信在学习C语言的时候大家都见过下面这个图------程序地址空间但其实它的真实名字是进程地址空间或虚拟地址空间。上图中有4GB,则地址个数为32位机器—2^32个地址64位机器—2^64个地址在此我先提出一个问题程序地址空间是物理内存吗答案不是为啥呢请往下看#includestdio.h#includeunistd.h#includestdlib.hintg_val0;intmain(){pid_tidfork();if(id0){perror(fork);return0;}elseif(id0)//child{printf(child[%d]: %d : %p\n,getpid(),g_val,g_val);}else//parent{printf(parent[%d]: %d : %p\n,getpid(),g_val,g_val);}sleep(1);return0;}上述代码的输出结果是parent[2995]:0:0x80497d8child[2996]:0:0x80497d8这个结果就和我们预想的结果一样输出的变量值和地址是一模一样的因为子进程按照父进程为模板父子并没有对变量进行任何修改。可我们对代码稍加改动#includestdio.h#includeunistd.h#includestdlib.hintg_val0;intmain(){pid_tidfork();if(id0){perror(fork);return0;}elseif(id0)//child{g_val100;printf(child[%d]: %d : %p\n,getpid(),g_val,g_val);}else//parent{sleep(3);printf(parent[%d]: %d : %p\n,getpid(),g_val,g_val);}sleep(1);return0;}输出结果为parent[3046]:100:0x80497d8child[3045]:0:0x80497d8我们发现父子进程输出地址是一致的但是变量内容不一样能得出如下结论变量内容不一样,所以父子进程输出的变量绝对不是同一个变量但地址值是一样的说明该地址绝对不是物理地址在Linux地址下这种地址叫做虚拟地址我们在用C/C语言所看到的地址全部都是虚拟地址物理地址用户一概看不到由OS统一管理OS必须负责将虚拟地址转化成物理地址。2.2 进程地址空间上面的图就说明同一个变量地址相同其实是虚拟地址相同内容不同其实是被映射到了不同的物理地址2.3 虚拟内存管理描述linux下进程的地址空间的所有的信息的结构体是 mm_struct内存描述符。每个进程只有一个mm_struct结构在每个进程的 task_struct 结构中有一个指向该进程的mm_struct结构体指针。可以说mm_struct 结构是对整个用户空间的描述。每一个进程都会有自己独立的 mm_struct这样每一个进程都会有自己独立的地址空间才能互不干扰。先来看看由 task_struct 到 mm_struct进程的地址空间的分布情况那既然每一个进程都会有自己独立的 mm_struct操作系统肯定是要将这么多进程的 mm_struct 组织起来的虚拟空间的组织方式有两种①当虚拟区较少时采取单链表由 mmap 指针指向这个链表②当虚拟区间多时采取红黑树进行管理由 mm_rb 指向这棵树。linux内核使用 vm_area_struct 结构来表示一个独立的虚拟内存区域(VMA)由于每个不同质的虚拟内存区域功能和内部机制都不同因此一个进程使用多个 vm_area_struct 结构来分别表示不同类型的虚拟内存区域。上面提到的两种组织方式使用的就是 vm_area_struct 结构来连接各个VMA方便进程快速访问。2.4 为什么要有虚拟地址空间这个问题其实可以转化为如果程序直接可以操作物理内存会造成什么问题在早期的计算机中要运行一个程序会把这些程序全都装入内存程序都是直接运行在内存上的也就是说程序中访问的内存地址都是实际的物理内存地址。当计算机同时运行多个程序时必须保证这些程序用到的内存总量要小于计算机实际物理内存的大小。那当程序同时运行多个程序时操作系统是如何为这些程序分配内存的呢例如某台计算机总的内存大小是128M现在同时运行两个程序A和BA需占用内存10MB需占用内存110M。计算机在给程序分配内存时会采取这样的方法先将内存中的前10M分配给程序A接着再从内存中剩余的118M中划分出110M分配给程序B。这种分配方法可以保证程序A和程序B都能运行但是这种简单的内存分配策略问题很多。①安全风险每个进程都可以访问任意的内存空间这也就意味着任意一个进程都能够去读写系统相关内存区域如果是一个木马病毒那么他就能随意的修改内存空间让设备直接瘫痪。②地址不确定众所周知编译完成后的程序是存放在硬盘上的当运行的时候需要将程序搬到内存当中去运行如果直接使用物理地址的话我们无法确定内存现在使用到哪里了也就是说拷贝的实际内存地址每一次运行都是不确定的比如第一次执行a.out时候内存当中一个进程都没有运行所以搬迁到内存地址是0x00000000但是第二次的时候内存已经有10个进程在运行了那执行a.out的时候内存地址就不一定了③效率低下如果直接使用物理内存的话一个进程就是作为一个整体内存块操作的如果出现物理内存不够用的时候我们一般的办法是将不常用的进程拷贝到磁盘的交换分区中好腾出内存但是如果是物理地址的话就需要将整个进程一起拷走这样在内存和磁盘之间拷贝时间太长效率较低。存在这么多问题有了虚拟地址空间和分页机制就能解决了吗当然地址空间和页表是OS创建并维护的是不是也就意味着凡是想使用地址空间和页表进行映射也一定要在OS的监管之下来进行访问也顺便保护了物理内存中的所有的合法数据包括各个进程以及内核的相关有效数据因为有地址空间的存在和页表的映射的存在我们的物理内存中可以对未来的数据进行任意位置的加载物理内存的分配和进程的管理就可以做到没有关系进程管理模块和内存管理模块就完成了解耦合。因为有地址空间的存在所以我们在C、C语言上new, malloc空间的时候其实是在地址空间上申请的物理内存可以甚至一个字节都不给你。而当你真正进行对物理地址空间访问的时候才执行内存的相关管理算法帮你申请内存构建页表映射关系延迟分配这是由操作系统自动完成用户包括进程完全0感知因为页表的映射的存在程序在物理内存中理论上就可以任意位置加载。它可以将地址空间上的虚拟地址和物理地址进行映射在进程视角所有的内存分布都可以是有序的。结尾uu们本文的内容到这里就全部结束了道尔在这里再次感谢您的阅读道尔柯南C/C Linux 底层探索者 | 一个正在努力学习的技术博主【关注】跟随我一起深耕技术领域见证每一次成长。❤️【点赞】让优质内容被更多人看见让知识传递更有力量。⭐【收藏】把核心知识点存好在需要时随时查、随时用。【评论】分享你的经验或疑问评论区一起交流避坑不要忘记给博主“一键四连”哦“今日目标达成”结语希望对学习Linux相关内容的uu有所帮助不要忘记给博主“一键四连”哦