fork和exec之间的区别

时间:2009-10-31 03:47:33

标签: c unix fork exec

forkexec之间有什么区别?

9 个答案:

答案 0 :(得分:344)

forkexec的使用体现了UNIX的精神,因为它提供了一种非常简单的方法来启动新进程。

fork调用基本上会复制当前进程,在几乎中都是相同的。并非所有内容都被复制(例如,某些实现中的资源限制),但我们的想法是创建尽可能接近的副本。

新进程(子进程)获取不同的进程ID(PID),并将旧进程(父进程)的PID作为其父PID(PPID)。因为这两个进程现在运行的代码完全相同,所以它们可以通过返回代码fork来判断哪个进程 - 子进程为0,父进程获取子进程的PID。当然,这就是假设fork调用有效 - 如果没有,则不会创建子节点并且父节点会收到错误代码。

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

因此,forkexec通常按顺序使用,以使新程序作为当前进程的子进程运行。每当你尝试运行像find这样的程序时,shell通常会这样做 - shell分叉,然后子程序将find程序加载到内存中,设置所有命令行参数,标准I / O等等

但它们不需要一起使用。例如,如果程序包含父代码和子代码,那么fork本身没有exec的程序就完全可以接受(你需要小心你做什么,每个实现都有限制)。对于守护进程使用了​​很多(现在仍然如此),它们只是在TCP端口上监听,并fork自己的副本来处理特定请求,而父进程则回去监听。

同样地,知道他们已经完成并且只想运行另一个程序的程序不需要forkexec然后wait为孩子。他们可以直接将孩子装入他们的处理空间。

某些UNIX实现具有优化的fork,它使用了他们称之为copy-on-write的内容。这是在fork中延迟复制进程空间的技巧,直到程序尝试更改该空间中的某些内容。这对于仅使用fork而非exec的程序非常有用,因为它们不必复制整个进程空间。

如果在exec之后调用fork (这是主要发生的事情),则会导致写入进程空间,然后为子进程复制过程

请注意,有一整套exec次来电(execlexecleexecve等等,但这里的exec意味着任何他们。

下图说明了典型的fork/exec操作,其中bash shell用于列出具有ls命令的目录:

+--------+
| 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

答案 1 :(得分: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之后用来在当前进程中执行目标可执行文件。

答案 2 :(得分:30)

我认为"Advanced Unix Programming" by Marc Rochkind中的一些概念有助于理解fork() / exec()的不同角色,特别是对于习惯于Windows CreateProcess()模型的人:

  

程序是一组指令和数据,保存在磁盘上的常规文件中。 (来自1.1.2程序,进程和线程)

  

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

  

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

一旦理解了程序和流程之间的区别,fork()exec()函数的行为可以概括为:

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

(这实际上是一个简化的“傻瓜版”paxdiablo's much more detailed answer

答案 3 :(得分:28)

Fork创建一个调用进程的副本。 一般遵循结构 enter image description here

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

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

EXEC用新进程的代码,数据和堆栈替换当前进程。 一般遵循结构 enter image description here

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exec(foo);

  exit(0);

}

//parent code

wait(cpid);

// end

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

答案 4 :(得分: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
}

答案 5 :(得分:4)

fork()创建当前进程的副本,并在fork()调用之后从新子进程执行。在fork()之后,除了fork()函数的返回值之外,它们是相同的。 (RTFM了解更多细节。)然后,两个进程可以进一步发散,一个不能干扰另一个,除非可能通过任何共享文件句柄。

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

答案 6 :(得分:2)

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); 
} 

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

execvp()

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

答案 7 :(得分:1)

enter image description here fork()

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

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

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

  3. 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;
    }
    

答案 8 :(得分:0)

理解fork()exec()概念的主要示例是 shell ,用户登录后通常执行的命令解释程序system.the shell将命令行的第一个单词解释为命令名称

对于许多命令, shell forks 和子进程 execs 与名称关联的命令,将命令行上的剩余单词视为命令的参数。

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