Linux 进程生命周期是如何工作的——父进程、子进程和初始化进程
进程不过是程序的运行实例。它也被定义为一个正在运行的程序。
进程的概念是Linux系统的基本概念。进程可以产生其他进程、杀死其他进程、与其他进程通信等等。
在本文中,我们将讨论流程的生命周期,并基于流程在其生命周期中经历的各个方面进行接触。
1. 代码 - 程序 - 流程
让我们首先了解代码、程序和进程之间的区别。
代码:以下是代码示例:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("\n Hello World\n");
sleep(10);
return 0;
}
让我们将上面的代码保存在一个名为 helloWorld.c 的文件中。所以这个文件变成了代码。
程序:现在,当编译代码时,它会生成一个可执行文件。以下是上述代码的编译方式:
$ gcc -Wall helloWorld.c -o helloWorld
这将生成一个名为 helloWorld 的可执行文件。该可执行文件称为程序。
过程:现在,让我们运行这个可执行文件:
$ ./helloWorld
Hello World
一旦运行,就会创建一个对应于这个可执行文件(或程序)的进程。此过程将执行程序中存在的所有机器代码。这就是为什么进程被称为程序的运行实例的原因。
要检查新创建的进程的详细信息,请按以下方式运行 ps 命令:
$ ps -aef | grep hello*
1000 6163 3017 0 18:15 pts/0 00:00:00 ./helloWorld
要了解 ps 命令的输出,请阅读我们关于7 ps 命令示例的文章。
2. 父子进程
每个进程都有一个父进程,它可能有也可能没有子进程。让我们一一来看。考虑 ps 命令在我的 Ubuntu 机器上的输出:
1000 3008 1 0 12:50 ? 00:00:23 gnome-terminal
1000 3016 3008 0 12:50 ? 00:00:00 gnome-pty-helper
1000 3017 3008 0 12:50 pts/0 00:00:00 bash
1000 3079 3008 0 12:58 pts/1 00:00:00 bash
1000 3321 1 0 14:29 ? 00:00:12 gedit
root 5143 2 0 17:20 ? 00:00:04 [kworker/1:1]
root 5600 2 0 17:39 ? 00:00:00 [migration/1]
root 5642 2 0 17:39 ? 00:00:00 [kworker/u:69]
root 5643 2 0 17:39 ? 00:00:00 [kworker/u:70]
root 5677 2 0 17:39 ? 00:00:00 [kworker/0:2]
root 5680 2 0 17:39 ? 00:00:00 [hci0]
root 5956 916 0 17:39 ? 00:00:00 /sbin/dhclient -d -sf /usr/lib/NetworkManager/nm-dhcp-client.action -pf /run/sendsigs.
root 6181 2 0 18:35 ? 00:00:00 [kworker/1:0]
root 6190 2 0 18:40 ? 00:00:00 [kworker/1:2]
1000 6191 3079 0 18:43 pts/1 00:00:00 ps -aef
上述输出的第二列和第三列中的整数表示进程 ID 和父进程 ID。观察以粗体突出显示的数字。当我执行命令“ps -aef”时,创建了一个进程,它的进程 ID 是 6191。现在,看看它的父进程 ID,它是 3079。如果你看输出的开头,你会看到 ID 3079是 bash 进程的进程 ID。这确认 bash shell 是您通过它运行的任何命令的父级。
同样,即使对于不是通过 shell 创建的进程,也有一些父进程。只需在您的 Linux 机器上运行“ps -aef”命令并观察 PPID(父进程 ID)列。您不会在其中看到任何空条目。这证实了每个进程都有一个父进程。
现在,让我们来看看子进程。每当一个进程创建另一个进程时,前者称为父进程,后者称为子进程。从技术上讲,子进程是通过在代码中调用fork() 函数来创建的。通常,当您从 shell 运行命令时,fork() 后面是exec() 系列函数。
我们讨论了每个进程都有一个父进程,这会带来一个问题,如果父进程被杀死,子进程会发生什么?好吧,这是一个很好的问题,但让我们稍后再讨论。
3.初始化过程
当 Linux 系统启动时,首先加载到内存中的是 vmlinuz。它是压缩的 Linux 内核可执行文件。这导致了init进程的创建。这是创建的第一个进程。Init 进程的 PID 为 1,并且是 Linux 会话中所有进程的超级父进程。如果您将 Linux 进程结构视为一棵树,那么 init 就是该树的起始节点。
要确认 init 是第一个进程,您可以在 Linux 机器上运行 pstree 命令。此命令显示 Linux 会话的进程树。
这是一个示例输出:
init-+-NetworkManager-+-dhclient
| |-dnsmasq
| `-3*[{NetworkManager}]
|-accounts-daemon---2*[{accounts-daemon}]
|-acpid
|-at-spi-bus-laun-+-dbus-daemon
| `-3*[{at-spi-bus-laun}]
|-at-spi2-registr---{at-spi2-registr}
|-avahi-daemon---avahi-daemon
|-bamfdaemon---3*[{bamfdaemon}]
|-bluetoothd
|-colord---{colord}
|-console-kit-dae---64*[{console-kit-dae}]
|-cron
|-cups-browsed
|-cupsd
|-2*[dbus-daemon]
|-dbus-launch
|-dconf-service---2*[{dconf-service}]
|-evince---3*[{evince}]
|-evinced---{evinced}
|-evolution-sourc---2*[{evolution-sourc}]
|-firefox-+-plugin-containe---16*[{plugin-containe}]
| `-36*[{firefox}]
|-gconfd-2
|-gedit---3*[{gedit}]
|-6*[getty]
|-gnome-keyring-d---7*[{gnome-keyring-d}]
|-gnome-terminal-+-bash
| |-bash-+-less
| | `-pstree
| |-gnome-pty-helpe
| `-3*[{gnome-terminal}]
|-gvfs-afc-volume---2*[{gvfs-afc-volume}]
|-gvfs-gphoto2-vo---{gvfs-gphoto2-vo}
|-gvfs-mtp-volume---{gvfs-mtp-volume}
|-gvfs-udisks2-vo---{gvfs-udisks2-vo}
|-gvfsd---{gvfsd}
|-gvfsd-burn---2*[{gvfsd-burn}]
|-gvfsd-fuse---4*[{gvfsd-fuse}]
输出确认 init 位于进程树的顶部。此外,如果您观察粗体文本,您将看到 pstree 进程的完整父子关系。在我们关于树和 pstree的文章中阅读更多关于 pstree 的信息。
现在,让我们回到关于父进程在子进程还活着的情况下被杀死的后果的问题(我们在上一节中没有讨论)。那么在这种情况下,孩子显然成为孤儿,但被 init 进程收养。因此,init 进程成为那些父进程终止的子进程的新父进程。
4. 流程生命周期
在本节中,我们将讨论普通 Linux 进程在被杀死并从内核进程表中删除之前的生命周期。
- 如前所述,通过 fork() 创建一个新进程,如果要运行新的可执行文件,则在 fork() 之后调用 exec() 系列函数。一旦创建了这个新进程,它就会排队进入准备运行的进程队列。
- 如果只调用了 fork(),那么新进程很可能在用户模式下运行,但如果调用 exec(),那么新进程将在内核模式下运行,直到为其创建新的进程地址空间。
- 在进程运行时,更高优先级的进程可以通过中断抢占它。在这种情况下,被抢占的进程再次进入准备运行的进程队列。这个过程在稍后的某个阶段被调度程序拾取。
- 进程可以在运行时进入内核模式。当它需要访问某些资源(例如保存在硬盘上的文本文件)时,这是可能的。由于涉及访问硬件的操作可能需要时间,因此该进程很可能会进入睡眠状态,并且只有在请求的数据可用时才会唤醒。当进程被唤醒时,并不意味着它会立即开始执行,它会再次排队,并在适当的时候被调度器挑选执行。
- 可以通过多种方式杀死一个进程。它可以调用exit()函数退出,也可以处理Linux信号退出。此外,某些信号无法被捕获并导致进程立即终止。
- 有不同类型的 Linux 进程。一旦进程被杀死,它并没有被完全消除。包含一些相关信息的条目保存在内核进程地址表中,直到父进程显式调用 wait() 或 waitpid() 函数以获取子进程的退出状态。在父进程这样做之前,终止的进程称为僵尸进程。
- 点赞
- 收藏
- 关注作者
评论(0)