目录
环境变量
进程地址空间
理解虚拟地址空间
进程地址空间区域划分
虚拟内存和物理内存建立联系
深刻理解虚拟地址空间
环境变量
当我们需要使用一个物品的时候,首先要先找到这个物品。同样的,当要运行一个程序(指令)时,要做的第一件事情就是先找到这个程序(指令)。
小测试,打开下图窗口,输入我想打开的程序:
新建个文件夹,将QQ快捷方式添加进去,将这个路径新增到环境变量中。
再次输入my_qq,就能从刚刚添加的路径下找到qq并运行了。
在上述的测试中,经过环境变量的配置后,当再次输入my_qq时,就可以找到该程序并运行了。
在linux下中更容易观察环境变量的作用,我们知道像pwd/cd等等这样的命令实际上就是一个个写好的程序,但是它们为什么可以直接运行,而我们自己写程序就要加上路径呢?下面试着配置环境变量,让我们的程序也能不加路径直接运行。
对比./mycode(./当前路径)执行和mycode执行。向PATH中添加了mycode后就能不加路径直接运行了。
环境变量常用命令:
echo: 显示某个环境变量值
export: 设置一个新的环境变量
env: 显示所有环境变量
unset: 清除环境变量
set: 显示本地定义的shell变量和环境变量
测试下export和env
环境变量的组织方式
命令行的三个参数介绍:
linux下的测试:
argv数组中存放的是命令后面根的一个个选项,argc变量记录的是数组中的元素个数。
char* env[]
env表中存放的是一个个环境变量,获取环境变量的三种方式有:getenv、char* env[i]、extern char**environ。
extern char** environ
getenv
进程地址空间
先来看一组代码,逻辑也很简单,fork()创建进程后,父子进程共享后面的代码。利用id不同的返回值让父子进程分流。它们都访问一个全局变量,在子进程中做下手脚,5秒后修改全局变量的值。
执行结果:
进程间要保持独立性,也就是说子进程修改了val后,不能影响父进程,父子进程得到不同的val可以接受。但是从上述的执行结果来看,父子进程访问的val地址都是一样的,从一个地址访问,得到的却是不同的结果,那么只能说明val的地址不是物理地址而是虚拟地址。
理解虚拟地址空间
站在操作系统“管理者”的角度上,手下的进程数不过来,作为老板而言,“画饼”就是必备技能,如果真的把全部的资源给了某一个进程,那么就会出现很大的麻烦,假设每个进程访问的都是物理地址,在上述案例中,进程间的独立性就被破坏了。基于这样的原因,操作系统给每个进程画了一个“饼”,这个饼就是进程地址空间,有了这个饼,你就会安心勤快的工作,当你真的有些需求的时候,我会实实在在的分配给你一些资源,但是你想让我把许诺的饼全部兑现给你,是不可能的。综上所述,每个进程都认为自己是独享系统资源的,坚信大饼早晚都是自己的。
作为操作系统,也要管理好上述提到的虚拟地址空间,先描述,在组织。地址空间的本质,就是内核的一种数据结构。每个进程都会得到一个用这个数据结构描绘的大饼,开始努力的打工。
进程地址空间区域划分
如上图所示,是在32位平台下编址的过程,每个地址由32个0或者1表示,从32个全0到全1一共有2^32个地址。用字节描述空间大小,每个地址占4个字节。这也是在32位平台下,一个指针的大小占4个字节的原因。
空间有了,接下来就是如何去合理的使用,我们知道这些空间不是胡乱用的,而是被划分车了一个个模块,那么接下来要谈论的就是区域划分:
观察上图,空间区域的划分,实际上就是用【区域起始地址,区域结束地址】来描述的。那么我们平时在栈或者堆上用空间时,就是在扩大栈或者堆区的区域。释放空间时,就是在缩小栈或者堆区,也就是在缩小对应的区域。也就是说,区域调整实际上就是在修改各个区域的end和start。
虚拟内存和物理内存建立联系
对于上述内容了解后,还差一个较为关键的环节,就是虚拟地址和物理地址是如何建立联系的,在这个过程中就要提到一个叫做页表的东西,它具体是什么这里暂时不谈,暂时知道它可以在虚拟地址和物理地址间能建某种映射即可:
回到开头谈论的问题,子进程修改了全局变量后,父子进程访问到相同的val地址,却拿到了不同的数据。此时在看这个问题,本质上每个进程都有一个操作系统给画的饼,也就是进程地址空间,当子进程没修改数据前父子进程通过页表实际上找到的是同一块物理空间,但是当子进程要对数据修改时,操作系统会先将数据进行拷贝,将数据进行分离,再更改页表的映射,使得父子进程访问不同的val。
操作系统为了保证进程间的独立性,通过页表、地址空间、让不同的进程映射到不懂的物理内存处。
小总结:
●给每个进程分配虚拟地址空间而不让它们直接访问物理内存的原因是防止一些非法操作,保证安全。●进程地址空间的存在,保证了进程间的独立性。当不同的进程访问同一个数据时,如果不发生修改这些进程就访问同一块物理空间,如果一方要修改,这时就会引发写时拷贝来保证进程间的独立性。
●每个进程地址空间的区域划分规则是一样的,编译器也遵循这样的规则。
深刻理解虚拟地址空间
理解了上述的内容,在来思考我们编写的代码是怎样运行的:实际上磁盘上的可执行程序在没有加载到内存时,内部就已经有地址了。虚拟地址空间这套规则不单单是操作系统遵守,编译器也是会遵守的。当编译器对代码进行编译时,就是按照虚拟地址空间的方式对代码和数据进行编址,这一套地址是虚拟地址。当程序加载到内存中后,还会天然的得到一套物理地址。也就是说当一个程序加载到内存上时,已将有了两套地址。物理地址确定数据和代码在内存中的位置,虚拟地址用来在程序内部跳转(函数调用之类的)。
如上图所示,cpu的几板斧:取指令、分析指令、执行指令。cpu取到的地址是程序内部的虚拟地址,这套地址的编址方式是和进程地址空间一致的,所以在通过进程地址空间找到在页表中的映射,在找到物理地址,通过物理地址锁定函数的位置,进而继续执行,反复重复上述的动作,直至该程序运行完。