【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个地址
在此,我先提出一个问题:
程序地址空间是物理内存吗?答案:不是
为啥呢?请往下看
#include<stdio.h>#include<unistd.h>#include<stdlib.h>intg_val=0;intmain(){pid_tid=fork();if(id<0){perror("fork");return0;}elseif(id==0)//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这个结果就和我们预想的结果一样:输出的变量值和地址是一模一样的,因为子进程按照父进程为模板,父子并没有对变量进行任何修改。
可我们对代码稍加改动:
#include<stdio.h>#include<unistd.h>#include<stdlib.h>intg_val=0;intmain(){pid_tid=fork();if(id<0){perror("fork");return0;}elseif(id==0)//child{g_val=100;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和B,A需占用内存10M,B需占用内存110M。计算机在给程序分配内存时会采取这样的方法:先将内存中的前10M分配给程序A,接着再从内存中剩余的118M中划分出110M分配给程序B。
这种分配方法可以保证程序A和程序B都能运行,但是这种简单的内存分配策略问题很多。
①安全风险
每个进程都可以访问任意的内存空间,这也就意味着任意一个进程都能够去读写系统相关内存区域,如果是一个木马病毒,那么他就能随意的修改内存空间,让设备直接瘫痪。
②地址不确定
众所周知,编译完成后的程序是存放在硬盘上的,当运行的时候,需要将程序搬到内存当中去运行,如果直接使用物理地址的话,我们无法确定内存现在使用到哪里了,也就是说拷贝的实际内存地址每一次运行都是不确定的,比如:第一次执行a.out时候,内存当中一个进程都没有运行,所以搬迁到内存地址是0x00000000,但是第二次的时候,内存已经有10个进程在运行了,那执行a.out的时候,内存地址就不一定了
③效率低下
如果直接使用物理内存的话,一个进程就是作为一个整体(内存块)操作的,如果出现物理内存不够用的时候,我们一般的办法是将不常用的进程拷贝到磁盘的交换分区中,好腾出内存,但是如果是物理地址的话,就需要将整个进程一起拷走,这样,在内存和磁盘之间拷贝时间太长,效率较低。
存在这么多问题,有了虚拟地址空间和分页机制就能解决了吗?当然!
地址空间和页表是OS创建并维护的!是不是也就意味着,凡是想使用地址空间和页表进行映射,也一定要在OS的监管之下来进行访问!!也顺便保护了物理内存中的所有的合法数据,包括各个进程以及内核的相关有效数据!
因为有地址空间的存在和页表的映射的存在,我们的物理内存中可以对未来的数据进行任意位置的加载!物理内存的分配和进程的管理就可以做到没有关系,进程管理模块和内存管理模块就完成了解耦合。
因为有地址空间的存在,所以我们在C、C++语言上new, malloc空间的时候,其实是在地址空间上申请的,物理内存可以甚至一个字节都不给你。而当你真正进行对物理地址空间访问的时候,才执行内存的相关管理算法,帮你申请内存,构建页表映射关系(延迟分配),这是由操作系统自动完成,用户包括进程完全0感知!!
因为页表的映射的存在,程序在物理内存中理论上就可以任意位置加载。它可以将地址空间上的虚拟地址和物理地址进行映射,在进程视角所有的内存分布都可以是有序的。
结尾
uu们,本文的内容到这里就全部结束了,道尔在这里再次感谢您的阅读!
👀【关注】跟随我一起深耕技术领域,见证每一次成长。 ❤️【点赞】让优质内容被更多人看见,让知识传递更有力量。 ⭐【收藏】把核心知识点存好,在需要时随时查、随时用。 💬【评论】分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主“一键四连”哦! |
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!
