unix fork和exec的区别

t30tvxxf  于 5个月前  发布在  Unix
关注(0)|答案(9)|浏览(47)

forkexec有什么区别?

uqxowvwt

uqxowvwt1#

forkexec的使用体现了UNIX的精神,因为它提供了一种非常简单的启动新进程的方法。
fork调用基本上复制了当前进程,几乎在所有方面都是相同的。并不是所有内容都被复制(例如,在某些实现中的资源限制),但其想法是创建尽可能接近的副本。
新进程(子进程)获得不同的进程ID(PID),并将旧进程(父进程)的PID作为其父进程的PID(PPID)。由于这两个进程现在运行的代码完全相同,因此它们可以通过返回代码fork来区分哪个是哪个-子进程获得0,父进程获得子进程的PID。当然,这就是全部,假设fork调用起作用,如果不起作用,则不创建子代,并且父代得到错误代码。
exec调用是一种用新程序替换整个当前进程的方法,它将程序加载到当前进程空间,并从入口点运行它。
因此,forkexec经常被顺序使用,以使一个新程序作为当前进程的子进程运行。每当你试图运行一个像find这样的程序时,shell通常会这样做-shell分叉,然后子进程将find程序加载到内存中,设置所有命令行参数,标准I/O等等。
但是它们并不一定要一起使用,例如,如果程序同时包含父代码和子代码,那么完全可以接受程序本身不执行exec操作(你需要小心你所做的,每个实现都可能有限制)。(现在仍然是)对于守护进程来说,它只是在TCP端口上侦听,并在父进程返回侦听时使用自己的fork副本来处理特定请求。
类似地,程序知道自己已经完成,只想运行另一个程序,不需要为子进程执行forkexecwait,它们可以直接将子进程加载到它们的进程空间。
一些UNIX实现有一个优化的fork,它使用他们所谓的写时复制。这是一个技巧,可以延迟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

字符串
顺便说一句,在Retrocomputing Stack Exchange site上有一个有趣的答案,它详细介绍了forkexec的一些历史。

dly7yett

dly7yett2#

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()返回代码告诉你你是父还是子。如果你是parent,返回值是child的id。
exec更容易掌握,你只需要告诉exec使用目标可执行文件执行一个进程,你不会有两个进程运行相同的代码或继承相同的状态。就像@Steve Hawkins说的,exec可以在你fork之后使用,在当前进程中执行目标可执行文件。

ahy6op9u

ahy6op9u3#

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

  • program* 是保存在磁盘上的常规文件中的指令和数据的集合。(摘自1.1.2程序、进程和线程)


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

如果不完全理解进程和程序之间的区别,就不可能理解exec或fork系统调用。如果您对这些术语不熟悉,可能需要返回并查看1.1.2节。如果您准备好继续,我们将用一句话来总结它们之间的区别:进程是由指令、用户数据和系统数据段以及在运行时获取的许多其他资源组成的执行环境,而程序是一个包含指令和数据的文件,用于初始化进程的指令和用户数据段。
一旦你理解了一个程序和一个进程之间的区别,fork()exec()函数的行为就可以概括为:

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

(this本质上是paxdiablo更为详细的答案的简化版“傻瓜版”)

4xy9mtcn

4xy9mtcn4#

Fork创建一个调用进程的副本。通常遵循结构x1c 0d1x

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

字符串
(for子进程文本(代码),数据,堆栈与调用进程相同)子进程执行if块中的代码。
EXEC用新进程的代码、数据、堆栈替换当前进程。通常遵循

结构

int cpid = fork( );

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

  exec(foo);

  exit(0);    
}

//parent code

wait(cpid);

// end


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

kqlmhetl

kqlmhetl5#

首先,调用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
}

字符串

1qczuiv0

1qczuiv06#

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() ing之前声明了一个计数器变量,设置为零。在fork调用之后,我们有两个进程并行运行,都递增自己的计数器版本。每个进程都会运行到完成并退出。因为进程并行运行,我们无法知道哪个进程会先完成。运行这个程序将打印类似于下面所示的内容,尽管结果可能从一次运行到下一次运行而变化。

--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()..

1cosmwyk

1cosmwyk7#

fork()创建了当前进程的一个副本,新的子进程从fork()调用之后开始执行。在fork()之后,它们是相同的,除了fork()函数的返回值。(RTFM了解更多细节。)然后两个进程可以进一步分离,一个不能干扰另一个,除非可能通过任何共享文件句柄。
exec()用一个新进程替换当前进程。它与fork()无关,只是当需要启动一个不同的子进程时,exec()通常会跟随fork(),而不是替换当前进程。

d4so4syb

d4so4syb8#


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

字符串

i5desfxk

i5desfxk9#

理解fork()exec()概念的最好例子是 shell,这是用户通常在登录系统后执行的命令解释程序。shell将命令行的第一个单词解释为 * 命令 * 名
对于许多命令,shell fork * 和子进程 execs 与名称关联的命令,将命令行上的其余单词视为命令的参数。

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

相关问题