环境变量 environment varible
wiki上有简单的说到, environment varibles are a set of dynamic named values that can affect the way running processes will behave on a computer。 也就是说环境变量为进程的运行提供了一个环境,比如shell这个进程就会使用PATH这个环境变量来搜寻可执行文件。不同的进程可以以不同的方式来使用或是说来解释同一个环境变量, 这是显而易见的,因为环境变量就是存在于进程空间的一组字符串。
1 关于环境变量的一个测试程序
在unix中,每个进程都有属于自己的一组环境变量,这些环境变量,或是这个全局字符串,是子进程从父进程继承过来的,如果子进程不对环境变量做修改的话,当然是和父进程的一模一样,下面用一个测试程序来检测一下
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 extern char ** environ; 5 void show_env(); 6 int main(int argc, char *argv[]){
8 if(fork() > 0){ //fork出子进程 9 // show_env(); 10 char *path_value = malloc(200); 11 path_value = getenv("PATH"); 12 printf("int parent : PATH=%s\n", path_value); 13 }else{
//修改环境变量PATH, 并且加上一个环境变量字串 "wangyu=wangyu" 14 setenv("PATH", "wy", 1); 15 putenv("wangyu=wangyu"); 16 char *path_value = malloc(200); 17 path_value = getenv("PATH"); 18 printf("int child : PATH=%s\n", path_value); 19 // show_env(); 20 }
//打印出环境变量表的地址 21 printf("environment table address :%p\n", environ); 22 return 0; 23 }
// 打印出一个进程的所有环境变量及相关地址 24 void show_env(){ 25 char ** env = environ; 26 int i = 0; 27 while(*env){ 28 printf("index : %d\nenv address : %p\nenv value address : %p\n%s\n", i++, env, *env, *env); 29 env++; 30 } 31 }
上面这个程序fork出一个子进程,并且在子进程中修改了一个和增加了一个环境变量,另外有一个单独的函数show_env用来打印出此进程的环境表地址,所有环境变量字串,以及name=value字符串存储的地址。运行结果见下面3小节的图
对unix进程来说,环境变量表是一个指针数组存在于进程空间中,每一个指针指向一个字符串,字符串是 name=value的形式,也就是这个环境变量的名字与值。而这个指针数组的起始地址就存在于一个全局变量 environ中, 在<unistd.h>头文件中有这样的声明
/* NULL-terminated array of "NAME=VALUE" environment variables. */ extern char **__environ; #ifdef __USE_GNU extern char **environ; #endif
先不管 __USE_GNU这个宏定义。可以看到unistd.h中声明了environ。当然unix并不鼓励我们直接使用environ, 而使用相应的系统调用来读取和修改环境变量表,虽然这在程序程面是可以的。
2. 跟环境变量有关的系统调用
在上面的函数中,用到了三个跟环境变量相关的系统调用, 另外再加上一个 unsetenv.
#include<stdlib.h>
1 char * getenv(char * name); 2 int putenv(char * str); 3 int setenv(const char * name, const char * value, int rewrite); 4 int unsetenv(const char * name);
这几个函数都是在 <stdlib.h>中声明的, 也就是要算属于C标准库的函数。其中putenv的参数是整个 name=value字符串。
可能是shell对环境变量的使用比较大,所以shell有内置的命令以及专门的程序来操作环境变量。比如set, env, unset, export。但是我在使用的时候发现这几个东西,其中set,unset,export应该是shell内置的,env应该是一个单独的程序,发现有点混乱,export可以为shell增加环变量,而 unset让所有环境变量失效,这都只针对当前的shell进程,shell重新启动时,应该是从某个配置文件中读入默认的环境变量的.
3。环境变量在进程空间的位置
在最上面的那个程序中,show_env打印出了,环境表的地址,每个环境变量在表中的位置,以及字符串在内存中的位置。可以观查到的一个现象是从bash继承过来的环境表,也就是系统默认一个进程的环境表,不管是表还是那些字符串都是存储在高位地址的,实际上基本是在栈的最开始段。但是我若在这个表中增加了一项,整个表都会被移到低位地址中的堆中。上面那个程序的运行结果是
可以看到父进程的环境表在进程空间的最高位部份,而子进程的增加了一条环境变量(wangyu=wangyu)则变到了低位地址中。在我的系统中(x86_64),虚拟地址空间是48位,也就是6个字节,6个字节需要12位16进制的数,从图中的第一个地址表示所使用的位数也可以看出。我的机子中在/proc/cpuinfo中有写到这样的信息 address sizes : 36 bits physical, 48 bits virtual。
我通过运行上面程序的进程,然后在 /proc/pid/maps中查看(只能查看正在运行的进程,可以通过sleep(int)让进程暂时不退出),可以看到父进程的环境表是在栈中的(栈的顶端),而子进程的环境表是在小堆中的(small heap),父进程环境表中的指针所指向的name=value字符串也都在stack中,并且是在环境表的上面(环境表本身再高地址的地方)。而我在子进程中新增加的那个环境变量字符串“wangyu=wangyu”,则存在属于代码段(text)部份中(应该是代码区的全局数据段吧),地址比小堆(small heap)还要小
下面是上面那个进程运行时其进程空间的布局,可以看到上面的两个env表的地址别在 [stack]区和[heap]区。 关于 x86_64进程空间的内存布局以后会详细写篇文章来学习。