Linux环境变量 & 进程地址空间
Posted 玄鸟轩墨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux环境变量 & 进程地址空间相关的知识,希望对你有一定的参考价值。
写在前面
这个博客主要谈一下环境变量和程序地址空间,其中程序地址空间可能有点不好理解,但是这个可以帮助我们解决前面我们遗留的一些问题,以后我们几乎都要和程序地址空间打交道,很重要.当然,前面的环境变量也解决了我们的指令问题.
环境变量
在谈这个之前,我们先来看一个例子,引出这个话题.
#include <stdio.h>
int main()
printf("我仅仅是一个 main 函数\\n");
return 0;
首先我不疑惑结果,这里我就想问一件事,为何我们执行这个程序需要**./**,这一点才是我感觉到不一样的.当然我们平常是这样做的,可能觉得是理所当然,那么我是知道的Linux是用C语言写的,一一些指令的本质就是函数.这一对比就出来,我们的ls就不需要指定路径,为何我们自己写的程序就需要路径.
我们先来看看如果使用路径会怎样,这样我们执行不了.
这里面就涉及到环境变量的知道知识点;了,在Windows环境下,我们也是可以查看自己电脑的环境变量的.我们这里主要就是看看.
env 指令
env是查看系统环境变量的指令,可以帮助我们查看系统所有的环境变量.
[bit@Qkj 08_13]$ env
你会发现这些环境变量实在实在太多了,这里面我们挑几个比较常用的熟悉一下.
PATH
PATH 指定命令的搜索路径 说人话就是我们把一些指令的地址放在PATH种,这个环境变量是我们今天要重点谈的,里面有些可以解决上面我们的疑问.
我们先来看看如何查看PATH.
第一种方法是使用grep指令,不过这个指令得到的不太简洁.
[bit@Qkj 08_13]$ env | grep PATH
第二种就是通过echo\\指令来获得,这个倒是很舒服,不过也有要注意的地方.
[bit@Qkj 08_13]$ echo $PATH # 注意 一定要加$
如果上面我们不加和PATH分离都不会得到这个结果,他只会它们当作一个字符串直接打印出来.
PATH是什么
前面我们已经谈过的,PATH可以看做很多串路径的集合,其中一个:作为一个分隔符.
其中我们主要关注的是/usr/bin目录,这个目录放在我们Linux种几乎所有的指令.这里我截出来几个让大家看看.
[bit@Qkj 08_13]$ ls
到这里我们就明白了,如果我们想要执行某一条指令,操作系统会去PATH里面保存的路径中寻找,找到了就执行,找不到就会出现警告,我们 之前学的which指令的本质也是去PATH保存的路径中寻找.
修改PATH
我们希望可以把自己写的程序可以直接运行,不用在指明什么路径。这里我们存在两种方法,这里我推荐第二种。
- 把可执行程序直接拷到 /usr/bin/目录
- 修改PATH
我们先来看第一种方法,这里我们需要超级用户权限
#include <stdio.h>
int main()
printf("我仅仅是一个 main 函数\\n");
return 0;
[root@Qkj 08_14]# cp mybin /usr/bin/
但是这种方法有一个弊端,就是你这个程序已经是不变的了,如果你要改变,还要再次拷贝才可,我们不建议这么用.
#include <stdio.h>
int main()
printf("我仅仅是一个 main 函数\\n");
printf("我仅仅是一个 main 函数\\n");
return 0;
第二种方法就比较简单了,我们可以直接修改PATH的值,这样的话就可以知道找到我们对应的程序了
如果我们要是直接修改PATH,你就会发现我们一些指令不能用了,因外OS在PATH路径中找不到我们用的指令.不过也不用担心,我们退出下用户再次进入就可以了,这也是因为OS会再次给PATH赋值.
所以一般情况下我们都是给PATH加上路径,很少删除路径的,这里我们也是加上路径.
[bit@Qkj 08_14]$ export PATH=$PATH:/home/bit/104/2022/08_14 # export 后面再说
这样的话我们就可以直接运行自己的可执行程序了
#include <stdio.h>
int main()
printf("我仅仅是一个 main 函数\\n");
return 0;
设置环境变量
我们在想是不是自己可以设置环境变量,毕竟后面我们也可能使用到.Linux是允许我们自己设置的,不过在设置之前我们需要看一下本地变量
本地变量
我们经常拿本地变量和环境变量进行比对,这里我们先不说它们原理,只谈用法.设置本地变量的方法是很简单的.
[bit@Qkj 08_14]$ aaa=12345
上面的aaa就是一个本地变量,本地变量是不会放在环境便里面的,也就是我们env是查不出本地变量的.
设置环境变量
上面我们谈过了如何设置本地变量,这里需要看看环境变量是如何设置的,我们要用到上面的一个指令.export的作用就是设置一个新的环境变量 .
[bit@Qkj 08_14]$ export bbbb=111222333
当然我们也可以把自己设置的本地变量导到环境变量里面
[bit@Qkj 08_14]$ export
unset 指令
这是清除环境变量的指令.
[bit@Qkj 08_14]$ unset
set 指令
我们知道了env是不不能产看本地变量的,这里的set却可以查看它们两个.set显示本地定义的shell变量和环境变量.我们这里只演示查看本地变量的方法.
[bit@Qkj 08_14]$ set | grep
常见的环境变量
下面我们看一下比较常见的一些环境变量,有助于理解Linux的指令.
HOSTNAME
产看用户的主机名称.
[bit@Qkj 08_14]$ echo $HOSTNAME
HOME
查看家目录,这里我们用root用户和普通用户做对比,你一看就会明白了为何cd ~会自动跳到自己的家目录.
[bit@Qkj 08_14]$ echo $HOME
[root@Qkj 08_14]# echo $HOME
PWD
这个环境变量实时记录当前位置的绝对路径,和我们之前的pwd命令是有联系的,可以认为pwd就是取出了这个环境变量的值.
[bit@Qkj 08_14]$ echo $PWD
后面还有一点,这里我就不演示了,直接出截图吧.
通过代码如何获取环境变量
这个也算是我们今天的主要内容吧,不过他衍生出来的知识才是比较重要的.
main函数可以带参数吗
我们心里想这不是废话吗,我们写了这么长时间的main函数,是一次都没有带过参数,肯定也是不能带参数的.如果你要是这么认为,那么你的C语言只能算是掌握阶段,但是一些边边角角的知识还是没有掌握的,这里我告诉大家,main函数不仅能带参数,而且还能带两个(目前结论).我们测试一下你就知道了.
#include <stdio.h>
int main(int argc, char *argv[])
return 0;
我们先来看看这两个参数是什么,这样才有利于我们理解后面的知识.
#include <stdio.h>
int main(int argc, char *argv[])
int i = 0;
for(i=0;i<argc;i++)
printf("argv[%d] : %s\\n",i,argv[i]);
return 0;
这里面我来解释一下,这两个参数究竟是什么.
倒着里你就明白了这两个参数的含义,这里面的字符串可以看作用空格隔开的,其中./可执行程序也算是一个字符串.
指令的选项
这里我们就可以简单的明白一些东西了,我们前面说过大多数的指令都是函数,这里的指令的选项和我们现在所看到是多么的像.我们也可以简单的理解这些指令的函数也是这么实现的.
可以给无参的函数传参吗
这也是一个问题,我们在之前些=]写main函数时从来没有写过参数,这里打破了我们的认值.那么这还有一个打破认值的知识,我们可以给无参的函数传入参数吗,这里是可以的,反正我们函数不接受就可以了.
#include <stdio.h>
void func()
printf("func()\\n");
int main(int argc, char *argv[])
func(1,2,3);
return 0;
通过代码如何获取环境变量
这里我们要谈一下如何通过代码来获取环境变量,这里一共有三种]方法.
- 命令行第三个参数
- 通过第三方变量environ获取
- 系统调用获取 getenv
main函数的第三个参数
是的,这里面我们更新一下自己的结论,main函数可以带入第三个参数,这第三个参数就是我们的环境变量.
#include <stdio.h>
int main(int argc, char *argv[],char*env[])
int i = 0;
for(i=0;env[i];i++)
printf("env[%d] : %s\\n",i,env[i]);
return 0;
从这里我们就可以得到一个结论,环境变量是被传入的进程里面的,这一点是十分重要的.
通过第三方变量environ获取
这个变量是一个全局变量,它是指向我们main函数第三个参数的指针.
我们来看一下他的作用
#include <stdio.h>
#include <unistd.h>
int main()
extern char** environ;
int i = 0;
for(i=0;environ[i];i++)
printf("environ``[%d] : %s\\n",i,environ[i]);
return 0;
系统调用获取
上面的两种方式获取的是全部,现在这个方法是指定哪个获取哪个.
#include <stdio.h>
#include <stdlib.h>
int main()
printf("%s\\n",getenv("PATH"));
return 0;
再析环境变量 & 本地变量
我们前面只是认识了本地变量和环境变量,但是我们还不知道它们的具体区别,这里我们演示一下。
bash
前面我们已经谈过了,在Linux中,几乎所有指令对应的进程的父进程都是bash.
int main()
while(1)
printf("hello world pid : %d,ppid : %d\\n",getpid(),getppid());
sleep(2);
return 0;
这里我们就要如果我们kill的bash,你就会发现Linux不能用了,原因就是命令行启用的进程,父进程都是bash,既然父进程没了,那么子进程又如何创建呢,我这里是直接退出来来了,不过有点系统是指令不能执行.
环境变量通常具有全局属性
这里我们先下一个结论,环境变量具有全局属性,它可以被子进程给继承,那么也就能被子进程的子进程所继承。这一点我们演示一下就可以了.
我们来解释一下,环境变量和本地变量都是存储在bash进程中,这一点我们先暂时这样理解,那么main函数所在的进程也是bash的子进程,但是确可以进程环境变量.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
while(1)
printf("hello world:%s\\n",getenv("pit_104"));
sleep(2);
return 0;
本地变量不能被继承
这里我们就可以演示一下,本地变量是不能被子进程继承的,他只能存在bash中.
内建命令
我们这里就有一个问题了,你说本地变量不能被继承,还说了在Linux中几乎所有的进程的指令是bash的子进程,那么下面的指令为何可以执行.
[bit@Qkj 08_14]$ set | grep
要知道我们的set可是bash的子进程,你不是说子进程拿不到本地变量吗,这里为何又拿到了?这不是矛盾了吗?注意了,这里我要补充一个结论,在Linux中几乎所有的指令都是按照子进程的形式来完成的,但是仍存在一下指令是通过bash调用自己的函数来完成某些功能的,我们把这类命令称为内建命令.
地址空间
这个模块很难理解,主要分为两种,当然本质是一种,名字不同罢了.
- 程序地址空间
- 进程地址空间
程序地址空间
我们先来简单的,在C语言中我们是学过的C程序虚拟地址空间的,这里我们要详细的谈一下.我们在C语言中学过指针,也学过变量在内存中的存储,今天我们就要看看内存的存储的实际情况.
内存为下面几个模块,这张图叫做虚拟地址空间,我们用代码验证一下实际的存储是不是和我们想的一样,这里建议在Linux环境下验证,VS可能做了优化.这里注意一下,共享区这里不好验证,就不验证了.
#include <stdio.h>
#include <stdlib.h>
int g_val;
int init_g_val = 1;
int main(int argc,char* argv[],char* env[])
printf("code : %p\\n",main);
printf("init data : %p\\n",&init_g_val);
printf("uninit data : %p\\n",&g_val);
char* array = (char*)malloc(10);
printf("heap area : %p\\n",array);
printf("stack area : %p\\n",&array);
printf("命令行参数 : %p\\n",argv[0]);
printf("环境变量 : %p\\n",env[0]);
free(array);
return 0;
堆区 & 栈区
我们都知道堆区先上增长,栈区线下增长,这里演示一下就可以了.
#include <stdio.h>
#include <stdlib.h>
int main()
char* arr1 = (char*)malloc(10);
char* arr2 = (char*)malloc(10);
char* arr3 = (char*)malloc(10);
char* arr4 = (char*)malloc(10);
printf("heap area : %p\\n",arr1);
printf("heap area : %p\\n",arr2);
printf("heap area : %p\\n",arr3);
printf("heap area : %p\\n",arr4);
printf("stack area : %p\\n",&arr1);
printf("stack area : %p\\n",&arr2);
printf("stack area : %p\\n",&arr3);
printf("stack area : %p\\n",&arr4);
free(arr1);
free(arr2);
free(arr3);
free(arr4);
return 0;
static修饰的局部变量
我们也知道static修饰的局部变量在生命周期被放大了,实际上这个变量是存储在全局变量区.
#include <stdio.h>
#include <stdlib.h>
int g_val;
int init_g_val = 1;
int main()
printf("code : %p\\n",main);
printf("init data : %p\\n",&init_g_val);
printf("uninit data : %p\\n",&g_val);
char* array = (char*)malloc(10);
static int a = 10;
static int b;
printf("init static : %p\\n",&a);
printf("static : %p\\n",&b);
printf("heap area : %p\\n",array);
printf("stack area : %p\\n",&array);
free(array);
return 0;
进程地址空间
我感觉上面的程序地址空间应该叫做进程地址空间,这个概念是系统层次的概念.我们先来看一下下面的的代码,你就会明白我们为何这么说了.
#include <stdio.h>
#include <unistd.h>
int main()
int val = 10;
pid_t id = fork();
if(id == 0)
// child
while(1)
printf("我是子进程 pid : %d,ppid : %d &val : %p\\n",getpid(),getppid(),&val);
sleep(1);
else
while(1)
printf("我是父进程 pid : %d,ppid : %d &val : %p\\n",getpid(),getppid(),&val);
sleep(1);
return 0;
他们的地址一样我不感到惊讶,毕竟上面我们说过父子进程共用同一片代码和空间,这里地址也是应该相同的,但是下面你就会感到疑惑了.
这里我们修改一下子进程里面val的值,你就会发现一个难以理解的东西.
int main()
int val = 10;
pid_t id = fork();
if(id == 0)
// child
while(1)
val = 20;
printf("我是子进程 val = %d &val : %p\\n",val ,&val);
sleep(1);
else
while(1)
printf("我是父进程 val = %d &val : %p\\n",val, &val);
sleep(1);
return 0;
程序地址空间是内存吗
从上面的代码我们就可以知道,这里好象出现了一种矛盾.我们在C语言中学过,指针指向的数据肯定是唯一确定的,我们在同一片空间怎么会出现两个不一样的数据,除非我们中程序地址空间是假的,不是真正的内存.实际上也是如此,我们得到的地址绝对不是真实的物理地址,它是虚拟地址,在Linux中还可以称为逻辑地址或者线性地址.程序地址空间可以看作是实际内存通过一种美化手端得到的.
这里我们就疑惑了吗,既然程序地址空间不是内存,那么我们就疑惑OS为何不让我们看到真实的内存.这是由于内存只是一个硬件,它可不会主动做某件事事,只能被动的被动的进行写入和读取.
mm_struct
在Linux
以上是关于Linux环境变量 & 进程地址空间的主要内容,如果未能解决你的问题,请参考以下文章