关于 fork 和 execve 系统调用

16
据说fork系统调用创建一个调用进程的克隆,然后(通常)子进程会发出execve系统调用来改变其映像并运行新进程。为什么要这样做呢?
顺便问一句,execve代表什么?
5个回答

18

两步的原因是为了灵活性。在这两个步骤之间,您可以修改子进程的上下文,新的exec'ed程序将继承该上下文。

您可能想要更改的一些内容包括:

  • 文件描述符
  • 用户/组ID
  • 进程组和会话ID
  • 当前目录
  • 资源限制
  • 调度优先级和亲和力
  • 文件创建掩码(umask)

如果您没有拆分fork和exec,而是使用单个类似于spawn的系统调用,则需要为每个这些进程属性提供参数,如果您希望在子进程中对它们进行不同设置。例如,请参见Windows API中CreateProcess的参数列表。

通过fork/exec,在调用新的程序之前,您可以更改您想要的任何可继承进程属性。

设置文件描述符是更改子进程上下文中较常见的操作之一。如果您想捕获程序的输出,通常会使用pipe(2)系统调用在父进程中创建一个管道,在fork(2)之后,在父进程中关闭写端口,并在子进程中关闭读端口,然后调用execve(2)。(您还将使用dup(2)将管道的子端口设置为文件描述符1(标准输出))。这在单个系统调用中可能是不可能或受限制的。


我想知道你所说的“如果你想在子进程中设置它们不同,它需要为每个进程属性接受参数”的意思。 - Tim
有没有一些示例代码来说明更改环境变量 w 和 w/o 分离 fork 和 exec 的灵活性,以显示哪种方法更灵活? - Tim
@Tim:请参考http://msdn.microsoft.com/en-us/library/ms682425(v=vs.85).aspx,这是一个例子,可以看到该函数调用的参数列表并未涵盖上述所有内容。 - camh
spawn*函数的envp参数是否可以接受任何环境变量?它们是否像fork-exec一样灵活,比createprocess函数更灵活?为什么文章说“spawn函数虽然可以处理最常见的用例,但缺乏fork-exec的全部功能,因为在fork之后,任何进程设置都可能在exec之后被更改。然而,在大多数情况下,这种不足可以通过使用更低级别的CreateProcess API来弥补”? - Tim
@Tim:这里的评论是用于评论,而不是提问。如果您有问题,请将其发布为问题。我的回答已经充分回答了原始问题,我认为不应该扩展到讨论Windows进程创建函数。 - camh

16
  • exec:执行新进程
  • v:使用参数数组
  • e:同时指定环境变量

exec还有其他变体:

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
  • l:函数的列表参数
  • p:使用$PATH查找可执行文件

10

每个步骤都相对简单。

在Unix中,你的进程有两个部分 - 一个只读的内存区域,包含应用程序代码("文本"),以及读写的内存区域("数据")。

fork克隆了读写区域,保留文本页不变。现在你有两个运行同一代码的进程。它们通过寄存器值(来自fork的返回值)不同而不同,这将父进程和子进程分开。

exec替换了文本页,保留数据页不变。有许多形式的exec,取决于你传递多少环境信息给它。请参见http://linux.die.net/man/3/exec以获取其他变体的列表。


实际上,使用fork()函数你不可能犯任何错误,因为它没有任何参数。有什么比这更简单的呢? - 0x6adb015
9
Umm,exec替换整个mm,而不仅仅是“text”...请参见/usr/src/linux/fs/exec.c和/usr/src/linux/fs/binfmt_elf.c。 - ephemient
1
通常情况下,fork不会克隆数据区域,而是将其标记为Copy-On-Write(COW)。如果父进程或子进程尝试更改数据,则页面将被克隆,并且父进程和子进程将拥有不同的页面。 - camh

3
“exec”函数族可以将当前进程(调用该函数的进程)的映像替换为新的进程映像,因此调用者的映像就被新的进程映像所代替。例如,如果您从shell(/bin/sh或/bin/csh)中运行“ls”命令,则shell会分叉出一个新进程,然后执行ls。一旦ls命令退出,它便会将控制返回到父进程,即在本示例中是shell。
如果没有分叉功能,那么shell将被“ls”进程所取代,而在执行完毕后,由于ls调用了exec以替换内存中的shell映像,您将无法访问终端。
有关“exec”系列的其他变体,请参阅0x6adb015的回答。

1

execve是什么意思?

C语言中的exec函数有6个变体,它们分别是exec{l,v}{,e,p}。详情请参见下面的函数原型。

命令行参数

  • v - 命令行参数作为指针数组(vector)传递给函数。
  • l - 命令行参数被单独传递(list)给函数。

环境变量(可选)

  • e - 一个指向e环境变量的指针数组被显式地传递给新进程映像。

定位要执行的文件(可选)

  • p - 使用PATH环境变量查找要执行的文件名中的文件参数。

int execl (char const *path, char const *arg0, ...);
int execle(char const *path, char const *arg0, ..., char const *envp[]);
int execlp(char const *file, char const *arg0, ...);
int execv (char const *path, char const *argv[]);
int execve(char const *path, char const *argv[], char const *envp[]);
int execvp(char const *file, char const *argv[]);

来源


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接