導入
環境変数は、開発者の皆様にとって非常に身近な存在かと思います。
例えば、プログラミングの環境構築の際、WindowsのGUIから環境変数を設定した経験がある方も多いのではないでしょうか。
npmのグローバルインストールや、シェルのプロンプトの装飾など、様々な場面で環境変数が登場します。
本記事では、環境変数とプロセスの関係性について、C言語のコードと実行結果を交えながら、仕組みをご紹介いたします。
1. プロセスとは?
プロセスとは、「実行中のプログラム」のことを指します。
プログラムはディスクからOSによってメモリ上にロードされ、CPUによって実行されます。
プロセスには親子関係があり、親プロセスが新たなプロセスを生成することで子プロセスが誕生します。
例:
$ ls
このようにシェルで ls
コマンドを実行した場合、シェルが親プロセス、ls
コマンドが子プロセスとなります。
Linuxでは、プロセスの生成に fork()
と execve()
というシステムコールが用いられます。
2. 環境変数とは?
環境変数とは、「キー=値」の形式で表現されるプロセス単位の情報です。
- 環境変数はプロセスに紐づくデータです。
- 親プロセスから子プロセスへ引き継がれます。
- 子プロセスが親の環境変数を変更することはできません。
- 実行中のプロセスは、自身の環境変数を追加・変更・削除できます。
3. C言語で環境変数を表示してみる
C言語では extern char **environ
を利用することで環境変数にアクセスできます。
以下のサンプルコードでは、現在のプロセスに設定されている全ての環境変数を表示します。
#include <stdio.h>
extern char **environ;
int main() {
char **env = environ;
while (*env) {
printf("%s\n", *env);
env++;
}
return 0;
}
実行方法
$ gcc -o test test.c
$ ./test
実行結果(例)
PATH=/usr/bin:/bin
HOME=/home/user
LANG=en_US.UTF-8
SHELL=/bin/bash
さて、このプログラムの実行は、コマンドラインからシェルに命令しました。
よって、この実行したプログラムの親プロセスはシェルなので、表示された環境変数はシェルから引き継がれたものとなります。
4. 環境変数をC言語から変更する
環境変数 environ
は変数なので、勿論上書きすることも可能です。
以下のサンプルでは、任意の環境変数を1つ設定しています。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern char **environ;
void set_env_simple(const char *key, const char *value) {
size_t key_len = strlen(key);
environ = malloc(2 * sizeof(char *));
environ[0] = malloc(key_len + strlen(value) + 2);
sprintf(environ[0], "%s=%s", key, value);
environ[1] = NULL;
}
int main() {
set_env_simple("MYVAR", "new_value");
char **env = environ;
while (*env) {
printf("%s\n", *env);
env++;
}
return 0;
}
実行結果(例)
MYVAR=new_value
5. プロセスの生成と環境変数の引き継ぎ
✅ プロセスはどう生成されるか?
Linuxでは以下の2つのシステムコールが用いられます。
-
fork()
:現在のプロセスをコピーし、新しいプロセスを生成します。 -
execve()
:現在のプロセスを別のプログラムで上書きします。
今回は fork()
に焦点を当てて解説します。
🧠 forkのイメージ図
fork()
によって、プロセスAのメモリ空間が別の場所にコピーされます
「コピーされる」とは、プロセスが使用していたメモリ空間全体が別領域に複製されることを意味します。
環境変数もこのメモリ空間に含まれているため、fork()
により 親プロセスの環境変数はそのまま子プロセスへ引き継がれます。
🧪 forkの動作確認
以下のコードは fork()
によって子プロセスを生成し、親と子のPIDの出力を行うサンプルです。
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
}
if (pid == 0) {
printf("This is the child process (PID: %d).\n", getpid());
} else {
printf("This is the parent process (PID: %d, child PID: %d).\n", getpid(), pid);
}
return 0;
}
実行結果(例)
This is the parent process (PID: 12345, child PID: 12346).
This is the child process (PID: 12346).
🧪 環境変数が引き継がれるか確認する
以下のプログラムで、親子プロセスそれぞれの環境変数を表示して確認します。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
extern char **environ;
void print_environment(const char *process_name) {
char **env = environ;
printf("\nEnvironment variables in %s process:\n", process_name);
while (*env) {
printf("%s\n", *env);
env++;
}
printf("\n");
}
int main() {
pid_t pid;
print_environment("Parent");
pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
print_environment("Child");
exit(0);
} else {
wait(NULL);
}
return 0;
}
実行結果(例)
Environment variables in Parent process:
PATH=/usr/bin:/bin
HOME=/home/user
LANG=en_US.UTF-8
SHELL=/bin/bash
Environment variables in Child process:
PATH=/usr/bin:/bin
HOME=/home/user
LANG=en_US.UTF-8
SHELL=/bin/bash
✅ まとめ
- 環境変数はプロセスごとに持つ「キー=値」形式のデータ。
-
fork()
によりプロセスを生成すると、親の環境変数はそのまま子に引き継がれる。 - C言語では
extern char **environ
を用いて環境変数を取得・操作できる。 -
fork()
の返り値により、親子プロセスを判定して処理を分岐可能。
6. おまけ:env
コマンドの実装はどうなっているか
env
コマンドは環境変数の一覧を出力しますが、内部で extern char **environ
を使っているのか?
気になったので実装を確認してみました。
GNU coreutils の env.c
に以下のような記述があります(ソースはこちら、GPL-3.0 ライセンス)。
if (! program_specified)
{
/* Print the environment and exit. */
char *const *e = environ;
while (*e)
printf ("%s%c", *e++, opt_nul_terminate_output ? '\0' : '\n');
return EXIT_SUCCESS;
}
environ をループで回して出力しておりました。