程序如何inheritance环境变量?

当我使用标准C库中的函数getenv() ,我的程序从其父级inheritance环境变量。

例:

 $ 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除非我的main函数不是我程序的真正入口点。 也许gcc通过添加一个_init函数来引导我,它是标准C库的一部分。

environ初始化在哪里?

这里没有神秘感。

首先,shell分叉。 分叉过程显然具有相同的环境。 然后在孩子中执行新程序。 所讨论的系统调用是execve ,其中包括指向环境的指针。

那么,在执行二进制文件之后设置什么环境完全取决于执行exec的代码。

所有这一切都可以通过运行strace轻松看出。

编辑:自编辑问题以询问environ

当您执行动态链接的二进制文件时,执行任何操作的第一个用户空间代码都来自加载程序。 加载器和其他东西设置变量,如argcargvenviron ,然后才从二进制文件中调用main()

再一次,所有这些的来源都是免费提供的。 虽然由于粗糙的格式化而使glibc的源代码难以读取,但BSD很容易在概念上等同。

http://code.metager.de/source/xref/freebsd/libexec/rtld-elf/rtld.c#389

环境变量从父进程向下传递,作为main第三个参数 。 发现这个的最简单方法是阅读系统调用execve的文档,特别是这一点:

 int execve(const char *filename, char *const argv[], char *const envp[]); 

描述

execve()执行filename指向的程序。 […] argv是传递给新程序的参数字符串数组。 按照惯例,这些字符串中的第一个应包含与正在执行的文件关联的文件名。 envp是一个字符串数组,通常格式为key=value ,它们作为环境传递给新程序。 argvenvp必须由NULL指针终止。 参数向量和环境可以被被调用程序的main函数访问,当它被定义为:

 int main(int argc, char *argv[], char *envp[]) 

在它调用main之前,C库将envp参数复制到其启动代码中的environ全局变量中:例如,GNU libc在_init执行此操作,而musl libc在__init_libc执行此__init_libc 。 (你可能会发现musl libc的代码比GNU libc更容易跟踪。)相反,如果你使用一个采用显式环境向量的exec包装函数启动一个程序,那么C库提供environ作为第三个参数execve 。 因此,环境变量的inheritance严格地是用户空间约定。 就内核而言,每个程序都接收两个参数向量,而不关心它们中的内容。

(注意,三参数main是C语言的扩展.C标准只指定int main(void)int main(int argc, char **argv)但它允许实现定义其他forms( C11 Annex J. 5.1环境参数 )。三个参数main是自Unix V7以来环境变量如何工作,如果不是更长,并且也由Microsoft记录 – 请参阅C和C ++中main()应该返回什么?

在Linux下程序启动时,它的参数和环境变量存储在堆栈中。 对于C程序,在main之前执行的代码查看此代码,生成指针的argvenvp数组,然后使用这些值(和argc )调用argc

当一个程序调用execvpe变成一个新程序时(通常在调用fork ),然后传入一个envp和一个argv 。 内核会将这些数据复制到新程序的堆栈中。

当调用任何其他exec函数时,glibc将作为新程序的envp execvpe (或直接传递给sys_exec)传入当前程序的environ

问题是,shell如何运行命令?

答案是通过使用fork()execl()创建一个新进程,它创建一个与当前进程具有相同环境的进程。

但是,您可以使用execvpe() / execvpe()创建具有自定义环境的新进程。

但是在任何不必要的正常情况下,特别是因为许多程序期望某些环境变量被定义为例如PATH ,所以通常子进程从调用它的环境inheritance环境变量。

调用程序(您的shell)的父进程定义了FOO。 新创建的进程从父进程接收副本。