fork()的目的是什么?

115
在许多 Linux 的程序和 man 手册中,我看到了使用 fork() 的代码。我们为什么需要使用 fork(),它的目的是什么?

163
为了让所有就餐的哲学家都不会挨饿。 - kenj0418
15个回答

134

fork()是在Unix中创建新进程的方法。当你调用fork时,你创建了自己进程的一个副本,该副本有其自己的地址空间。这使得多个任务可以独立运行,就好像它们各自拥有整个机器的内存一样。

下面是一些fork的用法示例:

  1. 你的shell使用fork来运行你从命令行调用的程序。
  2. apache这样的Web服务器使用fork来创建多个服务器进程,每个进程都在自己的地址空间中处理请求。如果其中一个死掉或泄漏内存,其他进程不会受到影响,因此它可以作为容错机制。
  3. Google Chrome使用fork来在单独的进程中处理每个页面。这将防止一个页面上的客户端代码导致整个浏览器崩溃。
  4. fork用于在某些并行程序(如使用MPI编写的程序)中生成进程。请注意,这与使用线程不同,后者没有自己的地址空间,存在于进程内部
  5. 脚本语言间接地使用fork来启动子进程。例如,每次你在Python中使用类似subprocess.Popen这样的命令时,你都会fork一个子进程并读取它的输出。这使得程序可以相互协作。

在shell中典型的fork用法可能像这样:

int child_process_id = fork();
if (child_process_id) {
    // Fork returns a valid pid in the parent process.  Parent executes this.

    // wait for the child process to complete
    waitpid(child_process_id, ...);  // omitted extra args for brevity

    // child process finished!
} else {
    // Fork returns 0 in the child process.  Child executes this.

    // new argv array for the child process
    const char *argv[] = {"arg1", "arg2", "arg3", NULL};

    // now start executing some other program
    exec("/path/to/a/program", argv);
}

Shell使用exec生成子进程并等待其完成,然后继续自己的执行。请注意,您不必以这种方式使用fork。您可以始终生成大量的子进程,就像并行程序可能会做的那样,并且每个进程可以同时运行一个程序。基本上,在Unix系统中创建新进程的任何时候都在使用fork()。对于Windows的等效方法,请查看CreateProcess

如果您需要更多示例和更长的解释,请查看维基百科的概述。此外,这里提供了一些幻灯片,介绍了现代操作系统中进程、线程和并发的工作原理。


25
有趣的是,它被称为CreateProcess()——这些疯狂的Windows人士 :-) - paxdiablo
5
直到现在我才意识到,“shell使用fork来运行你从命令行调用的程序”! - Lazer
1
幻灯片的链接已损坏。 - piertoni
2
所有的回答都说fork()是在UNIX中创建新进程的方法,但严谨地说,还有至少一种其他方法:posix_spawn() - Davislor
2
但是,有趣的是,使用posix_spawn在为cygwin编译时可能会更有效率,或者需要更少的仿真工作,让它只使用CreateProcess而不是伪造一个fork。 - Peter Cordes
显示剩余5条评论

21

fork() 是 Unix 创建新进程的方式。在调用 fork() 函数时,你的进程被克隆,两个不同的进程从那里开始执行。其中一个是子进程,将会返回 0,另一个是父进程,将会返回子进程的 PID(进程 ID)。

例如,如果你在 shell 中键入以下命令,shell 程序将调用 fork(),然后在子进程中执行你传递的命令(在此例中为 telnetd),而父进程将再次显示提示符,并显示一个指示后台进程 PID 的消息。

$ telnetd &

创建新进程的原因是让你的操作系统能够同时执行多个任务。这就是为什么你可以运行一个程序,而在它运行的同时切换到另一个窗口来做其他事情。


@varDumper 很好的发现! - Daniel C. Sobral

13

fork() 的基本作用是为调用该函数的进程创建一个子进程。每当你调用 fork() 函数时,它会返回子进程 id 的零值。

pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process

通过这种方式,您可以为父项和子项提供不同的操作,并利用多线程功能。


12

fork()用于创建子进程。当调用fork()函数时,会产生一个新的进程,并且fork()函数对于子进程和父进程返回不同的值。

如果返回值是0,则表示你是子进程;如果返回值是数字(这个数字是子进程的进程ID),则表示你是父进程;(如果返回一个负数,则表示fork()失败,没有创建子进程)

http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html


1
除非返回值为-1,否则fork()函数执行成功。 - Jonathan Leffler

10

fork()会创建一个和父进程完全相同的子进程。所以,在此之后运行的所有代码都将被两个进程执行,这对于例如有服务器并想处理多个请求的情况非常有用。


1
为什么要创建一个与父级完全相同的子级,有什么用处? - kar
2
这就像是建立一支军队而不是单兵作战。你进行分叉,以便你的程序可以同时处理更多的请求,而不是一个接一个地处理。 - cloudhead
fork()在子进程中返回0,在父进程中返回子进程的pid。然后子进程可以使用类似exec()的调用来替换其状态为新程序。这就是启动程序的方式。 - Todd Gamblin
这些进程非常相似,但是存在许多微妙的差异。明显的区别在于当前PID和父PID。还存在与锁定和信号量相关的问题。POSIX的fork()手册页面列出了父进程和子进程之间的25个不同之处。 - Jonathan Leffler
2
@kar:一旦您有两个进程,它们可以分别继续,并且其中一个可以使用exex()完全用另一个程序替换自己。 - Vatine

7

系统调用fork()用于创建进程。它不需要参数并返回一个进程ID。fork()的目的是创建一个新的进程,该进程成为调用者的子进程。创建新的子进程后,两个进程将执行fork()系统调用后面的下一条指令。因此,我们必须区分父进程和子进程。这可以通过测试fork()的返回值来实现:

如果fork()返回负值,则创建子进程失败。 fork()将零返回给新创建的子进程。 fork()返回正值,即子进程的进程ID,给父进程。返回的进程ID是在sys/types.h中定义的pid_t类型。通常,进程ID是整数。此外,进程可以使用函数getpid()来检索分配给该进程的进程ID。 因此,在调用fork()系统调用后,一个简单的测试可以告诉我们哪个进程是子进程。请注意,Unix会精确复制父进程的地址空间并将其赋予子进程。因此,父进程和子进程具有单独的地址空间。

让我们通过一个示例来理解上述要点。这个例子没有区分父进程和子进程。

#include  <stdio.h>
#include  <string.h>
#include  <sys/types.h>

#define   MAX_COUNT  200
#define   BUF_SIZE   100

void  main(void)
{
     pid_t  pid;
     int    i;
     char   buf[BUF_SIZE];

     fork();
     pid = getpid();
     for (i = 1; i <= MAX_COUNT; i++) {
          sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
          write(1, buf, strlen(buf));
     } 
}

假设以上程序执行到fork()调用的位置。
如果fork()调用成功,Unix将为父进程和子进程分别创建两个相同的地址空间副本。两个进程都将从fork()调用后的下一条语句开始执行。在这种情况下,两个进程都将从赋值语句开始执行。
pid = .....;

两个进程在系统调用fork()之后开始执行。由于两个进程具有相同但独立的地址空间,因此在fork()调用之前初始化的变量在两个地址空间中具有相同的值。由于每个进程都有自己的地址空间,任何修改都将与其他进程无关。换句话说,如果父进程更改其变量的值,则修改仅会影响父进程地址空间中的变量。由fork()调用创建的其他地址空间不会受到影响,即使它们具有相同的变量名。

为什么使用write而不是printf?这是因为printf()是“缓冲”的,意味着printf()将一个进程的输出分组在一起。在为父进程缓冲输出时,子进程也可以使用printf打印一些信息,这些信息也将被缓冲。因此,由于输出不会立即发送到屏幕上,您可能无法得到预期结果的正确顺序。更糟糕的是,两个进程的输出可能以奇怪的方式混合在一起。为了解决这个问题,您可以考虑使用“非缓冲”写入。

如果您运行此程序,则可能会在屏幕上看到以下内容:

................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
     ................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
     ................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
     ................

进程ID 3456可能是分配给父进程或子进程的ID。由于这些进程是同时运行的,它们的输出行以一种相当不可预测的方式交织在一起。此外,这些行的顺序是由CPU调度程序确定的。因此,如果再次运行此程序,则可能会得到完全不同的结果。


7
不必复制粘贴文本,你可以评论一个链接:http://www.csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html - chaitanya lakkundi

4

如果您编写应用程序,日常编程中可能不需要使用fork。

即使您想让程序启动另一个程序来完成某些任务,也有其他更简单的接口在幕后使用fork,例如C和Perl中的“system”。

例如,如果您希望应用程序启动另一个程序(如bc)为您进行一些计算,可以使用“system”运行它。 System执行“fork”以创建新进程,然后执行“exec”将该进程转换为bc。 一旦bc完成,system将控制返回到您的程序。

您还可以异步运行其他程序,但我记不清怎么做了。

如果您正在编写服务器、shell、病毒或操作系统,则更有可能需要使用fork。


感谢您使用 system()。我正在阅读关于 fork() 的内容,因为我想让我的 C 代码运行一个 Python 脚本。 - Bean Taxi

3

Fork()是用于创建新进程的函数,这一点已经被广泛写入文献中。

以下是我编写的创建二叉树形式进程的代码......它会要求您输入要创建的二叉树进程的级数。

#include<unistd.h> 
#include<fcntl.h> 
#include<stdlib.h>   
int main() 
{
int t1,t2,p,i,n,ab;
p=getpid();                
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);                
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)    
{        
    t1=fork();

    if(t1!=0)
        t2=fork();        
    if(t1!=0 && t2!=0)        
        break;            
    printf("child pid %d   parent pid %d\n",getpid(),getppid());fflush(stdout);
}   
    waitpid(t1,&ab,0);
    waitpid(t2,&ab,0);
return 0;
}

输出

  enter the number of levels
  3
  root 20665
  child pid 20670   parent pid 20665
  child pid 20669   parent pid 20665
  child pid 20672   parent pid 20670
  child pid 20671   parent pid 20670
  child pid 20674   parent pid 20669
  child pid 20673   parent pid 20669

3

Fork命令可以创建新的进程。如果没有fork,你只能运行init系统。


3

多进程对于计算机而言是至关重要的。例如,在您浏览互联网时,IE或Firefox可以创建一个进程来为您下载文件。或者,在您在文字处理器中打印文档时,您仍然可以查看不同的页面并对其进行一些编辑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接