fork
和之间有什么区别exec
?
fork
基本上是克隆的:O
fork
和之间有什么区别exec
?
fork
基本上是克隆的:O
Answers:
采用fork
和exec
体现了UNIX的精神,因为它提供了一个非常简单的方式开始新的进程。
该fork
调用基本上复制了当前过程,几乎在所有方面都相同。不是复制所有内容(例如,在某些实现中限制资源),但其想法是创建尽可能接近的副本。
新进程(子进程)获得不同的进程ID(PID),并将旧进程(父进程)的PID作为其父进程PID(PPID)。因为这两个进程现在正在运行完全相同的代码,所以它们可以通过返回码来分辨出哪个是fork
-子代为0,父代为子代的PID。当然,假设fork
调用成功,这就是全部-如果不成功,则不会创建任何子代,而父代会获得错误代码。
该exec
调用是用新程序基本上替换整个当前过程的方法。它将程序加载到当前进程空间并从入口点运行它。
因此,通常按顺序使用fork
和exec
来使新程序作为当前进程的子进程运行。当您尝试运行诸如find
shell fork之类的程序时,shell通常会执行此操作,然后子进程将find
程序加载到内存中,设置所有命令行参数,标准I / O等。
但是不需要将它们一起使用。程序fork
本身不带任何exec
子句是完全可以接受的,例如,该程序同时包含父代码和子代码(您需要小心操作,每个实现都可能有限制)。守护进程使用了很多(至今),这些守护进程仅侦听TCP端口及其fork
自身的副本,以处理父进程返回侦听的特定请求。
同样,知道自己已经完成并且只想运行另一个程序的程序也不需要fork
,exec
然后wait
对于孩子。他们可以将孩子直接加载到他们的处理空间中。
某些UNIX实现进行了优化fork
,使用所谓的写时复制。这是一个延迟复制进程空间的技巧,fork
直到程序尝试更改该空间中的某些内容为止。这对于仅使用fork
而不是不使用它们的程序exec
没有必要,因为它们不必复制整个进程空间。
如果exec
被称为以下fork
(这是什么会发生大多),导致进程空间写,然后将其复制子进程。
请注意,有一个全家exec
电话(execl
,execle
,execve
等),但exec
在上下文中这里是指任何人。
下图说明了使用shell通过命令列出目录的典型fork/exec
操作:bash
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 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
在当前进程中执行目标可执行文件后使用。
pid < 0
和fork()
呼叫失败
我认为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更为详尽的答案的简化的“傻瓜”版本)
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
创建当前进程(子进程)的副本。然后,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()
和之间的主要区别在于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”的输出。
它创建正在运行的进程的副本。正在运行的进程称为父进程,而新创建的进程称为子进程。区分两者的方法是查看返回的值:
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;
}