环境变量与程序地址空间
环境变量
命令行参数
main函数举例
功能:是实现程序不同子功能的方法。(类似 ls -a/-l/-b)

由上图代码和运行结果我们可以看出argv指的时命令行参数的个数,argv指的是存储命令行参数的数组。
由此我们也不难理解我们经常使用的常见命令,(ls ps top)这些命令是如何实现的了。

环境变量
简单理解
引入:看到上边可能大家会有一个小问题,就是我们实现的二进程程序和系统的二进制程序并没有本质区别,为什么我们执行程序的时候需要+./ 而执行系统命令时为什么不需要?
原因:我们要执行一个程序,必须要知道他的路径,./就是我们写的程序路径。ls不需要指明路径是因为系统中存在环境变量来帮我们找到ls对应的二进制指令。
/usr/bin/ 执行命令时系统默认先去usr/bin目录寻找
执行命令时系统默认去/usr/bin目录下寻找是因为存在 环境变量:PATH(该路径是系统默认搜索命令,系统中默认路径如下)

有关环境变量的一些内容
env;所有环境变量
echo $变量名 ; 查看某个变量内容
cd /home/ .bashrc ;环境变量最刚开始加载的地方
echo $HOME 当前用户家目录
export MYSHELL(名字)=1122233344;导入环境变量
unset 环境变量名;取消环境变量
理解环境变量(存储角度)
所谓的命令行参数表和环境变量表都存储在bash中。我们在执行系统命令时(比如ls -a -b),首先bash先拿到我们所要执行的命令ls,到系统变量PATH里边去寻找。找到后再根据后边参数去命令行参数表中去寻找。
获取环境变量的方法
方法一:main函数的第三个参数(char* env[])

方法二:getenv();根据环境变量名字获得环境变量内容。

方法三:使用全局变量environ[i]
该函数为2级指针,指向的是bash中的环境变量表,直接下表访问即可。
extern char** environ;声明之后才能使用
environ[i];
环境变量的特性
环境变量存在全局性(子进程可以继承父进程的环境变量)。
本地变量(set查看)不会被子进程继承。
[(export 导入环境变量实际上是在shell中执行的,但为什么会导入到bash父进程去呢?)
export:内建命令(让bash直接亲自执行,bash自己调用函数,或者通过系统调用完成)。]
程序地址空间
相关概念
1.一个进程一个虚拟地址空间
2.虚拟地址空间宽度一字节,表示范围1~2^32=4GB
3.[0,3GB] 用户空间 ,[3,4GB]内核空间
4.一套进程一套页表(用来做虚拟到物理地址之间的映射)
进程地址空间(虚拟地址空间)结构图

该地址显示的是程序的虚拟地址而并非真正的物理地址。下边代码可以验证:
int gval=100;
int main(){pid_t pid=fork();if(pid==0){while(1){printf("子:gval=%d,%p,%d\n",gval,&gval,getpid());sleep(1);gval++;}}else{while(1){printf("父:gval=%d,%p,%d\n",gval,&gval,getpid());sleep(1);}}return 0;
}//运行结果
/*父:gval=100,0x5617a808c010,3293642
子:gval=100,0x5617a808c010,3293643
父:gval=100,0x5617a808c010,3293642
子:gval=101,0x5617a808c010,3293643
父:gval=100,0x5617a808c010,3293642
子:gval=102,0x5617a808c010,3293643
父:gval=100,0x5617a808c010,3293642
子:gval=103,0x5617a808c010,3293643
子:gval=104,0x5617a808c010,3293643
父:gval=100,0x5617a808c010,3293642*/
//由以上运行结果可以看出,父进程变量gval一直没变,
//子进程变量一直在变,但两者地址一样,同一个地址只能有同一个变量的值,
//而上诉结果显示同一个地址有两中不同的变量,所以该地址并不是真实的物理地址。父子地址相同而变量不同的原因:

分析:一个进程一个虚拟地址空间,一套页表,上图父进程有自己的虚拟地址空间和自己的页表。创建子进程时子进程会创建新的地址空间,并继承父进程的页表,gval又是全局变量,所以两者显示地址一样,其实是各自虚拟地址一样。又因为进程之间具有独立性,所以当其中一个进程修改数据时,为了不影响另一个进程,os会自动在内存中开辟一块空间,供修改方使用。所以才有以上运行效果。这个技术也叫---写时拷贝。
理解虚拟地址空间是什么
在os中一个进程一个虚拟地址空间,每个虚拟地址空间4GB,在操作系统中os对这么多地址空间进行管理---->先描述后组织的方式。这也说明虚拟地址空间是一个数据结构(struct mm_struct)。
区域划分
我们通过上述虚拟地址结构图可以看出,整个的虚拟地址是一个mm_struct结构体,整个虚拟地址空间是通过编址组成的,并且划分了不同的区域,不同的区域只需要记录开始和结束编址地址即可完成区域划分。
区域调整
区域调整只需要指定区域start/end进行加或减即可完成调整。
可执行程序加载过程

分析:由上图可以看出,可执行程序加载时,一方面要创建mm_struct,并把代码和数据映射到该虚拟地址的代码位置,另外该可执行程序也需要把代码和数据映射到物理内存。
最后申请页表进行对应映射。
侧面看出虚拟地址数据的初始化是由加载程序的数据进行填充初始化的。
为什么存在虚拟地址空间
1.将地址从“无序”变为“有序”
数据代码变量加载到物理地址可以是随意的,通过页表一一映射可以使地址看起来比较有序。
2.地址转换过程中,可以对地址操作进行合法性判定,进而可以更好的保护物理内存。
a.野指针问题:当释放该地址后,对应页表中就不会存在该地址,当再去访问这个野指针时找不到对应的地址,系统自动中断程序。
b.const常量区不能修改:因为页表存在权限,在常量区只有可读权限,所以当用户修改常量内容时,会因为权限不够而不能修改。
c.缺页中断:当页表中的虚拟地址对应的物理地址没有被映射时,导致缺页中断,os会重新加载
磁盘中代码和数据。
3.让进程管理和内存管理进行一定程度的解耦合(如果不存在虚拟地址和页表,那么两者就是强祥相关,很难处理)。
一些相关问题
1.我们刚开始可以不加载代码和数据到物理内存,因为mm_struct创建好之后想要去访问变量/代码/数据时系统通过缺页中断动态给我们加载。
2.在创建进程时先有mm_struct,在加载代码数据到物理内存。
3.进程挂起状态就是把页表清空,物理内存的数据代码写回磁盘。(解耦优势)
vm_area_struct
在整个虚拟内存空间中,mm_struct标志的是整个虚拟地址的起始和结束位置,vm_area_struct标志的是每个区的起始和结束位置。

