执行子进程时,OS在哪里存储argv和argc?
我很难理解操作系统如何将数据从父进程的地址空间传递到子进程的地址空间。 也就是说,在一个C程序中,argc和argv在传入main时存储在哪里?
我理解argv本质上是一个双指针。 我不理解的是操作系统在将它们加载到内核后对这些值的作用。 为子进程创建地址空间后,是否会将这些值推送到新空间的堆栈上? 我们显然不想将指针传递给另一个地址空间。
为了记录,我正在使用MIPS32架构。
在Linux上,至少在我使用的架构上,该过程从%esp
开始指向类似于:
argc | argv[0] | argv[1] | ... argv[argc - 1] | argv[argc] == NULL | envp[0] | envp[1] ... envp[?] == NULL
第一个被调用的函数传统上命名为_start
,它的工作是计算(argc = %esp, argv = ((char *)%esp) + 1, envp = ((char *)%esp) + argc + 2)
,然后使用适当的调用约定调用main
。
在x86上,参数在堆栈上传递。
在amd64上,它们在寄存器%rdi
, %rsi
和%rdx
传递。
在mips上,Google告诉我有几种不同的调用约定 – 包括O32,N32,N64 – 但它们都首先使用$a0
, $a1
, $a2
。
对于不同的操作系统,该过程是不同的,并且实际上根据新过程的创建方式而不同。 由于我更熟悉现代微软操作系统如何处理这个问题,我将从那里开始,并在最后提到nix的引用。
当[Microsoft]操作系统创建进程时,它会分配一个进程环境块来保存特定于该进程的数据。 除其他外,这包括调用程序的命令行参数。 此进程环境块从目标进程的地址空间中分配,并将指向它的指针提供给进程的入口点。 通常通过将父进程的环境块复制到新进程的地址空间来初始化子进程的进程环境块 – 不涉及直接共享内存。
对于基于C的程序,入口点不是程序员提供的main()
函数。 相反,它是由C运行时库提供的例程,它负责在将控制权交给程序员的main()
之前初始化运行时环境。
初始化有很多东西,但其中一个方面是设置argc和argv值。 为此,运行时将查询进程环境块以查找用于调用它的程序名称和参数。 然后(通常)将argv的值分配给进程堆(即使用类似malloc()
),并将argc分配给找到的params数(加上一个,用于程序名)。
argc和argv的实际值被推送到堆栈,就像在C中传递任何其他参数一样,因为C运行时对main()
的调用只是一个普通的函数调用。
因此,当您在子进程的main()
内部编写的代码访问argv时,它将从其自己的进程堆中读取值。 这些值的来源是进程环境块(由OS在本地地址空间中存储),最初是通过从父进程复制进程环境块来初始化的。
在* nix平台上,事情有点不同。 本讨论的主要区别在于,nix会将新进程的命令行参数直接存储到进程初始线程的堆栈空间中。 (它还在这里存储环境变量。)因此,在* nix上,使用指向存储在堆栈本身中的值的argv参数调用main。
您可以在execve的联机帮助页中收集一些内容,而Michael Kerrisk的Linux编程接口在6.4节中有一个很好的描述,您可能会在网上摘录。