fork()的目的是什么?

时间:2009-06-12 04:49:06

标签: c unix posix fork

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

16 个答案:

答案 0 :(得分:102)

fork()是如何在Unix中创建新进程的。当您致电fork时,您正在创建自己的流程副本,该流程有自己的address space。这允许多个任务彼此独立地运行,就好像它们各自拥有机器的全部内存一样。

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

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

    如果您想要更多示例和更长的解释,here are some slides有一个不错的摘要。并且{{3}}在这里讨论了现代操作系统中进程,线程和并发的工作方式。

答案 1 :(得分:13)

fork()是Unix如何创建新进程的。在您调用fork()时,将克隆您的进程,并从那里继续执行两个不同的进程。其中一个,子,将fork()返回0.另一个,父,将fork()返回子的PID(进程ID)。

例如,如果在shell中键入以下内容,shell程序将调用fork(),然后执行您在子项中传递的命令(在本例中为telnetd),而父项将再次显示提示,以及指示后台进程的PID的消息。

$ telnetd &

至于创建新流程的原因,这就是您的操作系统可以同时执行多项操作的方式。这就是为什么你可以运行一个程序,当它运行时,切换到另一个窗口并做其他事情。

答案 2 :(得分:9)

fork()用于创建子进程。当调用fork()函数时,将生成一个新进程,fork()函数调用将为子进程和父进程返回不同的值。

如果返回值为0,则表示您是子进程,如果返回值是数字(恰好是子进程ID),则表示您是父进程。 (如果它是负数,则fork失败并且没有创建子进程)

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

答案 3 :(得分:6)

fork()将创建一个与父级相同的新子进程。因此,在此之后您在代码中运行的所有内容都将由两个进程运行 - 如果您拥有例如服务器,并且您希望处理多个请求,则非常有用。

答案 4 :(得分:6)

fork()主要用于为调用此函数的进程创建子进程。每当你调用一个fork()时,它会为子id返回一个零。

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

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

答案 5 :(得分:4)

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

即使您希望程序启动另一个程序来执行某项任务,还有其他更简单的接口在后台使用fork,例如C和perl中的“system”。

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

您也可以异步运行其他程序,但我不记得如何。

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

答案 6 :(得分:4)

Fork创建新流程。如果没有fork,你将拥有一个只能运行init的unix系统。

答案 7 :(得分:4)

系统调用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调度程序确定。因此,如果再次运行此程序,可能会得到完全不同的结果。

答案 8 :(得分:3)

多处理是计算的核心。例如,当您仍在浏览互联网时,您的IE或Firefox可以为您创建一个下载文件的过程。或者,当您在文字处理器中打印文档时,您仍然可以查看不同的页面并仍然使用它进行一些编辑。

答案 9 :(得分: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

答案 10 :(得分:1)

fork()用于生成子进程。通常它用于类似线程的情况,但存在差异。与线程不同,fork()创建完整的单独进程,这意味着子进程和父进程在fork()被调用时彼此直接复制时,它们完全独立,无法访问其他人的记忆空间(没有去你正常的麻烦去访问另一个程序的记忆)。

fork()仍然被某些服务器应用程序使用,主要是在* NIX机器上以root身份运行,在处理用户请求之前删除权限。还有一些其他用例,但现在大多数人已经转向多线程。

答案 11 :(得分:1)

首先需要了解什么是fork()系统调用。让我解释一下

  1. fork()系统调用创建父进程的精确副本,它使父堆栈,堆,初始化数据,未初始化数据的副本与父进程以只读模式共享代码。

  2. Fork系统调用在写时复制的基础上复制内存,意味着当需要复制时,子进入虚拟内存页面。

  3. 现在fork()的目的:

    1. Fork()可以在有工作分工的地方使用,就像服务器必须处理多个客户端一样,所以父必须定期接受连接,所以服务器为每个客户端执行fork来执行读写

答案 12 :(得分:0)

Fork()系统调用用于创建子进程。它与父进程完全相同。 fork从父级复制堆栈部分,堆部分,数据部分,环境变量,命令行参数。

参考:http://man7.org/linux/man-pages/man2/fork.2.html

答案 13 :(得分:0)

fork()函数用于通过复制调用它的现有进程来创建新进程。调用此函数的现有进程将成为父进程,新创建的进程将成为子进程。如前所述,孩子是父母的副本,但也有一些例外。

  • 孩子有一个独特的PID,就像在其中运行的任何其他进程一样 操作系统。

  • 子进程ID与父进程ID相同 创建它的过程。

  • 资源利用率和CPU时间计数器在子级中重置为零 过程

  • 子项中的待处理信号集为空。

  • Child不会从其父级继承任何计时器

示例:

    #include <unistd.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <stdio.h>
    #include <sys/wait.h>
    #include <stdlib.h>

    int var_glb; /* A global variable*/

int main(void)
{
    pid_t childPID;
    int var_lcl = 0;

    childPID = fork();

    if(childPID >= 0) // fork was successful
    {
        if(childPID == 0) // child process
        {
            var_lcl++;
            var_glb++;
            printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
        else //Parent process
        {
            var_lcl = 10;
            var_glb = 20;
            printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
    }
    else // fork failed
    {
        printf("\n Fork failed, quitting!!!!!!\n");
        return 1;
    }

    return 0;
}

现在,编译并运行上面的代码时:

$ ./fork

Parent process :: var_lcl = [10], var_glb[20]

Child Process :: var_lcl = [1], var_glb[1]

答案 14 :(得分:0)

an answer to a similar question on the unix stack exchange中解释了fork()背后的原理以及仅具有exec()函数来启动新进程的原理。

本质上,由于fork复制当前进程,因此默认情况下会为该进程建立所有各种可能的选项,因此程序员没有提供它们。

相比之下,在Windows操作系统中,程序员必须使用CreateProcess函数,该函数要复杂得多,并且需要填充各种各样的结构来定义新进程的参数。

因此,总而言之,进行分叉(相对于执行)的原因是创建新流程非常简单。

答案 15 :(得分:0)

Fork系统调用用于创建一个称为子进程的新进程,该子进程与该进程(该进程称为系统调用fork)同时运行,并且该进程称为父进程。

创建新的子进程后,两个进程将在fork()系统调用之后执行下一条指令。子进程使用与父进程相同的pc(程序计数器),CPU寄存器,打开文件。

在某些系统中,存在不同版本的fork() 取决于所需的行为

某些UNIX系统具有fork1()和forkall()

  • fork1()仅复制调用线程
  • forkall()复制一个进程中的所有线程