在Linux的许多程序和手册页中,我已经看到了使用的代码fork()
。我们为什么需要使用fork()
及其目的是什么?
Answers:
fork()
在Unix中创建新进程的方式。调用时fork
,您将创建自己的进程的副本,该副本具有自己的地址空间。这样一来,多个任务就可以彼此独立运行,就好像它们各自拥有机器的全部内存一样。
以下是一些示例用法fork
:
fork
运行从命令行调用的程序。fork
,以创建多个服务器进程,每个处理在自己的地址空间的请求。如果一个人死亡或泄漏内存,其他人则不受影响,因此它起着容错机制的作用。fork
在单独的流程中处理每个页面。这样可以防止一页上的客户端代码使整个浏览器崩溃。fork
用于在某些并行程序(如使用MPI编写的程序)中生成进程。注意,这与使用线程不同,后者没有自己的地址空间并且存在于进程中。fork
间接使用启动子进程。例如,每当您使用subprocess.Popen
Python中的命令时,您将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的摘要不错。而且这里有一些幻灯片这里如何进程,线程和并发在现代操作系统的工作。
fork()
是在UNIX中创建新进程的方式,但要学究一点,至少还有一个:posix_spawn()
。
fork()是Unix如何创建新进程的方法。在您称为fork()的点上,您的进程已克隆,并且两个不同的进程从此处继续执行。其中一个,子级,将使fork()返回0。另一个,父级,将使fork()返回子级的PID(进程ID)。
例如,如果在外壳中键入以下内容,则外壳程序将调用fork(),然后在子代中执行您传递的命令(在这种情况下为telnetd),而父代也将再次显示提示作为指示后台进程的PID的消息。
$ telnetd &
由于创建新进程的原因,这就是操作系统可以同时执行许多操作的方式。这就是为什么您可以运行一个程序,并在其运行时切换到另一个窗口并执行其他操作的原因。
fork()用于创建子进程。调用fork()函数时,将产生一个新进程,并且fork()函数调用将为子代和父代返回不同的值。
如果返回值为0,则说明您是子进程,如果返回值为数字(恰好是子进程ID),则说明您为父进程。(如果它是负数,则派生失败并且未创建任何子进程)
fork()将创建一个与父进程相同的新子进程。因此,之后在代码中运行的所有内容都将同时由两个进程运行-如果您拥有一台服务器,并且想要处理多个请求,则非常有用。
如果要编写应用程序,则在日常编程中可能不需要使用fork。
即使您确实希望您的程序启动另一个程序来执行某些任务,也有其他更简单的接口在后台使用fork,例如C和perl中的“系统”。
例如,如果您希望应用程序启动另一个程序(例如bc)来为您做一些计算,则可以使用“系统”来运行它。系统先执行“ fork”以创建新进程,然后执行“ exec”以将该进程转换为BC。BC完成后,系统将控制权返回给您的程序。
您也可以异步运行其他程序,但我不记得如何。
如果要编写服务器,外壳,病毒或操作系统,则更可能要使用fork。
system()
。我正在阅读有关内容,fork()
因为我希望我的C代码运行python脚本。
系统调用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调度程序确定。因此,如果再次运行该程序,则可能会得到完全不同的结果。
每个主体都使用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
首先需要了解什么是fork()系统调用。让我解释
fork()系统调用创建父进程的完全相同的副本,它使父堆栈,堆,已初始化的数据,未初始化的数据完全相同,并以只读模式与父进程共享代码。
Fork系统调用在写时复制的基础上复制内存,这意味着当需要复制时,子进程会在虚拟内存页面中创建。
现在,fork()的用途:
fork()
用于生成子进程。通常,它在与线程类似的情况下使用,但有区别。与线程不同,它fork()
创建了完全独立的过程,这意味着子代和父代在彼此直接复制的点上fork()
被调用是,但它们是完全分开的,它们都无法访问彼此的内存空间(不会造成正常的麻烦)您可以访问另一个程序的内存)。
fork()
仍然被某些服务器应用程序使用,大多数服务器应用程序是在* NIX机器上以root身份运行的,在处理用户请求之前会删除权限。仍然还有其他用例,但是现在大多数人已经转向多线程。
在对unix堆栈交换中类似问题的回答中,解释了fork()背后的原理以及仅具有exec()函数来启动新进程的原理。
本质上,由于fork复制当前进程,因此默认情况下会为该进程建立所有各种可能的选项,因此程序员没有提供它们。
相比之下,在Windows操作系统中,程序员必须使用CreateProcess函数,该函数要复杂得多,并且需要填充多种结构来定义新进程的参数。
因此,总而言之,分叉(相对于执行)的原因是创建新流程的简便性。
Fork()系统调用用于创建子进程。它与父进程完全相同。Fork从父级复制堆栈部分,堆部分,数据部分,环境变量,命令行参数。
所述叉()函数是用于创建通过复制现有过程从其中它被称为一个新进程。从中调用此函数的现有进程成为父进程,而新创建的进程成为子进程。如前所述,子级是父级的重复副本,但是有一些例外。
子级具有唯一的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]