【问题标题】:Can someone explain fork()?有人可以解释 fork() 吗?
【发布时间】:2020-12-29 20:17:27
【问题描述】:

有人可以解释fork() 并举例说明它的用途吗?

我从一些在线资源的理解是:

  1. fork() 创建一个与父进程运行相同的子进程。
  2. 2^n 进程将被创建,n = fork() 的数量。
  3. 子进程的 ID 将始终为 0,而父进程的 ID 将是其他值 - 正整数!= 0。

我还有一个问题,首先请看我下面的代码:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main() {
    int id = fork();
    int i = 45;
    fork();
    fork();
    if(id == 0) {
        printf("\nChild Process id = %d",id);
        printf("\ni = %d ; i = %d",i++,i);  
    }
    else {
        printf("\nParent Process id = %d",id);
        printf("\ni = %d ; i = %d",i,i+2);
    }
    return 0;
}   
/*
O/P:
Parent Process id = 9048
i = 45 ; i = 47
Parent Process id = 9048                                                                                                                                                        
i = 45 ; i = 47
Parent Process id = 9048
i = 45 ; i = 47
Parent Process id = 9048
i = 45 ; i = 47
Child Process id = 0
i = 45 ; i = 46
Child Process id = 0
i = 45 ; i = 46
Child Process id = 0
i = 45 ; i = 46
Child Process id = 0
*/

为什么最后一个子进程不显示第二个 printf() 即,

printf("\ni = %d ; i = %d",i++,i);

你能解释一下为什么第二个 printf() 没有被执行,而第一个 printf() 第 8 次执行 - 子进程。

【问题讨论】:

  • 您的打印问题几乎肯定是由缓冲问题引起的,例如printf anomaly after “fork()”。其他一切都只是在此之上增加复杂性。
  • 你读过fork(2)几遍吗?使用errno(3)stdio(3)Advanced Linux Programming?你研究过GNU libc的源码吗?另见kernelnewbies.org
  • 请注意,执行printf("\ni = %d ; i = %d",i++,i); 的程序的行为不是由C 标准定义的,C 标准对程序的行为方式没有任何要求。按照C标准的规定,这个程序根本不需要fork;一旦启动main 例程,它可能会以其他方式陷入困境或失败。
  • @BasileStarynkevitch 我们不妨关闭 stackoverflow 并有一个单独的 html 页面列出要研究的资源。
  • @TonyTannous stackoverflow 的所有者当然可以关闭它。

标签: c linux fork


【解决方案1】:

您期望的所有输出都在那里,只是您在后台将其写出来而没有终止换行符。这意味着某些输出可能会被过早出现在程序输出中间的 shell 提示所掩盖或覆盖。

要确切了解这是如何实现的,下面是您程序中某些进程的strace 输出:

3215092 write(1, "\n", 1)               = 1
3215092 write(1, "Child Process id = 0\n", 21) = 21
3215092 write(1, "i = 45 ; i = 46", 15) = 15
3215094 write(1, "\n", 1)               = 1
3215094 write(1, "Child Process id = 0\n", 21) = 21
3215094 write(1, "i = 45 ; i = 46", 15) = 15

这就是 Zsh 如何绘制它的提示符。首先是缺少换行指示符,然后是提示符本身。特别注意提示以\r 开头,一个回车符:

3215036 write(10, "\33[1m\33[3m%\33[23m\33[1m\33[0m          "..., 306) = 306
3215036 write(10, "\r\33[0m\33[23m\33[24m\33[Jmyhostname.i"..., 34) = 34

由于您在后台派生了写入终端的进程,因此所有派生的进程都在与 Zsh 竞争以写入它们的输出。

这是一种可能的有效排序,这可能会导致:

1. 3215036 write(10, "\33[1m\33[3m%\33[23m\33[1m\33[0m          "..., 306) = 306
2. 3215092 write(1, "\n", 1)               = 1
3. 3215092 write(1, "Child Process id = 0\n", 21) = 21
4. 3215092 write(1, "i = 45 ; i = 46", 15) = 15
5. 3215094 write(1, "\n", 1)               = 1
6. 3215094 write(1, "Child Process id = 0\n", 21) = 21
7. 3215094 write(1, "i = 45 ; i = 46", 15) = 15
8. 3215036 write(10, "\r\33[0m\33[23m\33[24m\33[Jmyhostname.i"..., 34) = 34

步骤 1-7 进行得相当顺利,产生了这样的屏幕(标有 | 的光标):

[...]
i = 45 ; i = 46
Child Process id = 0
i = 45 ; i = 46|

由于您没有终止换行符,因此光标结束在最后一个字符串之后而不是在最后一个字符串之下。

现在,正如您在 write 8 中看到的那样,Zsh 以一个前导回车开始其默认提示。这意味着将转到行首:

[...]
i = 45 ; i = 46
Child Process id = 0
|i = 45 ; i = 46

然后继续使用提示覆盖该行:

[...]
i = 45 ; i = 46
Child Process id = 0
myhostname.internal% 

实际上,即使最后一行输出被写入屏幕,Zsh 提示符通过使用回车符覆盖它有效地删除了它。

您可以使用重定向输出到文件,以更清楚地查看您的程序产生的输出:

./foo > myfile

在编辑器中打开文件会显示您的预期结果:

Parent Process id = 3135806
i = 45 ; i = 47
Parent Process id = 3135806
i = 45 ; i = 47
Parent Process id = 3135806
i = 45 ; i = 47
Child Process id = 0
i = 45 ; i = 46
Parent Process id = 3135806
i = 45 ; i = 47
Child Process id = 0
i = 45 ; i = 46
Child Process id = 0
i = 45 ; i = 46
Child Process id = 0
i = 45 ; i = 46

您可以类似地添加自己的终止换行和延迟以在终端中显示输出:

./foo; sleep 5; echo

这给出了与上面相同的结果。

正确的程序会在 after 而不是 before 行输出换行符,并且会为每个子进程输出wait()。 (请注意,如果您只修复其中一个,而保留另一个,它将不起作用)

【讨论】:

  • 关于“您期望的所有输出都在那里”:显示的输出中没有八个“i = ...”行。一个好的答案应该要么解释为什么没有 8 个,要么诊断出 OP 未能显示所有输出(并解释这是如何发生的)。
  • @EricPostpischil 我不确定我是否理解。你是说“你的终端上的输出可能在外观上很混乱”太神秘了,它应该包括提示如何覆盖 tty 上部分背景输出的确切机制?
  • “化妆品混乱”不会让读者理解所发生的事情。说“输出已发送到终端,但命令行提示符会覆盖它”可以。但是,我觉得这很可疑,因为用户的程序不打印回车,并且 shell 提示并不常见(它们通常从一行的当前位置继续打印,因此提示会出现在程序输出之后和不会覆盖它)。要回答这个问题,应该解释和确认不是所有八行都出现的原因。
  • @thatotherguy:我试过 ./tfork; sleep 5; echo; 。确实,现在它才打印子进程的 最后一秒 printf() 语句。 但是,在将输出写入新文本文件(即/tfork &gt; myfile )时,它再次返回与post-last second printf() 语句给出的输出不会被打印/执行。
  • @thatotherguy:对不起,两个 shell 提示:./tfork; sleep 5; echo./tfork &gt; myfile 按预期打印输出 - 最后一秒 printf() 被打印。但是,为什么只是 ./tfork 没有最后第二个 printf() 语句?以及为什么您的 shell 提示是否按预期正确?
【解决方案2】:

fork 的正确用法意味着立即测试当前进程是子进程(fork 返回 0)还是父进程(fork 返回子进程的 pid),并采取相应措施。在您的情况下,父母和孩子都启动了一对或虚假的进程,这些进程将无法知道它们是什么,因为返回的值被忽略了。他们中的一些人会认为他们是父进程。

如果必须做一些严肃的事情,通常子进程会执行一个新的可执行文件。

【讨论】:

  • 这并没有解决“你能解释一下为什么第二个 printf() 没有被执行,而第一个 printf() 第 8 次执行”的问题。
  • @EricPostpischil 它没有。但是,如果您可以解释没有发生的事情,请随时添加您自己的答案。第二个 printf 显然确实被执行了——那怎么解释它没有呢?
【解决方案3】:

fork() 创建与父进程相同运行的子进程。

没错。 fork() 创建一个子进程,它是它的父进程的副本。


将创建 2^n 个进程,n = fork() 的数量。

取决于代码!

int i = 0;
while (i < n)
{
    fork();
    i++;
}

这里假设没有fork()失败,那么是的,会有2^n个进程。


子进程的 ID 将始终为 0,而父进程的 ID 将是其他值 - 正整数!= 0。

子进程中的fork()返回值为0!不是id,而是返回值! 父进程中fork()的返回值是子进程的实际pid


现在到你的代码

1. int main() {
2.    int id = fork(); 
3.    int i = 45;
4.    fork();
5.    fork();
6.    if(id == 0) {
7.        printf("\nChild Process id = %d",id);
8.        printf("\ni = %d ; i = %d",i++,i);  
9.    }
10.   else {
11.       printf("\nParent Process id = %d",id);
12.       printf("\ni = %d ; i = %d",i,i+2);
13.   }
14.   return 0;
15. } 

在第 4 行,有 2 个进程

parent         |     child_1
    id > 0     |     id == 0
    i == 45    |     i == 45     

现在调用了一个分叉

    parent     |     child_1    |    child_OF_1   |   child_2
    id > 0     |     id == 0    |    id == 0      |   id > 0
    i == 45    |     i == 45    |    i == 45      |   i == 45

child_1child_OF_1 将有 id == 0 并输入 if 语句,而 child_2parent 将转到 else 语句并打印 47

我忽略了第三个 fork(),那时你有 8 个进程,其中 4 个将进入 if 语句,而其他将进入 else 块。这就是为什么 你看

i = 45 ; i = 47

4 次!

【讨论】:

  • 这并没有解决“你能解释一下为什么第二个 printf() 没有被执行,而第一个 printf() 第 8 次执行”的问题。
  • @EricPostpischil 我正在做这部分工作。该问题包含多个问题,如果您认为我的问题不够好,请随时添加您自己的答案。
  • 我根据答案中的文本投票,而不是在未来的答案草稿中的假设文本。假设,如果这个答案被更新为好的,它将有一个假设的未来投票。
  • @EricPostpischil 我从来没有要求过投票。
  • @TonyTannous 感谢您对 fork() 的解释,现在我理解得更好了。你能说一下 fork() 的一些用途吗?我要问一个相当愚蠢的问题:我们可以使用 fork() 代替循环来多次打印相同的语句吗?例如,fork(); fork(); fork(); printf("\nText printed"); 而不是 for(i=0;i&lt;7;i++) printf("\Text printed");
猜你喜欢
  • 2012-05-29
  • 2010-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多