当我使用标准C库中的函数getenv()
时,我的程序从其父级继承环境变量。
示例:
$ export FOO=42
$ <<< 'int main() {printf("%s\n", getenv("FOO"));}' gcc -w -xc - && ./a.exe
42
在libc中,environ
变量声明为environ.c
。我希望它在执行时是空的,但我得到42
。
进一步getenv
可以简化如下:
char * getenv (const char *name)
{
size_t len = strlen (name);
char **ep;
uint16_t name_start;
name_start = *(const uint16_t *) name;
len -= 2;
name += 2;
for (ep = __environ; *ep != NULL; ++ep)
{
uint16_t ep_start = *(uint16_t *) *ep;
if (name_start == ep_start && !strncmp (*ep + 2, name, len)
&& (*ep)[len + 2] == '=')
return &(*ep)[len + 3];
}
return NULL;
}
libc_hidden_def (getenv)
这里我将获取__environ
变量的内容。但是我从来没有初始化它。
所以我感到困惑,因为environ
应该是NULL
,除非我的主要功能不是我程序的真正切入点。也许gcc
通过添加作为标准C库一部分的_init
函数来帮助我。
environ
在哪里初始化?
答案 0 :(得分:8)
环境变量从父进程向下传递为第三个参数main
。发现这一点的最简单方法是阅读系统调用execve
的文档,特别是这一点:
int execve(const char *filename, char *const argv[], char *const envp[]);
<强>描述强>
时
execve()
执行filename
指向的程序。 [...]argv
是传递给新程序的参数字符串数组。按照惯例,这些字符串中的第一个应包含与正在执行的文件关联的文件名。envp
是一个字符串数组,通常为key=value
形式,它们作为环境传递给新程序。argv
和envp
都必须由NULL指针终止。参数向量和环境可以被被调用程序的主函数访问,当它被定义为:int main(int argc, char *argv[], char *envp[])
在调用envp
之前,C库将environ
参数复制到其启动代码中某处的main
全局变量中:例如,GNU libc在{{3}中执行此操作和musl libc在_init
中完成。 (您可能会发现musl libc的代码比GNU libc更易于跟踪。)相反,如果您使用不的__init_libc
包装函数之一启动程序,则采用显式环境向量,C库提供environ
作为execve
的第三个参数。因此,环境变量的继承严格地是用户空间约定。就内核而言,每个程序都接收两个参数向量,而不关心它们中的内容。
(请注意,三参数main
是C语言的扩展.C标准仅指定int main(void)
和int main(int argc, char **argv)
,但它允许实现定义其他形式({{3自三维参数main
以来,环境变量在Unix V7以后的工作方式如果不长,并且也由Microsoft记录 - 请参阅exec
。)
答案 1 :(得分:7)
这里没有任何谜。
首先,外壳分叉。分叉过程显然具有相同的环境。然后在孩子中执行新程序。有问题的系统调用是execve
,其中包括指向环境的指针。
那么,在执行二进制文件之后设置的环境完全取决于执行exec的代码。
通过运行strace可以很容易地看到所有这些。
编辑:,因为问题已被编辑,询问environ
:
当您执行动态链接的二进制文件时,执行任何操作的第一个用户空间代码都来自加载程序。加载器等设置了argc
,argv
或environ
之类的变量,然后才从二进制文件中调用main()
。
再一次,所有这些的来源都是免费提供的。虽然glibc的源代码由于残酷的格式化而难以阅读,但BSD很容易在概念上等同。
http://code.metager.de/source/xref/freebsd/libexec/rtld-elf/rtld.c#389
答案 2 :(得分:3)
在Linux下程序启动时,它的参数和环境变量存储在堆栈中。对于C程序,在main
之前执行的代码会查看此代码,生成argv
和envp
指针数组,然后使用这些值调用main
(并{{1 }})。
当程序调用{{1}}转换为新程序时(通常在调用argc
后),然后传入execvpe
以及fork
。内核会将这些数据复制到新程序的堆栈中。
当调用任何其他envp
函数时,glibc会将当前程序argv
作为新程序exec
传递给{{ 1}}(或直接到sys_exec)。
答案 3 :(得分:2)
问题是,shell如何运行命令?
答案是创建一个新流程,可能使用fork()
和execl()
,这会创建一个与当前流程环境相同的流程。
但是,您可以使用execvpe()
/execle()
创建一个包含自定义环境的新流程。
但是在任何不需要的正常情况下,特别是因为许多程序期望某些环境变量被定义为PATH
,例如,通常子进程从环境中继承环境变量它被调用。
答案 4 :(得分:1)
调用程序(你的shell)的父进程定义了FOO。新创建的进程从父进程接收副本。