fork和exec有什么区别?
当前回答
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创建当前进程(子进程)的副本。然后,在子进程内部调用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()将当前进程拆分为两个进程。换句话说,你的线性程序突然变成了两个单独的程序,运行着一段代码:
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后在当前进程中执行目标可执行文件。
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
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的代码{与父进程不相同})
我认为Marc Rochkind的“高级Unix编程”中的一些概念有助于理解fork()/exec()的不同角色,特别是对于习惯了Windows CreateProcess()模型的人:
程序是保存在磁盘上常规文件中的指令和数据的集合。(摘自1.1.2程序、进程和线程)
.
为了运行程序,首先要求内核创建一个新进程,这是一个程序执行的环境。(也来自1.1.2程序、进程和线程)
.
It’s impossible to understand the exec or fork system calls without fully understanding the distinction between a process and a program. If these terms are new to you, you may want to go back and review Section 1.1.2. If you’re ready to proceed now, we’ll summarize the distinction in one sentence: A process is an execution environment that consists of instruction, user-data, and system-data segments, as well as lots of other resources acquired at runtime, whereas a program is a file containing instructions and data that are used to initialize the instruction and user-data segments of a process. (from 5.3 exec System Calls)
一旦你理解了程序和进程之间的区别,fork()和exec()函数的行为可以总结为:
Fork()创建当前进程的副本 Exec()将当前进程中的程序替换为另一个程序
(这基本上是paxdiablo更详细答案的简化版“傻瓜版”)