fork和exec有什么区别?


当前回答

它们一起使用来创建一个新的子进程。首先,调用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
}

其他回答

fork ():

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

Fork()返回父进程中子进程的标识符(pid) 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;
}

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的代码{与父进程不相同})

Fork()创建当前进程的副本,在Fork()调用之后的新子进程中执行。在fork()之后,除了fork()函数的返回值外,它们是相同的。(详情请参阅RTFM。)这两个进程可以进一步分离,其中一个进程不能干扰另一个进程,除非可能通过任何共享的文件句柄。

Exec()将当前进程替换为新进程。它与fork()无关,只是当需要启动不同的子进程,而不是替换当前进程时,exec()通常会在fork()之后执行。

理解fork()和exec()概念的主要例子是shell,用户通常在登录到系统后执行命令解释器程序。shell将命令行的第一个单词解释为命令名

对于许多命令,shell fork和子进程执行与名称相关的命令,将命令行上的剩余单词作为命令的参数。

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

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

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

新进程(子进程)获得不同的进程ID (PID),并将旧进程(父进程)的PID作为其父PID (PPID)。因为两个进程现在正在运行完全相同的代码,它们可以通过fork的返回代码来区分哪个是哪个——子进程得到0,父进程得到子进程的PID。当然,这只是假设fork调用有效的情况——如果不行,就不会创建子进程,父进程会得到一个错误代码。

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

因此,fork和exec通常按顺序使用,以使新程序作为当前进程的子程序运行。每当您试图运行find之类的程序时,shell通常都会这样做——shell会fork,然后子进程将find程序加载到内存中,设置所有命令行参数、标准I/O等等。

但它们不需要一起使用。例如,如果程序包含父代码和子代码,则完全可以在不执行的情况下对程序进行fork(您需要小心,每个实现都可能有限制)。这在守护进程中被大量使用(现在仍然如此),这些守护进程只是在TCP端口上侦听,并生成自己的副本来处理特定的请求,而父进程则返回侦听。

类似地,知道自己已经完成,只想运行另一个程序的程序不需要fork、exec然后等待子程序。它们可以直接将子进程加载到进程空间中。

一些UNIX实现有一个优化的分叉,它使用所谓的写时复制。这是一种延迟fork中进程空间复制的技巧,直到程序试图更改该空间中的某些内容。这对于那些只使用fork而不使用exec的程序很有用,因为它们不需要复制整个进程空间。

如果在fork之后调用exec(这是最常见的情况),则会导致写入进程空间,然后将其复制给子进程。

注意,这里有一整个exec调用家族(execl, execle, execve等等),但这里上下文中的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