난 그냥 보고 있어요이 문제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 **environ
libc는 일반적으로 위의 첫 번째 포인터 주소로 초기화되지만 환경(및 향후 실행에 사용되는 수정)에 대한 수정으로 인해 일반적으로 새로운 포인터 배열이 생성되어 에 할당됩니다 environ
.
environ
처음에 , to , to , to 에 대한 포인터는 [a,b,c,d,NULL]
어디에 있습니까 ? 를 수행하면 이어야 합니다 . 초기 배열 목록이 읽기 전용인 시스템에서는 새 목록을 할당하고 저장 해야 합니다 . 다음으로 목록을 그 자리에서 수정할 수 있습니다. 위의 작업을 수행하는 경우에만 목록이 재할당되지 않을 수 있습니다( 어쩌면 point 까지만 증가했을 수도 있습니다 . 일부 libc 구현이 실제로 해당 최적화를 수행하는지 여부는 알 수 없습니다).a
x=1
b
y=2
c
z=3
d
q=5
unsetenv("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()
. 뿐만 아니라 문자열 자체는 힙에 복사되므로 환경을 처음 수정한 후 프로그램이 시작될 때 수신된 문자열과 완전히 연결이 끊어집니다 environ
.envp[]
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()
b
c
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)