程序如何继承环境变量?

时间:2015-06-24 19:10:46

标签: c linux libc

当我使用标准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在哪里初始化?

5 个答案:

答案 0 :(得分:8)

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

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

<强>描述

     

execve()执行filename指向的程序。 [...]   argv是传递给新程序的参数字符串数组。按照惯例,这些字符串中的第一个应包含与正在执行的文件关联的文件名。 envp是一个字符串数组,通常为key=value形式,它们作为环境传递给新程序。 argvenvp都必须由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

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

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

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

答案 2 :(得分:3)

在Linux下程序启动时,它的参数和环境变量存储在堆栈中。对于C程序,在main之前执行的代码会查看此代码,生成argvenvp指针数组,然后使用这些值调用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。新创建的进程从父进程接收副本。