1.环境变量
1.1基本概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
环境变量彼此之间其实没有关系,一般是具有特殊用途的变量 定义变量的本质,其实是开辟空间;在运行期间我们的程序也能开辟空间 操作系统/bash是用C语言写的程序,也可以在运行中开辟空间
1.2常见的环境变量
env 可以查看系统环境变量
- PATH : 指定命令的搜索路径
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
- SHELL : 当前Shell,它的值通常是/bin/bash。
- USER:用户
1.3和环境变量相关的命令
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
set 可以查到几乎所有的变量
当 PATH 的内容被覆盖之后 XShell断开登陆重新连接后 PATH又回到了默认了
登陆系统时,环境变量从磁盘中来(脚本或某些配置文件夹) 系统启动后环境变量从磁盘->内存
所有的环境变量都是内存级的,在进程(bash)的上下文当中的 当bash进程退出,环境变量自然就没了
1.4环境变量PATH
$PATH:
当我们 ls 显示当前路径文件时,和运行自己写的代码时,有没有想过: 为什么我们的代码运行要带路径,而系统的指令不用带路径? 如果我们直接输入我们的可执行程序,会显示 bash: process: command not found
可以用echo查看PATH环境变量
为什么系统命令(程序)"不用"绝对路径就能执行? 系统在环境变量PATH默认路径找,系统命令都能找到
那么如何让自己的程序不用绝对路径就能用呢? 1.拷到默认搜索路径下(这就相当于把软件安装到系统里) 2.把程序所属路径添加到默认搜索路径下
pwd查看当前路径,把当前路径添加到环境变量中:export PATH=$PATH:路径
1.5获取环境变量
main函数有3个参数
前两个参数叫作命令行参数,第三个叫作环境变量参数。
- 第一个参数:int argc是个整型变量,表示命令行参数的个数(含第一个参数)。
- 第二个参数:char *argv[ ]是个存放字符指针的数组,每一个元素一个字符指针,指向一个字符串。这些字符串就是命令行中的每一个参数(字符串)。
- 第三个参数:char *envp[ ]也是存放字符指针的数组,数组的每一个原元素是一个指向一个环境变量(字符串)的字符指针。
cpp
#include <stdio.h>
#include <unistd.h>
#include<stdlib.h>
int main(int argc,char *argv[],char *env[])
{
for(int i=0;env[i];i++)
{
printf("-----------env[%d]->%s\n",i,env[i]);
}
return 0;
}
环境变量是当你 进入系统这个环境给你准备的变量
bash进程创建子进程时会帮我们维护两张表:命令行参数表、环境变量表(environ)
而这个表就是我们main函数的第三给参数char* env[]指向的表,环境变量可以被子进程继承下去
还可以用第三方变量environ获取
另外还能直接通过变量名获取环境变量
2.进程地址空间
2.1引入
怎么理解这张图呢?
我们来验证一下
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_unval;
int g_val = 100;
int main()
{
printf("code addr:%p\n",main);
printf("init data addr:%p\n",&g_val);//已初始
printf("uninit data addr:%p\n");
char *heap = (char*)malloc(20);
printf("heap addr:%p\n",heap);//堆
printf("stack addr:%p\n",&heap);//栈
return 0;
}
已初始化数据和未初始化数据会在进程运行期间一直存在
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
char *heap1 = (char*)malloc(20);
char *heap2 = (char*)malloc(20);
char *heap3 = (char*)malloc(20);
printf("heap1 addr:%p\n",heap1);//堆
printf("heap2 addr:%p\n",heap2);
printf("heap3 addr:%p\n",heap3);
printf("stack1 addr:%p\n",&heap1);//栈
printf("stack2 addr:%p\n",&heap2);
printf("stack3 addr:%p\n",&heap3);
return 0;
}
可以看出堆向上增长 栈向下增长
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 0;
}
else if(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);
return 0;
}
可以发现值不一样但是地址却是一样的 那么这个地址不可能是物理地址
我们所用到的所有的地址全都不是物理地址
所以刚才那个内存不是物理内存,叫进程地址空间
平时在用时访问的是地址空间的地址(虚拟内存),最终的数据是一定要存到物理内存当中的
那么地址空间怎么找到内存中的值呢?
引入一个区域划分的概念
为什么要进行区域划分?1.可以判断是否越界;2可以进行扩大或者缩小范围
区域划分的本质就是:区域内的各个地址都可以使用
可是我们的地址空间不具备对我们的代码和数据的保存能力,需要在物理内存中存放
需将地址空间上的(虚拟/线性)地址转化到物理内存中,系统给我们的进程提供一张映射表------页表
回到刚才值不一样地址一样的问题:
每个进程都有task_struct、进程地址空间、映射关系,子进程也要有,而且子进程会继承父进程的地址空间、大部分属性、映射关系,所以会指向同一地址
那么值不一样是怎么修改的呢?因为进程是有独立性的(不能直接修改,会影响父进程)
子进程的值不一样改的是映射关系,同样的虚拟地址映射关系不同,那么指向的物理内存就不同 在物理内存重新开辟一块空间,修改映射关系指向这块空间
之前我们用id进行分流(为父/子进程)怎么可能一个id既大于0又等于0,就是同一个虚拟内存映射到不同的物理内存
2.2什么是进程地址空间
进程地址空间是指每个进程在计算机内存中所占用的地址空间。地址空间是指能被访问的内存地址 范围,它由若干个连续的内存块组成。每个进程都有自己的地址空间,这意味着每个进程都有自己 的内存地址范围,不会与其他进程冲突
系统给每个进程划分了很大的一块虚拟空间,所有的进程都认为自己会独占系统资源(实际上,这只是操作系统给进程画的饼)。等到进程真正申请的资源和空间的时候系统再到物理内存分配空间给进程
既然进程地址空间是一张"饼",那么"饼"要管理吗?OS要不要堆地址空间做管理呢?自然是要的
怎么管理?先描述,再组织
所以进程地址空间的本质就是数据结构,具体到进程中就是特定数据结构的对象
2.3为什么要有地址空间+页表
地址空间:
1.进程地址空间会识别不安全的命令,并拒绝,可以阻止地址非法访问
2.通过页表将物理内存从无序变有序,让进程以统一的视角看内存(比如:正文区域,在物理内存申请的空间是一块一块的,通过页表可以把它们集中在正文区域)
3.进程地址空间的存在,可以更方便的进行进程和进程的数据代码之间的解耦合(减少模块与模块直接的关联性,以前说过开发要尽量:低耦合,高内聚)保证了进程的独立性