进程与环境变量

环境变量

我们首先捋一下一个进程的启动步骤,拿 C 语言来举个例子吧,当内核执行 C 程序时,在调用 main 函数前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址—这是由链接编辑器设置的,而链接编辑器则由C编译器调用。启动例程丛内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。

一个进程的环境变量是在调用main函数之前,由启动例程帮这个进程设置好的。准确点说,一个进程的环境变量是继承其父进程的。在linux 下,进程的环境变量可根据 cat /proc/1/envrion 查看。而用户启动的进程一般继承自终端shell,而终端shell的环境变量又可以被设置,常用的命令 export 也是如此,会写入当前终端shell对应的进程的环境变量的文件中。而一个进程的信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
root@9-134-239-95:3675# pwd
/proc/3675
root@9-134-239-95:3675# ll
总用量 0
-rw-r--r-- 1 root root 0 4月 13 10:00 autogroup
-r-------- 1 root root 0 4月 13 10:00 auxv
-r--r--r-- 1 root root 0 4月 13 10:00 cgroup
--w------- 1 root root 0 4月 13 10:00 clear_refs
-r--r--r-- 1 root root 0 4月 13 10:00 cmdline
-rw-r--r-- 1 root root 0 4月 13 10:00 comm
-rw-r--r-- 1 root root 0 4月 13 10:00 coredump_filter
-r--r--r-- 1 root root 0 4月 13 10:00 cpuset
lrwxrwxrwx 1 root root 0 4月 13 10:00 cwd -> /usr/local/services/monitor_agent/bin
-r-------- 1 root root 0 4月 13 10:00 environ
lrwxrwxrwx 1 root root 0 4月 13 10:00 exe -> /usr/local/services/monitor_agent/bin/monitor_agent
dr-x------ 2 root root 0 4月 13 10:00 fd
dr-x------ 2 root root 0 4月 13 10:00 fdinfo
-r--r--r-- 1 root root 0 4月 13 10:00 hostinfo
-r-------- 1 root root 0 4月 13 10:00 io
-r--r--r-- 1 root root 0 4月 13 10:00 latency
-r--r--r-- 1 root root 0 4月 13 10:00 limits
-rw-r--r-- 1 root root 0 4月 13 10:00 loginuid
-r--r--r-- 1 root root 0 4月 13 10:00 maps
-rw------- 1 root root 0 4月 13 10:00 mem
-r--r--r-- 1 root root 0 4月 13 10:00 mountinfo
-r--r--r-- 1 root root 0 4月 13 10:00 mounts
-r-------- 1 root root 0 4月 13 10:00 mountstats
dr-xr-xr-x 7 root root 0 4月 13 10:00 net
dr-x--x--x 2 root root 0 4月 13 10:00 ns
-r--r--r-- 1 root root 0 4月 13 10:00 numa_maps
-rw-r--r-- 1 root root 0 4月 13 10:00 oom_adj
-r--r--r-- 1 root root 0 4月 13 10:00 oom_score
-rw-r--r-- 1 root root 0 4月 13 10:00 oom_score_adj
-r--r--r-- 1 root root 0 4月 13 10:00 pagemap
-r--r--r-- 1 root root 0 4月 13 10:00 personality
lrwxrwxrwx 1 root root 0 4月 13 10:00 root -> /
-rw-r--r-- 1 root root 0 4月 13 10:00 sched
-r--r--r-- 1 root root 0 4月 13 10:00 sessionid
-r--r--r-- 1 root root 0 4月 13 10:00 smaps
-r--r--r-- 1 root root 0 4月 13 10:00 stack
-r--r--r-- 1 root root 0 4月 13 10:00 stat
-r--r--r-- 1 root root 0 4月 13 10:00 statm
-r--r--r-- 1 root root 0 4月 13 10:00 status
-r--r--r-- 1 root root 0 4月 13 10:00 syscall
dr-xr-xr-x 3 root root 0 4月 13 10:00 task
-r--r--r-- 1 root root 0 4月 13 10:00 wchan
root@9-134-239-95:1327# cat environ
LANG=CPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/binNOTIFY_SOCKET=/run/systemd/notifySSH_USE_STRONG_RNG=0

而文件 envrion 存的是这个进程的环境变量。这个文件是只读的。

1. 环境变量表

环境变量表是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址。全局变量 environ 则包含来该指针数组的地址:extern char** environ; 按照惯例,环境变量是由 name=value 这样的字符串组成,大多数预定义名完全由大写字母组成,但这只是一个惯例。我们可以查看这个 environ 全局变量。

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;

extern char** environ;

int main(){
while(*environ) {
std::cout << *environ << std::endl;
environ++;
}
return 0;
}

2. 操作环境变量

linux 提供了一些操作环境变量的环境

1
2
3
4
char* getenv(char* name);
int putenv(char* str);
int setenv(const char* name, const char* value, int rewrite);
int unsetenv(const char* name);

尤其需要主要的是,如果我们希望改变或者增加新的环境变量,我们能影响到的知识当前进程及其后生成和调用的任何子进程的环境变量,但不能影响父进程的环境变量,这通常是一个shell进程。解释下,当我们在终端 export 了一个环境变量,对于已经运行了的进程是没有任何影响的,而会影响到将要在这个终端下启动运行的进程。另,环境变量一般存在于进程栈空间,但是如果有新增也有可能存在于进程的堆空间中。

一个进程修改自身环境变量的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <stdlib.h>

extern char** environ;

int main(){
char** env1 = environ;
while(*env1) {
std::cout << *env1 << std::endl;
env1++;
}
char* newEnv = (char*)"ENV_TEST=env_test";
if (0 == putenv(newEnv)) {
std::cout << std::endl << "new env" << std::endl;
char** env2 = environ;
while(*env2) {
std::cout << *env2 << std::endl;
env2++;
}
}else {
std::cout << "putenv failed" << std::endl;
}
return 0;
}