【问题标题】:What is the purpose of fork()?fork() 的目的是什么?
【发布时间】:2010-11-02 08:56:08
【问题描述】:

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

【问题讨论】:

  • 这样所有用餐的哲学家都不会饿死。

标签: c unix posix fork


【解决方案1】:

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

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

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

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

如果您想要更多示例和更长的解释,Wikipedia 有一个不错的摘要。 here are some slides 在这里了解进程、线程和并发如何在现代操作系统中工作。

【讨论】:

  • 奇怪的是,它被称为 CreateProcess() - 那些疯狂的 Windows 家伙 :-)
  • 直到现在才意识到“shell 使用 fork 来运行你从命令行调用的程序”!
  • Slides 的链接已损坏
  • 所有答案都说fork()是在UNIX中创建新进程的方法,但要学究起来,至少还有一个:posix_spawn()
  • 但是,有趣的是,使用posix_spawn 在为 cygwin 编译时可能会更有效率,或者需要更少的模拟工作,让它只使用 CreateProcess 而不是伪造一个 fork。
【解决方案2】:

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

例如,如果你在shell中键入以下内容,shell程序会调用fork(),然后在child中执行你传递的命令(telnetd,在这种情况下),而parent会再次显示提示,以及指示后台进程的PID的消息。

$ telnetd &

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

【讨论】:

  • @varDumper 好收获!
【解决方案3】:

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

如果返回值为 0,你就知道你是子进程,如果返回值是一个数字(恰好是子进程 id),你就知道你是父进程。 (如果是负数,fork 失败,没有创建子进程)

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

【讨论】:

  • 除非返回值为-1,这种情况下fork()失败。
【解决方案4】:

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

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

这样您可以为父子节点提供不同的操作,并利用多线程特性。

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 为什么要创建一个与父级相同的子级有什么用?
    • 这就像组建一支军队对一名士兵。你 fork 是为了让你的程序可以同时处理更多的请求,而不是一一处理。
    • fork() 在子节点上返回 0,在父节点上返回子节点的 pid。然后,孩子可以使用类似 exec() 的调用将其状态替换为新程序。这就是程序的启动方式。
    • 过程非常接近相同,但有很多细微差别。明显的区别是当前 PID 和父 PID。存在与持有的锁和持有的信号量相关的问题。 POSIX 的 fork() 手册页列出了父子节点之间的 25 个差异。
    • @kar:一旦你有两个进程,它们就可以从那里分开继续,其中一个可以完全用另一个程序替换自己 (exex())。
    【解决方案6】:

    系统调用 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】:

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

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

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

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

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

    【讨论】:

    • 感谢system()。我正在阅读有关 fork() 的信息,因为我希望我的 C 代码运行 python 脚本。
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

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

      【讨论】:

        【解决方案10】:

        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
        

        【讨论】:

          【解决方案11】:

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

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

          2. fork系统调用以copy-on-write的方式复制内存,即当需要复制时,子进程在虚拟内存页中创建。

          现在 fork() 的用途:

          1. fork() 可以用在有分工的地方,比如一个服务器要处理多个客户端,所以父级必须定期接受连接,所以服务器会为每个客户端分叉执行读写.

          【讨论】:

            【解决方案12】:

            fork() 用于生成子进程。通常它用于与线程类似的情况,但也有区别。与线程不同,fork() 创建了完全独立的进程,这意味着子进程和父进程在调用fork() 时是彼此的直接副本,但它们是完全独立的,都不能访问对方的内存空间(无需去访问另一个程序的内存的正常麻烦)。

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

            【讨论】:

            • 我不理解“大多数人”已经转向多线程的看法。进程将保留,线程也将保留。没有人从这两者中“继续前进”。在并行编程中,最大和最多的并发代码是分布式内存多进程程序(例如 MapReduce 和 MPI)。尽管如此,大多数人还是会为多核机器选择 OpenMP 或一些共享内存范例,而 GPU 现在正在使用线程,但除此之外还有很多。不过,我敢打赌,这个网站上的程序员在服务器端遇到的进程并行性比多线程的要多。
            【解决方案13】:

            an answer to a similar question on the unix stack exchange 解释了 fork() 与仅使用 exec() 函数来启动新进程的基本原理。

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

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

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

            【讨论】:

              【解决方案14】:

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

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

              【讨论】:

                【解决方案15】:

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

                • 与运行在 操作系统。

                • 子进程的父进程 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]
                

                【讨论】:

                  猜你喜欢
                  • 2016-05-04
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-06-16
                  • 1970-01-01
                  • 2010-11-02
                  • 1970-01-01
                  • 2015-06-24
                  相关资源
                  最近更新 更多