Linux环境变量 & 进程地址空间

Posted 玄鸟轩墨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux环境变量 & 进程地址空间相关的知识,希望对你有一定的参考价值。


写在前面

这个博客主要谈一下环境变量和程序地址空间,其中程序地址空间可能有点不好理解,但是这个可以帮助我们解决前面我们遗留的一些问题,以后我们几乎都要和程序地址空间打交道,很重要.当然,前面的环境变量也解决了我们的指令问题.

环境变量

在谈这个之前,我们先来看一个例子,引出这个话题.

#include <stdio.h>

int main()

printf("我仅仅是一个 main 函数\\n");
return 0;

Linux环境变量

首先我不疑惑结果,这里我就想问一件事,为何我们执行这个程序需要**./**,这一点才是我感觉到不一样的.当然我们平常是这样做的,可能觉得是理所当然,那么我是知道的Linux是用C语言写的,一一些指令的本质就是函数.这一对比就出来,我们的ls就不需要指定路径,为何我们自己写的程序就需要路径.

我们先来看看如果使用路径会怎样,这样我们执行不了.

Linux环境变量

这里面就涉及到环境变量的知道知识点;了,在Windows环境下,我们也是可以查看自己电脑的环境变量的.我们这里主要就是看看.

Linux环境变量

env 指令

env是查看系统环境变量的指令,可以帮助我们查看系统所有的环境变量.

[bit@Qkj 08_13]$ env

Linux环境变量

你会发现这些环境变量实在实在太多了,这里面我们挑几个比较常用的熟悉一下.

PATH

PATH 指定命令的搜索路径 说人话就是我们把一些指令的地址放在PATH种,这个环境变量是我们今天要重点谈的,里面有些可以解决上面我们的疑问.

我们先来看看如何查看PATH.

第一种方法是使用grep指令,不过这个指令得到的不太简洁.

[bit@Qkj 08_13]$ env | grep PATH

Linux环境变量

第二种就是通过echo\\指令来获得,这个倒是很舒服,不过也有要注意的地方.

[bit@Qkj 08_13]$ echo $PATH   # 注意  一定要加$

Linux环境变量

如果上面我们不加和PATH分离都不会得到这个结果,他只会它们当作一个字符串直接打印出来.

Linux环境变量

PATH是什么

前面我们已经谈过的,PATH可以看做很多串路径的集合,其中一个:作为一个分隔符.

Linux环境变量

其中我们主要关注的是/usr/bin目录,这个目录放在我们Linux种几乎所有的指令.这里我截出来几个让大家看看.

[bit@Qkj 08_13]$ ls

Linux环境变量

到这里我们就明白了,如果我们想要执行某一条指令,操作系统会去PATH里面保存的路径中寻找,找到了就执行,找不到就会出现警告,我们 之前学的which指令的本质也是去PATH保存的路径中寻找.

Linux环境变量

修改PATH

我们希望可以把自己写的程序可以直接运行,不用在指明什么路径。这里我们存在两种方法,这里我推荐第二种。

  • 把可执行程序直接拷到 /usr/bin/目录
  • 修改PATH

我们先来看第一种方法,这里我们需要超级用户权限

#include <stdio.h>

int main()

printf("我仅仅是一个 main 函数\\n");
return 0;
[root@Qkj 08_14]# cp mybin /usr/bin/

Linux环境变量

但是这种方法有一个弊端,就是你这个程序已经是不变的了,如果你要改变,还要再次拷贝才可,我们不建议这么用.

#include <stdio.h>

int main()

printf("我仅仅是一个 main 函数\\n");
printf("我仅仅是一个 main 函数\\n");
return 0;

Linux环境变量

第二种方法就比较简单了,我们可以直接修改PATH的值,这样的话就可以知道找到我们对应的程序了

Linux环境变量

如果我们要是直接修改PATH,你就会发现我们一些指令不能用了,因外OS在PATH路径中找不到我们用的指令.不过也不用担心,我们退出下用户再次进入就可以了,这也是因为OS会再次给PATH赋值.

Linux环境变量

所以一般情况下我们都是给PATH加上路径,很少删除路径的,这里我们也是加上路径.

[bit@Qkj 08_14]$ export PATH=$PATH:/home/bit/104/2022/08_14  # export 后面再说

Linux环境变量

这样的话我们就可以直接运行自己的可执行程序了

#include <stdio.h>

int main()

printf("我仅仅是一个 main 函数\\n");
return 0;

Linux环境变量

设置环境变量

我们在想是不是自己可以设置环境变量,毕竟后面我们也可能使用到.Linux是允许我们自己设置的,不过在设置之前我们需要看一下本地变量

本地变量

我们经常拿本地变量和环境变量进行比对,这里我们先不说它们原理,只谈用法.设置本地变量的方法是很简单的.

[bit@Qkj 08_14]$ aaa=12345

上面的aaa就是一个本地变量,本地变量是不会放在环境便里面的,也就是我们env是查不出本地变量的.

Linux环境变量

设置环境变量

上面我们谈过了如何设置本地变量,这里需要看看环境变量是如何设置的,我们要用到上面的一个指令.export的作用就是设置一个新的环境变量 .

[bit@Qkj 08_14]$ export bbbb=111222333

Linux环境变量

当然我们也可以把自己设置的本地变量导到环境变量里面

[bit@Qkj 08_14]$ export

Linux环境变量

unset 指令

这是清除环境变量的指令.

[bit@Qkj 08_14]$ unset

Linux环境变量

set 指令

我们知道了env是不不能产看本地变量的,这里的set却可以查看它们两个.set显示本地定义的shell变量和环境变量.我们这里只演示查看本地变量的方法.

[bit@Qkj 08_14]$ set | grep

Linux环境变量

常见的环境变量

下面我们看一下比较常见的一些环境变量,有助于理解Linux的指令.

HOSTNAME

产看用户的主机名称.

[bit@Qkj 08_14]$ echo $HOSTNAME

Linux环境变量

HOME

查看家目录,这里我们用root用户和普通用户做对比,你一看就会明白了为何cd ~会自动跳到自己的家目录.

[bit@Qkj 08_14]$ echo $HOME
[root@Qkj 08_14]# echo $HOME

Linux环境变量

PWD

这个环境变量实时记录当前位置的绝对路径,和我们之前的pwd命令是有联系的,可以认为pwd就是取出了这个环境变量的值.

[bit@Qkj 08_14]$ echo $PWD

Linux环境变量

后面还有一点,这里我就不演示了,直接出截图吧.

Linux环境变量

通过代码如何获取环境变量

这个也算是我们今天的主要内容吧,不过他衍生出来的知识才是比较重要的.

main函数可以带参数吗

我们心里想这不是废话吗,我们写了这么长时间的main函数,是一次都没有带过参数,肯定也是不能带参数的.如果你要是这么认为,那么你的C语言只能算是掌握阶段,但是一些边边角角的知识还是没有掌握的,这里我告诉大家,main函数不仅能带参数,而且还能带两个(目前结论).我们测试一下你就知道了.

#include <stdio.h>
int main(int argc, char *argv[])

return 0;

Linux环境变量

我们先来看看这两个参数是什么,这样才有利于我们理解后面的知识.

#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;

Linux环境变量

这里面我来解释一下,这两个参数究竟是什么.

Linux环境变量

倒着里你就明白了这两个参数的含义,这里面的字符串可以看作用空格隔开的,其中./可执行程序也算是一个字符串.

Linux环境变量

指令的选项

这里我们就可以简单的明白一些东西了,我们前面说过大多数的指令都是函数,这里的指令的选项和我们现在所看到是多么的像.我们也可以简单的理解这些指令的函数也是这么实现的.

可以给无参的函数传参吗

这也是一个问题,我们在之前些=]写main函数时从来没有写过参数,这里打破了我们的认值.那么这还有一个打破认值的知识,我们可以给无参的函数传入参数吗,这里是可以的,反正我们函数不接受就可以了.

#include <stdio.h>
void func()

printf("func()\\n");


int main(int argc, char *argv[])

func(1,2,3);
return 0;

Linux环境变量

通过代码如何获取环境变量

这里我们要谈一下如何通过代码来获取环境变量,这里一共有三种]方法.

  • 命令行第三个参数
  • 通过第三方变量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;

Linux环境变量

从这里我们就可以得到一个结论,环境变量是被传入的进程里面的,这一点是十分重要的.

通过第三方变量environ获取

这个变量是一个全局变量,它是指向我们main函数第三个参数的指针.

Linux环境变量

我们来看一下他的作用

#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;

Linux环境变量

系统调用获取

上面的两种方式获取的是全部,现在这个方法是指定哪个获取哪个.

#include <stdio.h>
#include <stdlib.h>

int main()

printf("%s\\n",getenv("PATH"));
return 0;

Linux环境变量

再析环境变量 & 本地变量

我们前面只是认识了本地变量和环境变量,但是我们还不知道它们的具体区别,这里我们演示一下。

bash

前面我们已经谈过了,在Linux中,几乎所有指令对应的进程的父进程都是bash.

int main()

while(1)

printf("hello world pid : %d,ppid : %d\\n",getpid(),getppid());
sleep(2);

return 0;

Linux环境变量

这里我们就要如果我们kill的bash,你就会发现Linux不能用了,原因就是命令行启用的进程,父进程都是bash,既然父进程没了,那么子进程又如何创建呢,我这里是直接退出来来了,不过有点系统是指令不能执行.

Linux环境变量

环境变量通常具有全局属性

这里我们先下一个结论,环境变量具有全局属性,它可以被子进程给继承,那么也就能被子进程的子进程所继承。这一点我们演示一下就可以了.

我们来解释一下,环境变量和本地变量都是存储在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;

Linux环境变量

本地变量不能被继承

这里我们就可以演示一下,本地变量是不能被子进程继承的,他只能存在bash中.

Linux环境变量

内建命令

我们这里就有一个问题了,你说本地变量不能被继承,还说了在Linux中几乎所有的进程的指令是bash的子进程,那么下面的指令为何可以执行.

[bit@Qkj 08_14]$ set | grep

Linux环境变量

要知道我们的set可是bash的子进程,你不是说子进程拿不到本地变量吗,这里为何又拿到了?这不是矛盾了吗?注意了,这里我要补充一个结论,在Linux中几乎所有的指令都是按照子进程的形式来完成的,但是仍存在一下指令是通过bash调用自己的函数来完成某些功能的,我们把这类命令称为内建命令.

地址空间

这个模块很难理解,主要分为两种,当然本质是一种,名字不同罢了.

  • 程序地址空间
  • 进程地址空间

程序地址空间

我们先来简单的,在C语言中我们是学过的C程序虚拟地址空间的,这里我们要详细的谈一下.我们在C语言中学过指针,也学过变量在内存中的存储,今天我们就要看看内存的存储的实际情况.

内存为下面几个模块,这张图叫做虚拟地址空间,我们用代码验证一下实际的存储是不是和我们想的一样,这里建议在Linux环境下验证,VS可能做了优化.这里注意一下,共享区这里不好验证,就不验证了.

Linux环境变量

#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;

Linux环境变量

堆区 & 栈区

我们都知道堆区先上增长,栈区线下增长,这里演示一下就可以了.

Linux环境变量

#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;

Linux环境变量

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;

Linux环境变量

进程地址空间

我感觉上面的程序地址空间应该叫做进程地址空间,这个概念是系统层次的概念.我们先来看一下下面的的代码,你就会明白我们为何这么说了.

#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;

Linux环境变量

他们的地址一样我不感到惊讶,毕竟上面我们说过父子进程共用同一片代码和空间,这里地址也是应该相同的,但是下面你就会感到疑惑了.

这里我们修改一下子进程里面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;

Linux环境变量

程序地址空间是内存吗

从上面的代码我们就可以知道,这里好象出现了一种矛盾.我们在C语言中学过,指针指向的数据肯定是唯一确定的,我们在同一片空间怎么会出现两个不一样的数据,除非我们中程序地址空间是假的,不是真正的内存.实际上也是如此,我们得到的地址绝对不是真实的物理地址,它是虚拟地址,在Linux中还可以称为逻辑地址或者线性地址.程序地址空间可以看作是实际内存通过一种美化手端得到的.

这里我们就疑惑了吗,既然程序地址空间不是内存,那么我们就疑惑OS为何不让我们看到真实的内存.这是由于内存只是一个硬件,它可不会主动做某件事事,只能被动的被动的进行写入和读取.

mm_struct

在Linux

以上是关于Linux环境变量 & 进程地址空间的主要内容,如果未能解决你的问题,请参考以下文章

[OS-Linux]详解Linux的进程2(进程的优先级,环境变量,程序地址空间,进程地址空间,进程调度队列)

Linux进程概念--环境变量和进程地址空间

Linux环境变量和进程地址空间

Linux环境变量和进程地址空间

Linux环境变量和进程地址空间

Linux环境变量 & 进程地址空间