fork和exec之间的区别


Answers:


364

采用forkexec体现了UNIX的精神,因为它提供了一个非常简单的方式开始新的进程。

fork调用基本上复制了当前过程,几乎在所有方面都相同。不是复制所有内容(例如,在某些实现中限制资源),但其想法是创建尽可能接近的副本。

新进程(子进程)获得不同的进程ID(PID),并将旧进程(父进程)的PID作为其父进程PID(PPID)。因为这两个进程现在正在运行完全相同的代码,所以它们可以通过返回码来分辨出哪个是fork-子代为0,父代为子代的PID。当然,假设fork调用成功,这就是全部-如果不成功,则不会创建任何子代,而父代会获得错误代码。

exec调用是用新程序基本上替换整个当前过程的方法。它将程序加载到当前进程空间并从入口点运行它。

因此,通常按顺序使用forkexec来使新程序作为当前进程的子进程运行。当您尝试运行诸如findshell fork之类的程序时,shell通常会执行此操作,然后子进程将find程序加载到内存中,设置所有命令行参数,标准I / O等。

但是不需要将它们一起使用。程序fork本身不带任何exec子句是完全可以接受的,例如,该程序同时包含父代码和子代码(您需要小心操作,每个实现都可能有限制)。守护进程使用了​​很多(至今),这些守护进程仅侦听TCP端口及其fork自身的副本,以处理父进程返回侦听的特定请求。

同样,知道自己已经完成并且只想运行另一个程序的程序也不需要forkexec然后wait对于孩子。他们可以将孩子直接加载到他们的处理空间中。

某些UNIX实现进行了优化fork,使用所谓的写时复制。这是一个延迟复制进程空间的技巧,fork直到程序尝试更改该空间中的某些内容为止。这对于仅使用fork而不是不使用它们的程序exec没有必要,因为它们不必复制整个进程空间。

如果exec 称为以下fork(这是什么会发生大多),导致进程空间写,然后将其复制子进程。

请注意,有一个全家exec电话(execlexecleexecve等),但exec在上下文中这里是指任何人。

下图说明了使用shell通过命令列出目录的典型fork/exec操作:bashls

+--------+
| pid=7  |
| ppid=4 |
| bash   |
+--------+
    |
    | calls fork
    V
+--------+             +--------+
| pid=7  |    forks    | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash   |             | bash   |
+--------+             +--------+
    |                      |
    | waits for pid 22     | calls exec to run ls
    |                      V
    |                  +--------+
    |                  | pid=22 |
    |                  | ppid=7 |
    |                  | ls     |
    V                  +--------+
+--------+                 |
| pid=7  |                 | exits
| ppid=4 | <---------------+
| bash   |
+--------+
    |
    | continues
    V

52

fork()将当前过程分为两个过程。换句话说,您容易想到的线性程序突然变成了运行一个代码的两个独立程序:

 int pid = fork();

 if (pid == 0)
 {
     printf("I'm the child");
 }
 else
 {
     printf("I'm the parent, my child is %i", pid);
     // here we can kill the child, but that's not very parently of us
 }

这可能会让您大吃一惊。现在,您具有由两个进程执行的状态几乎相同的一段代码。子进程继承了刚刚创建它的进程的所有代码和内存,包括从fork()刚刚结束的调用处开始。唯一的区别是fork()返回码告诉您您是父母还是孩子。如果您是父母,则返回值为孩子的ID。

exec有点容易理解,您只是告诉您exec使用目标可执行文件执行一个进程,而没有两个进程运行相同的代码或继承相同的状态。就像@Steve Hawkins所说的那样,exec可以fork在当前进程中执行目标可执行文件后使用。


6
还有情况是什么时候pid < 0fork()呼叫失败
Jonathan Fingland

3
这一点根本没让我惊讶:-)每当使用共享库或DLL时,就会发生由两个进程执行的一段代码。
paxdiablo

31

我认为Marc Rochkind的“高级Unix编程”中的某些概念有助于理解fork()/ 的不同角色exec(),特别是对于那些习惯于Windows CreateProcess()模型的人:

一个方案是被保存在磁盘上的常规文件的指令和数据的集合。(来自1.1.2程序,进程和线程)

为了运行程序,首先要求内核创建一个新进程,该进程是在其中执行程序的环境。(同样来自1.1.2程序,进程和线程)

如果不完全了解进程和程序之间的区别,就不可能理解exec或fork系统调用。如果您不熟悉这些条款,则可能需要返回并阅读第1.1.2节。如果您准备立即进行操作,我们将用一句话来概括区别:进程是一种执行环境,由指令,用户数据和系统数据段以及在运行时获取的许多其他资源组成,而程序是一个包含指令和数据的文件,用于初始化进程的指令和用户数据段。(来自5.3 exec系统调用)

一旦了解了程序与流程之间的区别,就可以将其行为fork()exec()功能概括为:

  • fork() 创建当前流程的副本
  • exec() 用另一个程序替换当前进程中的程序

(这实际上是paxdiablo更为详尽的答案的简化的“傻瓜”版本)


29

Fork创建调用过程的副本。通常遵循以下结构 在此处输入图片说明

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

(对于子进程文本(代码),数据,堆栈与调用进程相同),子进程在if块中执行代码。

EXEC用新流程的代码,数据,堆栈替换当前流程。通常遵循以下结构 在此处输入图片说明

int cpid = fork( );

if (cpid = = 0) 
{   
  //child code

  exec(foo);

  exit(0);    
}

//parent code

wait(cpid);

// end

(在exec调用unix内核后清除子进程的文本,数据,堆栈并填充与foo进程相关的文本/数据),因此子进程具有不同的代码(foo的代码{与父代不相同))


1
这与问题无关,但是如果子进程碰巧先完成了它的代码,那么上面的代码是否不会引起竞争状态?在这种情况下,父进程将永远徘徊,等待子进程终止,对吧?
stdout

7

它们一起用于创建新的子进程。首先,调用fork创建当前进程(子进程)的副本。然后,exec从子流程中调用,以用新流程“替换”父流程的副本。

该过程如下所示:

child = fork();  //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail

if (child < 0) {
    std::cout << "Failed to fork GUI process...Exiting" << std::endl;
    exit (-1);
} else if (child == 0) {       // This is the Child Process
    // Call one of the "exec" functions to create the child process
    execvp (argv[0], const_cast<char**>(argv));
} else {                       // This is the Parent Process
    //Continue executing parent process
}

2
在第7行中提到了exec()函数创建了子进程。是真的吗,因为fork()已经创建了子进程,而exec()调用只是替换了刚刚创建的新进程的程序
cbinder

4

fork()创建当前进程的副本,并在fork()调用之后立即在新子进程中执行。在fork()之后,它们相同,除了fork()函数的返回值。(有关更多详细信息,请参见RTFM。)然后这两个进程可以进一步分散,一个进程无法干扰另一个进程,除非可能通过任何共享文件句柄进行。

exec()用新进程替换当前进程。它与fork()无关,除了exec()通常在需要启动另一个子进程而不是替换当前子进程时跟随fork()。


3

fork()和之间的主要区别在于exec()

fork()系统调用创建当前运行程序的克隆。在fork()函数调用之后,原始程序将继续执行下一行代码。克隆还将在下一行代码处开始执行。查看我从http://timmurphy.org/2014/04/26/using-fork-in-cc-a-minimum-working-example/获得的以下代码

#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    printf("--beginning of program\n");
    int counter = 0;
    pid_t pid = fork();
    if (pid == 0)
    {
        // child process
        int i = 0;
        for (; i < 5; ++i)
        {
            printf("child process: counter=%d\n", ++counter);
        }
    }
    else if (pid > 0)
    {
        // parent process
        int j = 0;
        for (; j < 5; ++j)
        {
            printf("parent process: counter=%d\n", ++counter);
        }
    }
    else
    {
        // fork failed
        printf("fork() failed!\n");
        return 1;
    }
    printf("--end of program--\n");
    return 0;
}

该程序在声明之前声明一个计数器变量,该变量设置为零fork()。在fork调用之后,我们有两个并行运行的进程,都增加了它们自己的counter版本。每个过程将运行完成并退出。因为这些进程是并行运行的,所以我们无法知道哪个将首先完成。运行该程序将打印与以下所示类似的内容,尽管每次运行的结果可能有所不同。

--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--

exec()系统调用的家人替换进程的当前正在执行的代码与另一段代码。该进程保留其PID,但成为新程序。例如,考虑以下代码:

#include <stdio.h> 
#include <unistd.h> 
main() {
 char program[80],*args[3];
 int i; 
printf("Ready to exec()...\n"); 
strcpy(program,"date"); 
args[0]="date"; 
args[1]="-u"; 
args[2]=NULL; 
i=execvp(program,args); 
printf("i=%d ... did it work?\n",i); 
} 

该程序调用该execvp()函数以用日期程序替换其代码。如果代码存储在名为exec1.c的文件中,则执行该代码将产生以下输出:

Ready to exec()... 
Tue Jul 15 20:17:53 UTC 2008 

程序输出“ Ready to exec()”行。。。”,并在调用execvp()函数后,将其代码替换为date程序。注意,线―。。。它没有工作吗”,因为此时代码已被替换。相反,我们看到了执行“ date -u”的输出。


1

在此处输入图片说明fork()

它创建正在运行的进程的副本。正在运行的进程称为父进程,而新创建的进程称为子进程。区分两者的方法是查看返回的值:

  1. fork() 返回父进程中子进程的进程标识符(pid)

  2. fork() 在子级中返回0。

exec()

它在流程中启动一个新流程。它将新程序加载到当前进程中,以替换现有程序。

fork()+ exec()

当启动一个新程序时,首先要fork()创建一个新进程,然后exec()(即加载到内存中并执行)它应该运行的程序二进制文件。

int main( void ) 
{
    int pid = fork();
    if ( pid == 0 ) 
    {
        execvp( "find", argv );
    }

    //Put the parent to sleep for 2 sec,let the child finished executing 
    wait( 2 );

    return 0;
}

0

了解fork()and exec()概念的主要示例是shell,它是用户登录系统后通常执行的命令解释程序。shell将命令行的第一个单词解释为命令名称。

对于许多指令,所述 和子进程高层名为处理在命令行上剩余的词作为参数传递给该命令相关联的命令。

外壳允许三种类型的命令。首先,命令可以是一个 可执行文件,其中包含通过编译源代码(例如C程序)生成的目标代码。其次,命令可以是包含一系列Shell命令行的可执行文件。最后,命令可以是内部shell命令(而不是可执行文件ex-> cdls等)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.