程序如何继承环境变量?

now*_*wox 2 c linux libc

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

例:

$ export FOO=42
$ <<< 'int main() {printf("%s\n", getenv("FOO"));}' gcc -w -xc - && ./a.exe
42
Run Code Online (Sandbox Code Playgroud)

在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)
Run Code Online (Sandbox Code Playgroud)

在这里,我将获取__environ变量的内容.但是我从来没有初始化它.

所以我感到困惑,因为除非我的主要功能不是我的程序的真正切入点,否则environ应该NULL是这样.或许gcc通过添加一个_init属于标准C库的函数来勾选我.

在哪里environ初始化?

zwo*_*wol 9

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

int execve(const char *filename, char *const argv[], char *const envp[]);
Run Code Online (Sandbox Code Playgroud)

描述

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

int main(int argc, char *argv[], char *envp[])
Run Code Online (Sandbox Code Playgroud)

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

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


emp*_*nth 8

这里没有神秘感.

首先,外壳分叉.分叉过程显然具有相同的环境.然后在孩子中执行新程序.有问题的系统调用是execve,其中包括指向环境的指针.

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

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

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

当您执行动态链接的二进制文件时,执行任何操作的第一个用户空间代码都来自加载程序.加载器和其他东西设置变量argc,argv或者environ只是main()从二进制文件调用.

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

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