fork()的作用是什么?


87

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


150
这样,所有餐饮哲学家都不会挨饿。
kenj0418

Answers:


106

fork()在Unix中创建新进程的方式。调用时fork,您将创建自己的进程的副本,该副本具有自己的地址空间。这样一来,多个任务就可以彼此独立运行,就好像它们各自拥有机器的全部内存一样。

以下是一些示例用法fork

  1. 您的Shell用于fork运行从命令行调用的程序。
  2. Web服务器像Apache的使用fork,以创建多个服务器进程,每个处理在自己的地址空间的请求。如果一个人死亡或泄漏内存,其他人则不受影响,因此它起着容错机制的作用。
  3. Google Chrome浏览器用于fork在单独的流程中处理每个页面。这样可以防止一页上的客户端代码使整个浏览器崩溃。
  4. fork用于在某些并行程序(如使用MPI编写的程序)中生成进程。注意,这与使用线程不同,后者没有自己的地址空间并且存在进程中。
  5. 脚本语言fork间接使用启动子进程。例如,每当您使用subprocess.PopenPython中的命令时,您将fork成为一个子进程并读取其输出。这使程序可以一起工作。

fork在shell中的典型用法如下所示:

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

外壳程序使用生成子进程,exec并等待其完成,然后继续其自身的执行。请注意,您不必以这种方式使用fork。您总是可以产生许多子进程,就像并行程序可能会这样做一样,并且每个子进程可能会同时运行一个程序。基本上,每当您在Unix系统中创建新进程时,都在使用fork()。对于Windows等效版本,请查看CreateProcess

如果您需要更多示例和更长的解释,Wikipedia的摘要不错。而且这里有一些幻灯片这里如何进程,线程和并发在现代操作系统的工作。


项目符号5:“经常”?只有“经常”?哪些不使用它,或者在什么情况下不使用fork()-在支持fork()的系统上。
乔纳森·莱夫勒

19
奇怪的是,它被称为CreateProcess()-那些疯狂的Windows家伙:-)
paxdiablo

2
从来没有意识到,“ shell使用fork运行从命令行调用的程序”!
Lazer

1
幻灯片的链接已断开
Piertoni

1
所有答案都表明,这fork()是在UNIX中创建新进程的方式,但要学究一点,至少还有一个:posix_spawn()
戴维斯洛

15

fork()是Unix如何创建新进程的方法。在您称为fork()的点上,您的进程已克隆,并且两个不同的进程从此处继续执行。其中一个,子级,将使fork()返回0。另一个,父级,将使fork()返回子级的PID(进程ID)。

例如,如果在外壳中键入以下内容,则外壳程序将调用fork(),然后在子代中执行您传递的命令(在这种情况下为telnetd),而父代也将再次显示提示作为指示后台进程的PID的消息。

$ telnetd &

由于创建新进程的原因,这就是操作系统可以同时执行许多操作的方式。这就是为什么您可以运行一个程序,并在其运行时切换到另一个窗口并执行其他操作的原因。


@varDumper好抓住!
Daniel C. Sobral '18

9

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

如果返回值为0,则说明您是子进程,如果返回值为数字(恰好是子进程ID),则说明您为父进程。(如果它是负数,则派生失败并且未创建任何子进程)

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


1
除非返回值为-1,否则fork()将失败。
乔纳森·勒夫勒

8

fork()基本上用于为调用此函数的进程创建一个子进程。每当您调用fork()时,它都会为子ID返回零。

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

这样,您可以为父级和子级提供不同的操作,并利用多线程功能。


6

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


你为什么要创建一个与父母相同的孩子,有什么用?

1
就像建立一支军队与一名士兵。您进行分叉,以便程序可以同时处理更多请求,而不是一个接一个地处理。
cloudhead 2009年

fork()在子代上返回0,在父代上返回子代的pid。然后,孩子可以使用exec()之类的调用将其状态替换为新程序。这是程序启动的方式。
Todd Gamblin

这些过程非常接近相同,但是存在许多细微的差异。明显的差异是当前PID和父PID。存在与持有锁和持有信号灯有关的问题。POSIX的fork()手册页列出了父级和子级之间的25个差异。
乔纳森·勒夫勒

2
@kar:一旦有了两个进程,它们可以从那里继续分开,其中一个可以将自己(exex())完全替换为另一个程序。
Vatine 2010年

4

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

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

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

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

如果要编写服务器,外壳,病毒或操作系统,则更可能要使用fork。


谢谢你system()。我正在阅读有关内容,fork()因为我希望我的C代码运行python脚本。
豆出租车2016年

4

叉子创造了新的过程。如果没有fork,您将拥有只能运行init的unix系统。


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


3
除了复制粘贴文本,您还可以评论以下链接:csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html
chaitanya lakkundi

3

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


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

2

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

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

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

现在,fork()的用途:

  1. Fork()可以在存在工作分工的地方使用,例如服务器必须处理多个客户端,因此父级必须定期接受连接,因此服务器会分叉给每个客户端执行读写操作。

1

fork()用于生成子进程。通常,它在与线程类似的情况下使用,但有区别。与线程不同,它fork()创建了完全独立的过程,这意味着子代和父代在彼此直接复制的点上fork()被调用是,但它们是完全分开的,它们都无法访问彼此的内存空间(不会造成正常的麻烦)您可以访问另一个程序的内存)。

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


2
我不理解“大多数人”已经转向多线程的看法。进程就在这里,线程也在这里。没有人从任何一个“继续前进”。在并行编程中,最大和最多的并发代码是分布式内存多进程程序(例如MapReduce和MPI)。尽管如此,大多数人还是会选择OpenMP或一些用于多核计算机的共享内存范例,而GPU目前正在使用线程,但除此之外,还有很多。不过,我敢打赌,这个站点上的程序员在服务器端遇到的进程并行性要高于任何多线程。
Todd Gamblin,2009年

1

在对unix堆栈交换中类似问题的回答中,解释了fork()背后的原理以及仅具有exec()函数来启动新进程的原理。

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

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

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


0

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

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


0

所述叉()函数是用于创建通过复制现有过程从其中它被称为一个新进程。从中调用此函数的现有进程成为父进程,而新创建的进程成为子进程。如前所述,子级是父级的重复副本,但是有一些例外。

  • 子级具有唯一的PID,就像操作系统中运行的任何其他进程一样。

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

  • 在子进程中,资源利用率和CPU时间计数器被重置为零。

  • 子项中的未决信号集为空。

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

范例:

    #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]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.