【发布时间】:2015-12-09 23:58:21
【问题描述】:
在“操作系统概念”一书 - 第 9 版 - 第 3 章 - 第 117 页 - 第 120 页中说:
两个进程(父进程和子进程)在 fork() 之后的指令处继续执行,但有一个区别:fork() 的返回码对于新的(子)进程为零,而(非零)子进程标识符返回给父进程。
唯一的区别是子进程的pid(进程标识符)的值为零,而父进程的pid是大于零的整数值(实际上是子进程的实际pid) .
请有人向我解释一下这个概念。
【问题讨论】:
在“操作系统概念”一书 - 第 9 版 - 第 3 章 - 第 117 页 - 第 120 页中说:
两个进程(父进程和子进程)在 fork() 之后的指令处继续执行,但有一个区别:fork() 的返回码对于新的(子)进程为零,而(非零)子进程标识符返回给父进程。
唯一的区别是子进程的pid(进程标识符)的值为零,而父进程的pid是大于零的整数值(实际上是子进程的实际pid) .
请有人向我解释一下这个概念。
【问题讨论】:
每当处理器(CPU)运行程序时,它都会存储它正在执行的代码的行号(更正式的地址指向该指令)。计算机将其存储在称为堆栈指针的寄存器(一种变量)中.因此堆栈指针(SP)将存储当前处理器必须执行(或运行)的指令。这样计算机会跟踪应该执行哪条指令。
当一个程序运行时,它在计算机的主内存中分配了一些小内存。这里是我们所有的代码以及重要的寄存器(包括 SP),它们帮助处理器在运行时跟踪程序。一个进程也是唯一的由进程 ID(PID) 标识。
现在让我来回答你的问题。每当我们调用 fork 时,都会创建您调用 fork 的程序的副本。这个副本称为“子进程”,我们的原始进程称为父进程。
创建副本时,您的程序已分配的所有内存都将复制到内存中的其他位置(现在是子进程的内存)。因此创建了一个相同的运行程序(进程)。
现在这个复制的内存包含了父进程的 SP,所以每当处理器运行程序时,它都会直接从同一个 fork 调用行运行程序(因为在创建进程时,SP 会将此行存储为当前指令)。因为我们的fork调用成功了,它必须返回一个非负值(表示fork系统调用成功)所以它返回0给子进程和子进程ID给父进程(因为当前进程ID也指向这里)。
将子进程 ID 返回给父进程很划算,因为父进程可以跟踪从它创建的子进程。返回子进程 0 会使交易变得更好,因为我们必须返回一个非负数并且其他正数可能是某个进程的PID。
在您的 linux 系统中运行 cd /proc 。所有具有一些数字名称的目录都是某个进程的 pid(可能是活动的/非活动的)。阅读有关它的更多信息以清除概念。
希望能消除您的疑虑 :)。
【讨论】:
当您在代码中调用 fork 时,它会返回两个值,以防调用成功,一个用于父(父运行的代码),一个用于子(由子运行的代码)
int childparent;
childparent = fork();
if (childparent >=0)
{
if (childparent==0)
//child code
if (childparent>0)
//parent code
}
你句子中提到的pid 0不是shell命令ps显示的进程id
编辑:是的,在父级(如果 childparent>0)案例中运行的代码是在刚刚创建的特定子级的上下文中运行的。所以返回给父级的值是子级的实际进程 ID(PID)。如果你在你的简单代码中分叉并在足够长的代码中休眠以运行 ps 你可以匹配 PS 中显示的 PID 和 parent 中的 printf fork() 的返回值 (printf("%d",childparent))
【讨论】:
fork() 通过创建一个新进程然后用当前进程填充它来制作当前进程的完整副本。它选择一个作为父母,另一个作为孩子。仅通过调整fork()的返回值来表示差异。如果进程忽略返回值,它们的行为相同。 (与信号处理和传递、使用特殊属性打开的文件描述符、内存映射等相关的进程之间存在一些很少显着的其他差异。)
当您认为自己开始理解它时,请查看this 看看您是否理解。
【讨论】:
我在看书的时候也有这个疑问。答案如下:
当主程序(父程序)执行 fork() 时,会创建其地址空间的副本,包括程序和所有数据。系统调用fork()将子进程ID返回给父进程,将0返回给子进程。父进程和子进程现在都可以从 fork 系统调用后的下一行开始执行。 让我用一个简单的例子来说明这一点。
考虑这段代码:
main()
{
pid=fork();
if(pid == 0) // Condition to determine Parent/Child Process
ChildProcess();
else
ParentProcess();
}
void ChildProcess()
{
//Some Arbitrary Code
}
void ParentProcess()
{
//Some Arbitrary Code
}
这个 sn-p 解释说,基于一个条件,两个进程(父进程和子进程)现在都可以以它们自己的预定义方式执行。
在上面的例子中,假设子进程的 id 是 3456,那么父进程会从 fork 中获取该 id 作为返回值。但是,子进程总是将 process-id 设为 0,然后继续执行。
fork() 调用的设计是因为子进程的完整管理现在可以由父进程处理,并且父进程可以随时记录哪些子进程正常或异常终止,通过隐式/显式调用 exit() 系统调用。父母也可以简单地通过 wait() 系统调用等待孩子,然后返回孩子的 pid,这样,父母可以记录哪个孩子已经终止。
这是处理子进程创建和终止的方式。
我还想在这里补充一件事,这与问题并不完全相关,但我认为会有所帮助。
在本书的讨论之后,你也会注意到 exec() 系统调用的描述。简而言之,两个讨论都解释了这一点:
分叉为现有进程提供了一种启动新进程的方法,但 新流程不属于同一流程的情况如何? 程序作为父进程? shell中就是这种情况;当一个用户 启动它需要在新进程中运行的命令,但它不相关 到外壳。
这就是 exec 系统调用发挥作用的地方。 exec 将替换 当前运行进程的内容与信息 来自程序二进制文件。
因此,shell 在启动新程序时遵循的过程是 首先 fork,创建一个新进程,然后执行(即加载到 内存并执行)它应该运行的程序二进制文件。
如果您想了解更多关于 fork() 系统调用的信息,那么您还应该了解它的内部实现,尤其是 clone() 系统调用的工作原理。
参考网站:
【讨论】: