环境变量_进程地址空间
环境变量
简单认识什么是环境变量
环境变量是操作系统的概念,所以无论是windos还是Linux等等,他们都有对应的环境变量。本篇博客主要针对Linux系统学习环境变量以及介绍基本概念
windos
从windos系统我们可以认识到,所谓的环境变量本质就是一对kv值,有变量名和变量值。
Linxu
环境变量是系统提供的一组value=name形式的变量,不同环境变量有不同的用户,通常具有全局属性。
常见的环境变量:
- PATH:Linux系统指定的默认搜索路径
- HOME:Linux系统指定的工作目录
PATH环境变量:
在Linux下一切皆文件,我们输入的指令也是文件。比如ls,cd,su等等。说白了他们就是一个个的可执行程序而已,我们在Linxu下也可以运行c/c++程序,不过一定要加上./,这个是为了确定当前目录。而我们输入的系统指令却不用确定目录,这是因为大部分的系统指令都在PATH环境变量进行设置,系统指令默认是在PATH环境变量里去寻找。
打印PATH环境变量
echo $PATH //打印PATH环境变量
添加环境变量
PATH=$PATH:绝对地址//添加环境变量
覆盖环境变量
PATH=绝对地址//覆盖环境变量
查看所有的环境变量
env
获取环境变量的三种方法
系统调用接口C函数
getenv()
命令行第三个参数(后面有讲解)
第三方变量environ获取
(这里要看懂,先往后看命令行参数,再往这里看)
environ本质是一个二级指针,存储环境变量表(一级指针)的地址
#include <stdio.h>int main(int argc, char *argv[])
{extern char **environ;int i = 0;for(; environ[i]; i++){printf("%s\n", environ[i]);}return 0;
}
注意environ是第三方变量,由系统初始化提供,我们使用environ变量,需要加extern关键字声明外部变量。
命令行参数
命令行参数是程序运行时通过操作系统命令行传递给程序的额外输入信息,常用于为程序指定初始配置、输入数据路径或运行模式等。命令行参数是操作系统的概念,很多语言都有该设计,比如python,c++等等
C语言的main函数其实是有参数的,在Linux系统中由BASH命令行上传参数。
便利命令行参数表
默认第一个就是./文件名,我们在BASH命令行输入的这些指令,会被拆解成字符串,由操作系统上传。
打印命令行参数
1 #include <stdio.h>
W> 2 int main(int argc,char *argv[],char *env[])3 {4 int i = 0;5 for(;argv[i];i++)6 {7 printf("argv[%d]->%s\n",i,argv[i]);8 }9 return 0;10 }
打印环境变量(命令行第三个参数获取环境变量)
env数组和命令行参数表一样,末尾自动添加宏NULL(0),继承父进程的环境变量。
1 #include <stdio.h>
W> 2 int main(int argc,char *argv[],char *env[])3 { 4 int i = 0;5 for(;env[i];i++)6 {7 printf("env[%d]->%s\n",i,env[i]);8 }9 return 0;10 }
增加环境变量
export MY_VALUE=123
//export 变量名=变量值
恢复环境变量
unset MY_VALUE
//unset 变量名
从这里就可以证明,环境变量具有全局属性。
bash本身在启动的时候就会从操作系统的配置文件获取环境变量,我们所有运行的进程都是子进程,子进程会继承父进程的环境变量。注意环境变量既然是变量,那他就是数据,数据就会发生写实拷贝。上面我们在增加了一个新的环境变量后,重新运行一个程序,打印环境变量。发现,新增环境变量也被打印。因此,环境变量是具有全局属性的。
本地变量&&内建命令
直接在命令行上定义的变量叫本地变量,本地变量属于shell,操作系统的概念。
定义格式
MY_VALUE=123
//变量名=变量值
查看本地变量值
echo $MY_VALUE
//echo $变量名
上面讲了环境变量,这里又引入了一个本地变量。那这两者有何区别呢?
本地变量不会被继承,只在本BASH有效。而环境变量是会被继承具有全局属性。
set指令
set
//查看所有的环境变量和本地变量
以下就是一些本地变量,本地变量很少会使用,了解即可
内建命令
Linux系统中的命令分为两种,一种是常规命令,创建子进程完成;另一种是内建命令,bash不创建子进程,而是由bash亲自执行,类似于bash调用自己的代码或者系统提供的函数
MY_VALUE是我定义的本地变量,本地变量只在BASH内有效,可是为啥我执行echo指令,还能获取到MY_VALUE呢?难道echo执行不会创建子进程吗?答案是:不会创建子进程。而是由BASH亲自操刀。比如典型cd命令,就是一个内建命令。如果cd命令是常规命令,执行创建子进程,那他更改的就是子进程的工作目录,可是现实是他更改父进程的工作目录。cd命令调用的是chdir接口。
1 #include <stdio.h>2 #include <unistd.h>
W> 3 int main(int argc,char *argv[],char *env[])4 {5 printf("begin\n");6 sleep(30);7 if(argc == 2)8 {9 chdir(argv[1]);10 }11 printf("end\n");12 sleep(30); 13 return 0;14 }~
上面就更改子进程的工作路径,使用chdir接口,chdir的头文件是<unistd.h>
进程地址空间
指针里存储的地址是真的物理地址吗?
栈区向下开辟空间,堆区向上开辟空间。
1 #include <stdio.h>2 #include <unistd.h>3 int g_val = 100;4 int main()5 {6 pid_t id = fork();7 if(id == 0)8 {9 int cnt = 5;10 while(1)11 {12 printf("i am child, pid : %d, ppid : %d, g_val : %d, &g_val : %p\n",getpid(),getppid(),g_val,&g_val);13 sleep(1);14 if(cnt) cnt--;15 else16 {17 g_val = 200;18 }19 }20 }21 else22 { 23 while(1)24 {25 printf("i am parent, pid : %d, ppid : %d, g_val : %d, &g_val : %p\n",getpid(),getppid(),g_val,&g_val);26 sleep(1);27 }28 }29 return 0;30 31 }
同一个地址,两个值
如果获取到的地址是物理地址,为什么同一块空间,值却有两个呢?答案是:获取到地址是虚拟地址。
地址空间是什么,什么是进程地址空间。我们平常指针存储的是物理地址吗?如果你没有学过虚拟地址相关的知识,现在内心肯定有一万个为什么?难不成之前学的知识都是错的吗?
实际上,我们平常获取的地址都是虚拟地址,这些虚拟地址是由一个结构体定义的大小范围。地址空间,是由地址总线排列组合形成的地址范围[0,2^32),数学上的概念。这是因为32位机器,只有32根总线,每根线有0,1两种状态,而内存中最小的存储单元是byte(字节),因此2^32byte等于4GB。所以,我们的地址空间大小是0到2^32。而这个地址空间又叫进程地址空间,因为操作系统给每一个进程单独打造了一份地址空间,地址空间依附于进程上。所以,他准确的名字就是叫进程地址空间。进程地址空间本质就是一个结构体,里面存储start,end管理线性地址,因为我们的物理地址就是线性地址,从低到高。进程地址空间和物理内存中间有一个中介叫页表,相当于一个kv结构,一个虚拟地址对应一个物理地址,当然他也可以有其他的值,比如权限。我们知道字符常量区是只读区。可是操作系统的怎么知道他是只读区呢?所以,设置了一个权限标志位,rwx。cpu里有一个寄存器叫cr3寄存器,这是x86架构的一个寄存器,存储的是页表地址,注意这里存储的就是物理地址,不是虚拟地址。页表地址就是硬件上下文。既然是上下文,就要存储在进程里。所以,PCB里有对应变量,可以访问的页表地址。
因此,我们现在可以下一个简短的进程定义,进程 = 内核数据结构(task_struct && 页表 && mm_struct) + 程序代码和数据。task_struct就是描述进程对象的结构体,mm_struct就是每一个进程的地址空间的结构体。
相信听到这里,内心可能还是又很多个为什么。页表干啥的?为啥要这么设计呢?不着急,听我娓娓道来。
方便管理,提高效率。进程不管理直接管理他的代码和数据,而是放在物理内存中,又虚拟地址管理和维护。即使这个进程需要退出,我们也可以从页表获取对应的代码和数据,保存一份。然后退出,他再次被唤醒的时候,加载代码和数据即可,维持之前的运行状态。否则,进程又要管理程序的运行,还要管理内存。太麻烦了。
方便维护,虚拟地址可以对寻址请求审查,保护物理内存。实现解耦。物理地址就像一个听话小白,你要他干什么,他就干什么,他不会拒绝。因此,虚拟地址和页表作为中介就可以起到保护物理地址的作用,防止非法访问。
那既然都这样设计了,为啥会出现同一个虚拟地址,两个物理地址情况呢?不能搞成一一对应吗?这里情况类似于,虚拟地址只管访问数据,至于能不能访问到真实的数据,他可不管。所以,为了访问到真实的数据,物理地址就会为他开辟一份新的内存,这也就是写时拷贝。就像你的老师一样,告诉你了你要好好学习,不要谈恋爱。可是你会听吗?这件事的主动权在你身上,不在老师身上呀?老师就相当于这个虚拟地址,你就是物理地址,告诉你了我要访问数据,我要真实的,不管你用什么方法。你就说,好的,我一个家徒四壁的普通小孩子,除了学习没有其他出路,我决定听你的,给你真实数据。
操作系统这样管理进程,实现了每个进程独立运行,各不影响。操作系统给每一个进程一份进程地址空间,就相当于老板给你画饼一样。你要好好工作,公司你后都又你的一份。至于你能不能得到公司给你的那一份大饼,可就看老板了。你就是进程地址空间,老板就是物理地址,你说我要4字节内存,老板说没问题,给你,加油干年轻人;你又说我要2G内存,我要干大事,老板一个驳回拒绝了你,小伙子,公司资金不足呀。
相信看到这里,疑惑应该差不多没了。所以,之前学的知识,不能说他是错,只能说是之前没办法和你讲这部分知识。如果刚开始都这样讲,那我估计你应该刚学c语言就放弃了。
缺页中断
一般缺页中断会发生在分批加载中,比如磁盘的IO流。
空间分布图
实际上的进程地址空间
父子进程共享代码,而数据却写时拷贝