环境变量(environment variables):系统当中用做特殊用途的系统变量。
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
子进程默认会复制拥有与父进程相同的环境变量。
PATH : 指定命令的搜索路径
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL : 当前Shell,它的值通常是/bin/bash。
echo $NAME NAME:你的环境变量名称
指令 env——显示所有环境变量
echo: 显示某个环境变量值
export: 设置一个新的环境变量(export aaaa)
env: 显示所有环境变量
unset: 清除环境变量(rm只是普通的文件操作指令,无法删除环境变量)
set: 显示本地定义的shell变量(本地变量)和环境变量
1.echo: 显示某个环境变量值
2.export : 导出环境变量
3.env : 显示所有环境变量
4.unset :删除环境变量
5.set:查看本地定义的shell变量(本地变量)和环境变量
基本指令也是程序,为什么我们的代码程序运行要带路径,而系统的指令不用带路径?
比如使用指令ls,pwd时直接使用即可,使用自己的myproc 可执行程序时(gcc -o mproc.c myproc) 需要./myproc.c
答:系统中是存在相关的环境变量,保存了程序的搜索路径的! 比如:执行 ls 这个可执行程序时,系统会在PATH中一个一个搜索,在特定路径(系统所有命令都在usr/bin路径下)下可以找到ls,就可以执行。
系统中搜索可执行程序的环境变量叫做 PATH !
把自己的程序拷贝进环境变量中,就可以直接myproc执行程序了,拷贝的过程就是安装软件,但是不建议这样做,本身我们的软件就没什么意义,会污染系统,删除=卸载。
把myproc自己的文件加入环境变量PATH中,export PATH=$PATH:路径(相当于把PATH中的路径改成PATH和myproc的路径)。
错误示范:如果直接 export PATH=路径 ,会覆盖环境变量PATH的原有路径:这里pwd还能用,ls,top什么的就不能用了
# 二、常见的环境变量
XDG_SESSION_ID=299733
TERM_PROGRAM=vscode
HOSTNAME=VM-24-7-centos
TERM=xterm-256color
SHELL=/bin/bash : 显示shell所在路径
HISTSIZE=3000 : 历史能够记录自己敲过的命令条数
SSH_CLIENT=111.18.128.241 7177 22 : ip地址
TERM_PROGRAM_VERSION=1.78.2 : 版本
USER=root/ : 用户名
VSCODE_GIT_ASKPASS_MAIN=/root/.vscode-server/bin/b3e4e68a0bc097f0ae7907b217c1119af9e03435/extensions/git/dist/askpass-main.js
LOGNAME=root
MAIL=/var/spool/mail/root
PWD=/root/new/add.ringqueue : PWD:当前用户所处路径
LANG=en_US.utf8 : 支持的编码格式,现在支持的是UTF8
VSCODE_GIT_ASKPASS_EXTRA_ARGS=
HOME=/root :代表不同用户的家目录
SHLVL=5
VSCODE_GIT_IPC_HANDLE=/run/user/0/vscode-git-d20790e3d3.sock
SSH_CONNECTION=111.18.128.241 7177 10.0.24.7 22
VSCODE_IPC_HOOK_CLI=/run/user/0/vscode-ipc-729d4afb-0a8a-48d3-848b-da318c83e93a.sock
LESSOPEN=||/usr/bin/lesspipe.sh %s
BROWSER=/root/.vscode-server/bin/b3e4e68a0bc097f0ae7907b217c1119af9e03435/bin/helpers/browser.sh
PROMPT_COMMAND=__vsc_prompt_cmd_original
VSCODE_GIT_ASKPASS_NODE=/root/.vscode-server/bin/b3e4e68a0bc097f0ae7907b217c1119af9e03435/node
GIT_ASKPASS=/root/.vscode-server/bin/b3e4e68a0bc097f0ae7907b217c1119af9e03435/extensions/git/dist/askpass.sh
XDG_RUNTIME_DIR=/run/user/0
HISTTIMEFORMAT=%F %T
COLORTERM=truecolor
OLDPWD=/root/new
_=/usr/bin/env
[root@VM-24-7-centos add.ringqueue]# echo $HOSTNAME
VM-24-7-centos
环境变量:系统当中用做特殊用途的系统变量。
命令行变量分两种:
环境变量具有全局属性:环境变量是会被子进程继承下去的! !
所谓得本地变量,本质就是在bash内部定义的变量,不会被子进程继承下去!
可以,实际是三个。
main函数的前两个参数分别是下图所示:这两个参数我们称为:命令行参数
int main(int argc,char *argv[]) { // 数组个数 数组
}
我们给main函数传递的前两个参数 argc,char* argv[] 称为 命令行参数,传递的是命令行中输入的程序名和选项!比如命令行输入了./myproc -a -b -c,argc对应就是4,argv[ ] 中传入了这4个字符串,argv[0] = “./myproc”,argv[1] = “-a”,argv[2] = “-b”,argv[3] = “-c”,argv[4] = “NULL”,指针数组以NULL结尾。
给命令行参数传程序名和选项意义是什么?我们通过实现一个命令行版的计算器来理解:
我们要实现的功能:
执行 ./myproc -a 10 20 要实现 10+20=30;
执行 ./myproc -s 10 20 要实现 10-20=-10
// makefile
myproc:myproc.c
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f myproc
// main.c
#include
#include
#include
#include
int main(int argc,char *argv[])
{
if (argc != 4)
{
//如果用户输入不对,打印使用手册,-a加法,-s(subtract)减法,-m乘法,-d除法:
printf("Usage: %s [-a|-s|-m|-d] one_data two_data\n", argv[0]);
return 0;
}
int x = atoi(argv[2]); //atoi:把字符串转为整数
int y = atoi(argv[3]);
if (strcmp("-a", argv[1]) == 0) //如果输入-a,就是加法
{
printf("%d+%d=%d\n", x, y, x + y);
}
else if (strcmp("-s", argv[1]) == 0) //如果输入-s,就是减法
{
printf("%d-%d=%d\n", x, y, x - y);
}
else if (strcmp("-m", argv[1]) == 0) //如果输入-m,就是乘法
{
printf("%d*%d=%d\n", x, y, x * y);
}
else if (strcmp("-d", argv[1]) == 0 && y != 0) //如果输入-d,就是除法
{
printf("%d/%d=%d\n", x, y, x / y);
}
else
{
//输入错误说明不会用,还是打印使用手册
printf("Usage: %s [-a|-s|-m|-d] one_data two_data\n", argv[0]);
}
return 0;
}
答:同一个程序,通过传递不同的参数,让同一个程序有不同的执行逻辑 / 执行结果。
这就解释了指令中那么多选项的由来和起作用的方式!!Linux系统中,会根据不通的选项,让不同的命令,可以有不同的表现!
这就解释了我们平时输入的指令传入了哪里!
#include
int main(int argc,char* argv[],char* env[])
{
// 存环境变量的指针数组以NULL结尾,所以到NULL时for循环结束:
for(int i=0;env[i];i++)
{
printf(“env[%d]:%s\n”,i,env[i]);
}
return 0;
}
但是如果是int fun(void) 就不可以传参。
这就解释了我们可以直接给main()函数传参的原因
#include
int main(int argc,char* argv[],char* env[])
{
// 存环境变量的指针数组以NULL结尾,所以到NULL时for循环结束:
for(int i=0;env[i];i++)
{
printf("env[%d]:%s\n",i,env[i]);
}
return 0;
}
C语言给我们提供了一个全局变量environ。
#include
#include
#include
#include
int main() {
extern char** environ;
for (int i = 0; environ[i]; i ++) {
printf("%d : %s \n",i,environ[i]);
}
return 0;
}
通过环境变量名直接获得环境变量的内容。
#include
#include
#include
#include
int main() {
char *val = getenv("PATH");
printf("%s\n",val);
return 0;
}
程序地址空间,不是内存!
程序地址空间=进程地址空间,是操作系统上的概念!
堆,栈相对而生。
堆区向地址增大方向增长
栈区向地址减少方向增长
我们一般在C函数中定义的变量,通常在栈上保存,那么先定义的一定是地址比较高的 !
如何理解static变量?
函数内定义的变量用static修饰,本质是编译器会把该变量编译进全局数据区!
函数内定义的变量用static修饰,本质是编译器会把该变量编译进全局数据区! 父子进程共享全局变量。
#include
#include
int g_val = 100;
int main() {
pid_t id = fork();
if (id == 0) {
int flag = 0;
while (true) {
printf("i am son : %d, ppid : %d, g_val : %d, &g_val : %p\n\n",getpid(),getppid(),g_val,&g_val);
sleep(1);
flag ++;
if (flag == 5) {
g_val = 200;
printf("全局数据我已经改了,请你注意查看\n");
}
}
}
else {
while(true) {
printf("i am father : %d, ppid : %d, g_val : %d, &g_val : %p\n\n",getpid(),getppid(),g_val,&g_val);
sleep(2);
}
}
return 0;
}
fork创建子进程,父子进程同时运行,若在子进程中第5秒改了全局变量g_val的值,会发现!
父子进程读取同一个变量(因为地址一样!),但是子进程修改的全局变量后,父子进程读取到的
内容却不一样! ! ! !
结论:
OS必须负责将虚拟地址转化成物理地址 !
进程地址空间:每一个进程在启动的时候,都会让操作系统给他创建一个地址空间,该地址空间就是进程地址空间
每一个进程,都会有一个自已的进程地址空间!
操作系统要不要管理这些进程地址空间呢??
先描述,在组织
进程地址空间,其实是内核的一个数据结构,struct mm_ struct (稍后看! )
举例子理解:
所谓的进程/虚拟地址空间:其实就是OS通过软件的方式,给进程提供一个软件视角,认为自己会独占系统的所有资源(内存)。
每个进程会维护一个mm_struct,虚拟地址和物理内存通过页表建立映射关系。(上学时,一个班级中,老师点名需要一张名单,这个名单就是虚拟地址空间)
定义:就是从进程的视角看到的地址空间,是进程运行时所用到的虚拟地址的集合,地址最大的作用是唯一性。
我们在语言层面遇到的地址都是虚拟地址!每个进程都有一个地址空间,都认为自己独占物理内存。
linux下:逻辑地址=虚拟地址=线性地址。
每个区域范围,都是可以有对应的编号的,在虚拟地址空间 mm_struct 中有存储各个数据区的起始地址和结束地址,也就是区域。
写时拷贝正好可以回答fork 现象,子进程修改的全局变量后,父子进程读取到的内容却不一样?
因为进程具有独立性,一个进程对被共享的数据做修改,如果影响了其他进程,不能称之为独立性,任何一方尝试写入,OS先进程数据拷贝,更改页表映射,然后让进程继续进行修改!!写时拷贝 : 操作系统自动做的!!(写时拷贝本身就是有OS的内存管理模块完成的!所以我们感知不到)。
写时拷贝:g_val会再拷贝一份,子进程中的映射关系会改变,指向新的g_val,但是g_val的虚拟地址(相对地址)还是原来的地址,和父进程的g_val虚拟地址(相对地址)一样,但是他们的物理地址不一样,100改成200时,只会改变新的g_val!
为什么要写时拷贝,创建子进程的时候,就把数据分开,不行吗?
所以最终采用写时拷贝:
当一个函数准备return,return 会被执行两次,return 的本质,就是通过寄存器将返回值写入到接受返回值的变量中,其实在return之前,你的子进程已经创建好了,准备被调度了,所以,返回的本质就是写入,谁先返回,谁就先写入父进程和子进程各自执行return!
当id=fork()的时候,谁先返回,谁就要发生写时拷贝,所以,同一个变量,会有不同的内容值,本质是因为大家的虚拟地址是一样的,但是大家对应的物理地址是不一样的! !