unsetenv() が /proc/pid/environ を変更しないのはなぜですか?

unsetenv() が /proc/pid/environ を変更しないのはなぜですか?

私はただ見ています。この問題unsetenv()私は修正を実証するためにnoddyプログラムを書いていましたが、/proc/pid/environ驚くほど効果はありませんでした。

これが私がしたことです:

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

int main(void)
{
  printf("pid=%d\n", getpid());
  printf("sleeping 10...\n");
  sleep(10);
  printf("unsetenv result: %d\n", unsetenv("WIBBLE"));
  printf("unset; sleeping 10 more...\n");
  sleep(10);

  return 0;
}

しかし、私が走るとき

WIBBLE=hello ./test_program

WIBBLE次に、実行前後の環境で以下を確認しますunsetenv()

# before the unsetenv()
$ tr '\0' '\n' < /proc/498/environ | grep WIBBLE
WIBBLE=hello
# after the unsetenv()
$ tr '\0' '\n' < /proc/498/environ | grep WIBBLE
WIBBLE=hello

unsetenv()/proc/pid/environを修正しないのはなぜですか?

答え1

プログラムが起動すると、フォーマットの一部の文字列へのポインタ配列の形で環境を受け取りますvar=value。 Linuxではスタックの一番下にあります。下部には、すべての文字列が順番に接続されています(図を参照/proc/pid/environ)。上記には、これらの文字列へのポインタ配列(NULLで終わる)があります(libcが通常初期化するchar *envp[]ものです)。int main(int argc, char* argv[], char* envp[])environ

putenv()// setenv()unsetenv()その文字列を変更しないでください。通常、ポインタも変更しません。一部のシステムでは、これらの文字列とポインタは読み取り専用です。

char **environlibcは通常上記の最初のポインタアドレスで初期化されますが、環境(および今後の実行に使用される修正)に対する変更により、通常は新しいポインタ配列が作成され、割り当てられますenviron

environ最初に、to、to、toへのポインタは[a,b,c,d,NULL]どこにありますか?を実行した後でなければなりません。初期配列リストが読み取り専用のシステムでは、新しいリストを割り当てて保存する必要があります。次に、リストをその場で変更できます。上記の操作を実行している場合にのみリストが再割り当てされない可能性があります(たぶんポイントまで増加した可能性があります。一部のlibc実装が実際にその最適化を実行しているかどうかはわかりません)。ax=1by=2cz=3dq=5unsetenv("y")environ[a,c,d,NULL]environ[a,c,d,NULL]unsetenv()unsetenv("x")environ&envp[1]

それにもかかわらず、スタックの一番下に格納された文字列自体を何らかの方法で変更する理由はありません。unsetenv()実装が元のスタックで受信したデータを実際に変更しても、ポインタが指す文字列を削除する手間をかけずにポインタのみを修正します。 (これはGNU libcがLinuxシステム(少なくともELF実行可能ファイルの場合)で実行されているように見え、envp環境変数の数が増えない限り、ポインタのリストを内部で変更します。

次のプログラムを使用して動作を観察できます。

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char **environ;
int main(int argc, char* argv[], char* envp[]) {
  char cmd[128];
  int i;

  printf("envp: %p environ: %p\n", envp, environ);
  for (i = 0; envp[i]; i++)
    printf("  envp[%d]: %p (%s)\n", i, envp[i], envp[i]);

#define DO(x) x; puts("\nAfter " #x "\n"); \
  printf("envp: %p environ: %p\n", envp, environ); \
  for (i = 0; environ[i]; i++) \
    printf("  environ[%d]: %p (%s)\n", i, environ[i], environ[i])

  DO(unsetenv("a"));
  DO(setenv("b", "xxx", 1));
  DO(setenv("c", "xxx", 1));

  puts("\nAddress of heap and stack:");
  sprintf(cmd, "grep -e stack -e heap /proc/%u/maps", getpid());

  fflush(stdout);
  system(cmd);
}

GNU libc(klibc、musl libc、またはDietlibcと同じ、メモリを割り当てるためにヒープの代わりにmmapped匿名メモリを使用することを除く)を使用してLinuxでasを実行すると、次のようになりますenv -i a=1 x=3 ./e(インラインコメント)。

envp: 0x7ffc2e7b3238 environ: 0x7ffc2e7b3238
  envp[0]: 0x7ffc2e7b4fec (a=1)
  envp[1]: 0x7ffc2e7b4ff0 (x=3)
   # envp[1] is almost at the bottom of the stack. I lied above in that
   # there are more things like the path of the executable
   # environ initially points to the same pointer list as envp

After unsetenv("a")

envp: 0x7ffc2e7b3238 environ: 0x7ffc2e7b3238
  environ[0]: 0x7ffc2e7b4ff0 (x=3)
   # here, unsetenv has reused the envp[] list and has not allocated a new
   # list. It has shifted the pointers though and not done the optimisation
   # I mention above

After setenv("b", "xxx", 1)

envp: 0x7ffc2e7b3238 environ: 0x1bb3420
  environ[0]: 0x7ffc2e7b4ff0 (x=3)
  environ[1]: 0x1bb3440 (b=xxx)
   # a new list has been allocated on the heap. (it could have reused the
   # slot freed by unsetenv() above but didn't, Solaris' version does).
   # the "b=xxx" string is also allocated on the heap.

After setenv("c", "xxx", 1)

envp: 0x7ffc2e7b3238 environ: 0x1bb3490
  environ[0]: 0x7ffc2e7b4ff0 (x=3)
  environ[1]: 0x1bb3440 (b=xxx)
  environ[2]: 0x1bb3420 (c=xxx)

Address of heap and stack:
01bb3000-01bd4000 rw-p 00000000 00:00                              [heap]
7ffc2e794000-7ffc2e7b5000 rw-p 00000000 00:00 0                    [stack]

FreeBSD(ここでは11-rc1)ではunsetenv()。さらに、文字列自体はヒープにコピーされるため、環境を最初に変更した後にプログラムが起動したときに受信した文字列と完全に切断されますenvironenvp[]

envp: 0x7fffffffedd8 environ: 0x7fffffffedd8
  envp[0]: 0x7fffffffef74 (x=2)
  envp[1]: 0x7fffffffef78 (a=1)

After unsetenv("a")

envp: 0x7fffffffedd8 environ: 0x800e24000
  environ[0]: 0x800e15008 (x=2)

After setenv("b", "xxx", 1)

envp: 0x7fffffffedd8 environ: 0x800e24000
  environ[0]: 0x800e15018 (b=xxx)
  environ[1]: 0x800e15008 (x=2)

After setenv("c", "xxx", 1)

envp: 0x7fffffffedd8 environ: 0x800e24000
  environ[0]: 0x800e15020 (c=xxx)
  environ[1]: 0x800e15018 (b=xxx)
  environ[2]: 0x800e15008 (x=2)

Solaris(ここでは11)では、forを再利用してスロットを取得するために上記の最適化(最終的にunsetenv("a")forとして実行されます)を見ることができますが、environ++もちろん、新しい環境が変更可能な変数に挿入された場合は、新しいポインタリストを割り当てる必要があります。 ):unsetenv()bc

envp: 0xfeffef6c environ: 0xfeffef6c
  envp[0]: 0xfeffefec (a=1)
  envp[1]: 0xfeffeff0 (x=2)

After unsetenv("a")

envp: 0xfeffef6c environ: 0xfeffef70
  environ[0]: 0xfeffeff0 (x=2)

After setenv("b", "xxx", 1)

envp: 0xfeffef6c environ: 0xfeffef6c
  environ[0]: 0x806145c (b=xxx)
  environ[1]: 0xfeffeff0 (x=2)

After setenv("c", "xxx", 1)

envp: 0xfeffef6c environ: 0x8061c48
  environ[0]: 0x8061474 (c=xxx)
  environ[1]: 0x806145c (b=xxx)
  environ[2]: 0xfeffeff0 (x=2)

関連情報