在Unix中,每当我们想要创建一个新进程时,我们会fork当前进程,创建一个与父进程完全相同的新子进程;然后我们执行 exec 系统调用以将来自父进程的所有数据替换为新进程的所有数据。
为什么我们首先创建父进程的副本而不是直接创建新进程?
最近我一直在挖掘有关 GNU/Linux 进程的信息,我遇到了臭名昭著的 fork 炸弹:
:(){ : | :& }; :
Run Code Online (Sandbox Code Playgroud)
理论上,它应该无限复制自己,直到系统耗尽资源......
但是,我已经尝试在CLI Debian和GUI Mint发行版上进行测试,它似乎对系统影响不大。是的,创建了大量进程,一段时间后我在控制台消息中阅读了如下内容:
bash: fork: 资源暂时不可用
bash: fork: 重试: 没有子进程
但是一段时间后,所有进程都会被杀死,一切都恢复正常。我读过ulimit为每个用户设置了最大进程数,但我似乎无法将其提高到很远。
什么是针对叉形炸弹的系统保护?为什么在一切都冻结或至少滞后很多之前它不会自我复制?有没有办法用叉子炸弹真正使系统崩溃?
在程序 1 中Hello world
只打印一次,但是当我删除 \n
并运行它时(程序 2),输出被打印了 8 次。有人可以向我解释一下\n
这里的重要性以及它如何影响fork()
?
方案一
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello world...\n");
fork();
fork();
fork();
}
Run Code Online (Sandbox Code Playgroud)
输出 1:
hello world...
Run Code Online (Sandbox Code Playgroud)
方案二
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello world...");
fork();
fork();
fork();
}
Run Code Online (Sandbox Code Playgroud)
输出 2:
hello world... hello world...hello world...hello world...hello world...hello world...hello world...hello world...
Run Code Online (Sandbox Code Playgroud) 在基于 Unix 的系统上部署服务时,从系统管理员的角度来看有哪些实际差异?
用于进程创建的 UNIX 系统调用 fork() 通过复制父进程来创建子进程。我的理解是,这之后几乎总是调用 exec() 来替换子进程的内存空间(包括文本段)。在 fork() 中复制父级的内存空间对我来说总是很浪费(尽管我意识到可以通过使内存段在写时复制从而只复制指针来最小化浪费)。无论如何,有谁知道为什么流程创建需要这种复制方法?
我正在 Arch Linux(内核 4.3.3-2)上运行一个带有多个容器的 docker 服务器。自从我上次重新启动后,容器内的 docker 服务器和随机程序都崩溃了,并显示一条关于无法创建线程或(较少)fork 的消息。具体的错误信息因程序而异,但大多数似乎都提到了具体的错误Resource temporarily unavailable
。有关一些示例错误消息,请参见本文末尾。
现在有很多人收到了这个错误信息,并且有很多人对他们做出了回应。真正令人沮丧的是,每个人似乎都在猜测如何解决问题,但似乎没有人指出如何确定存在问题的众多可能原因中的哪一个。
我收集了这 5 个可能的错误原因以及如何验证它们不存在于我的系统中:
/proc/sys/kernel/threads-max
( source ) 中配置的线程数有系统范围的限制。在我的情况下,这设置为60613
.ulimit -s
( source )配置的。我的壳的极限曾经是8192
,但我已经通过将增加其* soft stack 32768
成/etc/security/limits.conf
,因此它ulimit -s
现在的回报32768
。我也增加了它的码头工人,过程将LimitSTACK=33554432
进入/etc/systemd/system/docker.service
(源,和我核实,该限制适用通过查看/proc/<pid of docker>/limits
,并通过运行ulimit -s
一个泊坞窗容器内。ulimit -v
. 在我的系统上,它设置为unlimited
,并且我的 3 GB 内存中有 80% 是空闲的。ulimit -u
。在这种情况下,线程算作进程(源)。在我的系统上,限制设置为30306 …
我研究 Linux 内核行为已经有一段时间了,我一直很清楚:
当一个进程终止时,它的所有子进程都会返回给该
init
进程(PID 1),直到它们最终终止。
然而,最近,一个比我在内核方面更有经验的人告诉我:
当一个进程退出时,它的所有子进程也会死亡(除非你
NOHUP
在这种情况下使用它们返回到init
)。
现在,即使我不相信这一点,我仍然编写了一个简单的程序来确保它。我知道我不应该依赖时间 ( sleep
) 进行测试,因为它完全取决于进程调度,但对于这个简单的情况,我认为这已经足够了。
int main(void){
printf("Father process spawned (%d).\n", getpid());
sleep(5);
if(fork() == 0){
printf("Child process spawned (%d => %d).\n", getppid(), getpid());
sleep(15);
printf("Child process exiting (%d => %d).\n", getppid(), getpid());
exit(0);
}
sleep(5);
printf(stdout, "Father process exiting (%d).\n", getpid());
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
这是程序的输出,ps
每次printf
通话时都有相关的结果:
$ ./test &
Father process spawned (435).
$ ps -ef | grep test
myuser 435 392 tty1 …
Run Code Online (Sandbox Code Playgroud) 当我连接到我的服务器时,
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: Resource temporarily unavailable
Run Code Online (Sandbox Code Playgroud)
我也尝试遵循命令,结果是一样的。
-bash-4.1$ df -h
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: Resource temporarily unavailable
-bash-4.1$
-bash-4.1$ ls -lrth
-bash: fork: retry: Resource temporarily unavailable
-bash: fork: retry: Resource temporarily unavailable
-bash: …
Run Code Online (Sandbox Code Playgroud) 我对 fork 和 clone 有一些困惑。我已经看到:
fork 用于进程,clone 用于线程
fork 只是调用clone,clone 用于所有进程和线程
这两个都准确吗?这两个系统调用与 2.6 Linux 内核有什么区别?
一个fork()
系统调用克隆会从正在运行的进程子进程。这两个进程除了它们的 PID 之外是相同的。
当然,如果进程只是从它们的堆中读取而不是写入,那么复制堆将是对内存的巨大浪费。
整个进程堆是否被复制?它是否以仅写入触发堆复制的方式进行了优化?