今天心血來潮,忽然好奇環境變數在 Linux 的 Process 中是如何儲存的。因為在 Linux 下可以透過 /proc/$pid/environ 看到程式一開始執行時的環境變數
在 C 語言中,要存取環境變數主要可以透過
- C functions: getenv, setenv, putenv, ...
- 全域變數
extern char **environ
透過全域變數 extern char **environ,可以知道環境變數的結構如下
在 Linux 下,如果想知道一個記憶體位置是位在哪個區塊,可以從 /proc/self/maps 觀看
在比對 environ, environ array, environ variables 後可以得知
程式開始執行時
- environ 左值位於執行檔中(因此固定不動)
- environ array 位於 [stack] 中
- 所有的 environ varibales 位於 [stack] 中,且所有的 environ variables 緊密排列
當加入一個新的環境變數後或設置環境變數後
- environ array 位於 [heap] 中
- 所有的 environ variables 位於 [stack] 中,除了新加入的環境變數會在 [heap] 中
觀察過後會發現利用 C functions 對環境變數的修改都不會直接修改到 [stack] 中的 environ variables。於是猜測說 /proc/$pid/environ 的內容是直接對應到 [stack] 中的那些 environ varibales。於是進行實驗
environ[0][0] = 's';
sh-5.1$ cat /proc/297884/environ | tr '\0' '\n' | head -n 1
sHELL=/bin/bash
sh-5.1$
直接修改 [stack] 中的環境變數內容果然反應到了 /proc/$pid/environ 上面
在 Linux 下面想要改變 /proc/$pid/environ 對應到的 process 記憶體區塊,可以透過 prctl 搭配 PR_SET_MM_ENV_START, PR_SET_MM_ENV_END 來進行修改
以下的程式會使用 prctl 改變環境變數對應的區域,並且輸出環境變數 "SHELL" 與 /proc/$pid/environ 的內容。注意此程式需要使用 root 來跑,否則 prctl 執行會失敗
#include <sys/prctl.h>
#include <stdio.h>
#include <stdlib.h>
int
main()
{
char buffer[4096];
size_t nLen = 0;
FILE *fp = NULL;
char envs[] = "LINUX=123";
prctl(PR_SET_MM, PR_SET_MM_ENV_START, &envs[0], 0, 0);
prctl(PR_SET_MM, PR_SET_MM_ENV_END, &envs[sizeof(envs)], 0, 0);
printf("%s\n", getenv("SHELL"));
fp = fopen("/proc/self/environ", "r");
nLen = fread(buffer, 1, sizeof(buffer), fp);
fclose(fp);
fwrite(buffer, nLen, 1, stdout);
fputc('\n', stdout);
return 0;
}
此程式的輸出為
/bin/bash
LINUX=123
可以看到對程式內部而言,環境變數沒有什麼變化,但從 /proc/$pid/environ 已經變成程式另外指定的區域了