RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP,内置NPU,支持INT4/INT8/INT16/FP16混合运算能力,支持安卓12和、Debian11、Build root、Ubuntu20和22版本登系统。了解更多信息可点击迅为官网
【粉丝群】824412014
【实验平台】:迅为RK3588开发板
【内容来源】《iTOP-3588开发板系统编程手册》
【全套资料及网盘获取方式】联系淘宝客服加入售后技术支持群内下载
【视频介绍】:【强者之芯】 新一代AIOT高端应用芯片 iTOP -3588人工智能工业AI主板
第7章 Linux终端会话和进程管理
本章节将以上一章节所讲解的进程基本知识为前提,对进程进行深入的讲解。本章节要讲解的内容如下所示:
(1)阐述终端和控制台的历史演变
(2)对进程组和会话进行讲解
(3)对前台进程和后台进行进行讲解
(4)对守护进程、僵尸进程、孤儿进程进行讲解
(5)讲解祖先进程-init
7.1 终端和控制台
在计算机发展早期,终端(terminal)通常是指由键盘和显示器组成的一种外设,它可以连接到主机计算机并充当用户输入和输出信息的界面。早期的终端通常是大型机、小型机和远程计算机系统的重要组成部分,它们允许多个用户通过网络或串口等方式同时访问计算机系统。
在那个时代,计算机通常是以中央主机(central host)的形式存在,而终端则是计算机系统的重要外设。终端通过串口或者电话线等方式连接到主机上,用户可以通过终端输入命令,计算机系统则可以根据命令的要求进行处理,并将结果输出到终端上。因此,终端在计算机发展早期的意义非常重要,它们扮演了让人与计算机系统进行交互的桥梁角色。早期的终端设备如下图所示:
终端设备的显示器和键盘组合虽然比不过一台中央计算机的价格,但仍旧不能被个人所承受,更多时候人们使用的是tty终端设备(全称为TeleTYpewriter,翻译为电传打印机),用户仍旧通过键盘输入字符,这些字符被传输到计算机主机,并由计算机主机进行处理。计算机主机处理完成后,结果将通过电传打字机的输出机构打印到终端显示设备上。这样,用户就可以在终端显示设备上看到计算机的响应和结果,这里的终端显示设备不再是显示屏,而是将信息输出到一张白纸上,如下图所示:
而在早期的计算机系统中,控制台(console)是一种常见的设备,它通常与主机计算机物理相连,是计算机系统的核心控制单元。控制台通常是一个集成了屏幕、键盘、指示灯和其他硬件组件的设备。控制台的主要功能是允许用户直接与计算机进行交互,输入命令和数据,查看计算机的输出和结果。控制台设备通常具有高度的可靠性和稳定性,因为它们需要在计算机系统的整个生命周期中保持正常运行,以确保计算机系统的可靠性和稳定性。如下图所示:
看到这里大家可能对终端和控制台有些疑惑了,两个设备都是用来和计算机进行交互的,两个设备有什么区别呢?
下面是一个表格,将控制台和终端之间的主要区别进行了总结和描述:
区别 | 控制台 | 终端 |
设备类型 | 物理设备 | 逻辑设备 |
设备形态 | 集成键盘、屏幕、硬件等 | 可能是软件程序、模拟终端 |
设备连接方式 | 通常与主机物理相连 | 串口、网络、本地终端等 |
提供的功能 | 运行系统命令和应用程序 | 通过文本界面与计算机交互 |
用户交互方式 | 通过键盘和屏幕进行输入输出 | 通过终端窗口进行输入输出 |
数量限制 | 通常只有一个 | 可以使用多个 |
随着科技的继续发展,中央计算机逐渐变为了个人计算机,之前人们经常使用的电传打印机和外设终端也化作了历史的尘埃,而控制台现在通常是指系统的主控制台或管理终端,用于进行系统的配置、管理和故障诊断等操作。一般情况下不再是一个物理设备,而是一个软件界面或系统命令行终端。以Linux操作系统为例,控制台通常是由一个特殊的虚拟终端扮演(电传打印机虽然被淘汰了,但其一些传统保留了下来)。虚拟终端是一种由Linux内核提供的虚拟控制台,它是指在一台物理机器上,通过多个虚拟终端同时提供多个命令行界面的方式。用户可以通过按下特定的键组合(Ctrl+Alt+F1~F6)在不同的虚拟终端之间切换(tty1~tty6),当前所使用的虚拟终端就是该系统的控制台。每个虚拟终端都可以独立地运行一个Shell进程,用户可以在其中执行命令、启动应用程序等操作。
而伪终端(pseudo terminal)是一种通过网络或本地终端与另一个进程进行通信的机制。伪终端提供了一种在用户进程和另一个进程之间进行双向通信的方法,其中用户进程可以通过伪终端的控制端口与另一个进程进行交互,而另一个进程则可以通过伪终端的数据端口与用户进程进行交互。伪终端通常用于在客户端和服务器之间建立虚拟终端会话,例如在远程登录时。当用户通过 SSH 等协议连接到远程服务器时,实际上是通过伪终端来实现的。通过ssh连接到虚拟机Ubuntu如下图所示:
而我们在ubuntu系统图形界面所使用的终端同样属于伪终端的范畴,该终端模拟器应用程序使用了 Linux 操作系统提供的伪终端机制,通过终端设备文件和进程间通信来模拟真实终端的输入输出功能。用户在终端模拟器中输入的命令和操作都会被转发到后台 Shell 进程中执行,Shell 进程的输出也会返回到终端模拟器中显示,如下图所示:
至此关于终端和控制台相关的内容就讲解完成了。
7.2 进程组和会话
当我们使用终端或控制台运行一个可执行程序后,操作系统就会为该程序创建一个进程,
那Linux操作系统是怎样对该进程管理和控制的呢,这就要轮到进程组和会话出马了。
7.2.1 进程组
进程组是一组相关进程的集合,它们具有相同的进程组ID(PGID)。进程组是Linux操作系统中进程管理的重要机制之一,它为进程提供了一种集体管理和协同工作的机制。
一个进程组通常由一个父进程和若干个子进程组成。父进程创建子进程时,可以将子进程放入与父进程相同的进程组中,也可以将子进程放入其他进程组中。进程组的ID由系统分配,它是一个非负整数,通常等于进程组中的第一个进程的进程ID。
在进程组中,每个进程都有一个唯一的进程组ID(PGID),用于标识它所属的进程组。进程可以使用系统调用setpgid()来设置自己的进程组ID,使用getpgid()获取自己所属的进程组ID。在终端可以使用以下命令以树状图的形式查看进程所属的进程组,如下图所示:
ps -afo pid,pgid,cmd
或者使用“pstree -p”命令,以更直观的形式展现进程之间的关系,如下图所示:
下面对进程组的主要作用进行陈述:
(1)进程组提供了进程间通信的机制,进程组中的所有进程都可以相互发送信号。
(2)进程组可以用于控制进程的运行和管理。父进程可以使用waitpid()或wait()等系统调用等待子进程退出,也可以使用kill命令向子进程发送信号以终止它们的运行。
(3)进程组可以用于实现终端控制。当一个进程组在前台运行时,它会占用控制终端,并能够接收键盘输入。在后台运行的进程组不会占用控制终端,但可以接收信号和输出信息。
(4)进程组中的所有进程共享同一个进程组ID(PGID),并且会接收到向该进程组发送的信号。因此,如果向进程组发送信号,所有进程都会接收到信号并作出相应的响应。
7.2.2 会话
在Linux操作系统中,会话(session)是指一组相关的进程集合,它们在同一个终端设备上运行,并由同一个进程启动,共享同一个控制终端。以Ubuntu虚拟机的图形界面下启动的虚拟终端为例,该虚拟终端可以被视为一个会话。
在一个会话中,至少存在一个进程组,而一个进程组可以属于多个会话。通常情况下,会话的领头进程(session leader)是第一个启动的进程,一般为shell,它的进程ID(PID)被用作该会话的会话ID(SID)。
一个会话可以包含多个进程组,其中每个进程组都由同一个进程领头,包含多个相互关联的进程。进程组中的进程通常会共享同一个终端设备。进程组ID(PGID)与会话ID(SID)可以相同,也可以不同。
会话、进程组和进程之间的关系如下图所示:
在一个会话中,只有领头进程能够直接与终端设备交互,其他进程则需要通过进程间通信(IPC)机制与领头进程通信,以间接地与终端设备交互。如果领头进程终止,会话中的所有进程都会收到SIGHUP信号,通知它们与终端设备的连接已经断开。因此,会话是Linux操作系统中一个重要的概念,它提供了一个有组织、可控制的运行环境,方便用户进行任务的执行和管理。
窗体底端
可以使用以下命令对进程号、会话ID和进程组ID进行查看,如下图所示:
ps -o pid,sid,pgid,cmd
bash为该用户的领头进程,该进程的pid就是会话id,所以ps命令的会话id同样为bash进程的pid。
Linux操作系统中的会话是多用户、多任务操作系统的重要特性,它为用户提供了在终端设备上运行多个进程的灵活方式,并且使得用户可以在不同的会话之间进行切换,从而提高了系统的可用性和可靠性。
7.3 前台进程和后台进程
本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\36”目录下,如下图所示:
在上一章节中讲解到会话(Session)是用于表示一组交互式进程的集合。当用户登录到系统时,会话就开始了。而在一个会话中,进程组可以被分为前台进程组(foreground process group)和后台进程组(background process group)。
前台进程组是指当前正在与用户交互的进程组,通常是通过终端输入命令启动的进程组。在前台进程组运行期间,终端会将输入数据传递给该进程组,并将其输出内容显示在终端屏幕上。如果前台进程组结束或暂停,终端将重新回到shell进程,等待新的命令输入。
而后台进程组是指在终端不需要与用户进行交互的进程组,通常是通过在命令行末尾加上"&"符号启动的进程组。后台进程组可以在终端不被阻塞的情况下运行,且可以同时启动多个后台进程组。后台进程组的输出通常被重定向到文件或/dev/null设备中,以避免干扰终端的操作。
终端只与前台进程组有关联关系,所以只有前台进程组可以直接与终端进行交互,而后台进程组无法直接与终端进行交互。
前台进程组和后台进程组在运行时,如果用户希望将其转换为后台进程组,则可以使用"Ctrl+Z"快捷键将其暂停,并使用"bg 进程号码"命令将其转换为后台进程组;如果希望将后台进程组重新切换到前台,可以使用"fg 进程号码"命令将其转换为前台进程组。下面编写demo36_test.c来进行前后台进程切换测试。测试代码如下所示:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main(char argc, int *argv[])
{int count; // 定义计数器printf("this pid is %d\n", getpid()); // 输出当前进程的pidwhile(1) // 进入循环,无限制地执行以下代码{sleep(1); // 等待1秒钟printf("the count is %d\n", count); // 输出计数器的值count++; // 计数器加1}return 0; // 返回0,表示程序正常结束
}
该程序的作用是输出当前进程的pid,并在一个无限循环中输出计数器的值。程序通过sleep(1)函数每隔1秒钟输出一次计数器的值。保存退出之后对使用以下命令对该程序进行编译,编译完成如下图所示:
gcc -o demo36_test demo36_test.c
然后使用“./demo36_test”命令运行该程序,如下图所示:
首先使用“ctrl + z”组合按键将现在正在运行的前台进行停止,此时进程就成为了后台进程,如下图所示:
然后输入"jobs"命令显示当前所有的后台进程组如下图所示:
前面的序号1就是之后fg命令和bg命令要输入的参数,然后输入命令“bg 1”让该进程在后台继续运行如下图所示:
可以看到当进程在后台运行时仍旧可以继续与终端进行交互,然后同样输入“jobs”对后台进程进行查看,如下图所示:
可以看到该进程正在运行中,随后输入“fg 1”将该后台进程转换为前台进程如下图所示:
该进程就转换为了前台进程,终端也无法再进行交互。至此关于前台进程和后台进程的讲解就结束了。
7.4 守护进程
本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\37”目录下,如下图所示:
而本章节要讲解的守护进程(daemon)也是后台进程的一种,但守护进程通常不会与用户进行交互也不受用户登录或注销的影响。它们在系统启动时启动,一直运行到系统关闭。守护进程通常用来执行一些系统级别的任务,通常以root用户身份运行,因为它们需要对系统进行访问和控制,下面对守护进程的特点进行总结:
(1)后台运行:守护进程在后台运行,不会占用用户的终端或控制台,这可以让用户在前台继续执行其他任务。
(2)无人值守:守护进程通常不需要用户交互,它们会在系统启动时启动,一直运行到系统关闭。
(3)执行系统级别任务:守护进程通常被用来执行一些系统级别的任务,例如维护网络服务,备份数据,监控系统状态等。
(4)记录日志:守护进程通常会将执行过程中的信息记录在日志文件中,方便后续查看和分析。
(5)系统管理:守护进程可以监控系统状态,例如检测CPU和内存使用情况,提醒系统管理员需要进行资源调整。
部分常用Linux操作系统中的守护进程如下表所示:
守护进程名称 | 作用 |
systemd | 系统和服务管理器,负责启动和管理系统上的各种服务和进程 |
udevd | 硬件设备管理器,负责识别和管理系统中的硬件设备 |
crond | 定时任务管理器,负责在指定的时间运行指定的命令或脚本 |
sshd | SSH服务守护进程,负责监听SSH连接请求并提供SSH访问服务 |
syslogd/rsyslogd | 系统日志守护进程,负责收集、记录和管理系统日志信息 |
cupsd | 打印服务守护进程,负责管理打印机和打印作业 |
NetworkManager | 网络管理守护进程,负责管理网络连接和配置 |
avahi-daemon | Zeroconf服务守护进程,负责管理网络设备的发现和互相通信 |
dbus-daemon | D-Bus消息总线守护进程,负责管理进程间通信和系统总线 |
ntpd/chronyd | 时间同步守护进程,负责与时间服务器同步系统时间 |
acpid | 电源管理守护进程,负责监控和管理系统电源状态 |
通常情况下,创建守护进程的步骤如下所示:
(1)屏蔽一些控制终端操作的信号:在创建守护进程之前,需要屏蔽一些控制终端的操作信号,以免这些信号在守护进程运行时产生影响。通常需要屏蔽的信号有:SIGTTOU、SIGTTIN、SIGTSTP等。
(2)调用fork,父进程退出:创建守护进程的第二步是调用fork函数创建一个子进程。父进程退出,让子进程成为孤儿进程并脱离控制终端的控制。
(3)setsid创建一个新会话:子进程调用setsid函数创建一个新的会话,成为该会话的领头进程,并且脱离原来的进程组和控制终端的控制。setsid函数分配一个新的进程组ID和会话ID,将子进程从原来的进程组和会话中分离出来。
(4)禁止进程重新打开控制终端:守护进程不能重新打开控制终端,否则就会失去后台运行的特点,因此需要将守护进程与控制终端分离。可以通过以下方式实现:
在setsid函数返回后,进一步fork一个子进程,并在父进程中exit,这样就可以保证该进程不是会话首进程,也不会重新获得控制终端的控制。
也可以使用open函数打开/dev/null设备文件,并将其重定向到0、1、2号文件描述符,这样守护进程的标准输入、输出、错误输出就都被重定向到了/dev/null,也就是没有设备的空设备上,从而不会干扰终端的使用。
(5)关闭打开的文件描述符:由于守护进程在运行时不再需要与终端交互,因此需要关闭所有的打开的文件描述符,以免对终端产生干扰。这里包括stdin、stdout和stderr三个文件描述符以及其他不需要的文件描述符。
(6)改变当前工作目录:守护进程需要更改当前工作目录,以免影响其他进程的正常运行。
(7)重设文件创建掩模:在创建文件时,需要设置文件权限,因此需要重新设置文件创建掩模。通常可以将文件创建掩模设置为0。
(8)处理SIGCHLD信号:守护进程需要在后台运行,并且不能与终端交互。当守护进程的子进程退出时
根据上述代码步骤编写实验代码demo37_daemon.c,编写完成的实验代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>int main()
{pid_t pid;// 1.屏蔽一些控制终端操作的信号signal(SIGTTOU, SIG_IGN);signal(SIGTTIN, SIG_IGN);signal(SIGTSTP, SIG_IGN);// 2.调用fork,父进程退出pid = fork();if (pid < 0) {printf("fork error\n");exit(1);}if (pid > 0) {exit(0);}// 3.setsid创建一个新会话if (setsid() == -1) {printf("setsid error\n");exit(1);}// 4.禁止进程重新打开控制终端signal(SIGHUP, SIG_IGN);pid = fork();if (pid < 0) {printf("fork error\n");exit(1);}if (pid > 0) {exit(0);}// 5.关闭打开的文件描述符close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 6.改变当前工作目录chdir("/");// 7.重设文件创建掩模umask(0);// 8.处理SIGCHLD信号signal(SIGCHLD, SIG_IGN);// 此时守护进程已经创建完成while (1) {// 进程的主要工作内容sleep(1);}return 0;
}
上述代码仅仅用测试守护进程,所以并未在while循环中添加内容,保存退出之后,使用以下命令编译可执行文件,如下图所示:
然后使用“./demo37_deamon”命令运行该程序,如下图所示:
可以看到虽然在程序中存在while死循环,但是进程并没有阻塞,然后再使用以下命令查看全部进程,如下图所示:
ps -axj
从上图可以看出,demo37_deamon进程的父进程的PID为1(即init进程,关于init进程会在7.7小节进行讲解),1号进程的相关信息如下所示:
最后对普通后台进程和守护进程进行对比,对比表格如下所示:
特点 | 守护进程 | 普通后台进程 |
运行方式 | 作为系统服务在后台长期运行 | 在前台运行,但可以在后台运行 |
生命周期 | 长期运行 | 只有在当前shell会话期间运行 |
控制终端 | 不与任何控制终端相关联 | 可以与控制终端相关联 |
用户交互 | 不能与用户交互 | 可以与用户交互 |
创建方式 | 需要进行特定的创建步骤 | 可以直接在前台运行 |
运行状态 | 常驻内存 | 依赖于当前shell的运行环境 |
信号处理 | 需要特别处理一些信号 | 不需要特别处理信号 |
重定向标准输入输出 | 需要将标准输入输出重定向 | 可以在前台运行不进行重定向 |
至此关于守护进程相关的知识就讲解完成了。
7.5 僵尸进程
本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\38”目录下,如下图所示:
在操作系统中,每个进程都有一个唯一的进程ID,以及父进程ID。当一个进程终止时,它的进程ID和状态信息会被保留在系统中,从而方便父进程的查询。
而僵尸进程(zombie)是指已经执行完毕,但是父进程没有及时回收其资源的进程。在操作系统中,进程是由操作系统维护的,每个进程都有自己的进程控制块(Process Control Block,PCB),保存了进程的状态信息、程序计数器、寄存器状态等信息。当一个进程退出时,它的PCB会被保留在操作系统中,等待父进程使用wait系统调用来检查它的状态并回收它的资源。如果父进程没有使用wait()函数等待子进程的结束并清理其资源,那么子进程会变成僵尸进程。当父进程退出时,内核会将所有僵尸进程的父进程ID(PPID)设置为init进程(进程号为1的进程,会在7.7小节中进行讲解),init进程接管该进程,并执行以下操作:
(1)将进程的状态更改为"僵尸",并将其从进程列表中移除。
(2)从该进程的父进程中删除该进程的所有子进程。
(3)释放该进程占用的所有资源,包括内存、文件描述符等。
僵尸进程会占用系统的资源,尤其是进程表中的表项和PCB等系统资源。如果僵尸进程过多,就会导致进程表耗尽,从而导致系统崩溃。因此,及时清除僵尸进程是非常重要的。
最后编写demo38_zombie.c程序代码,对僵尸进程进行举例,具体代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid == 0) {// 子进程printf("Child process is running.\n");exit(0);} else if (pid > 0) {// 父进程char *args[] = {"ps", "-o", "pid,ppid,state,tty,command", NULL}; //要执行的命令及其参数printf("Parent process is running.\n");sleep(2);execvp(args[0], args); //使用execvp()函数执行命令printf("Parent process is exiting.\n");} else {// fork() 失败printf("Fork failed.\n");exit(1);}return 0;
}
在上述代码中,父进程在等待了2秒钟后退出,但它并没有获取子进程的退出状态。因此,当子进程结束时,它的进程描述符就变成了僵尸进程,占用了系统资源,父进程结束之前使用"ps -o pid,ppid,state,tty,command"命令对进程信息进行查看,保存退出之后使用以下命令对demo38_zombie.c进行编译,编译完成如下图所示:
然后使用“./demo38_zombie”命令运行该程序,如下图所示:
可以看到demo38_zombie子进程的状态为“Z”,表示僵尸状态(Zombie)。而当父进程也退出后,回到终端交互界面再使用相同的命令进行查看,就无法查询到该僵尸进程了,原因是1号进程init已经帮我们清理了。
至此,关于僵尸进程相关的讲解就完成了。
7.6 孤儿进程
本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\39”目录下,如下图所示:
孤儿进程是指其父进程先于它结束,从而没有父进程来对它进行资源回收和管理的进程。当进程变为孤儿进程之后,会被进程 init 进程领养(进程号为 1的进程,会在下一小节进行讲解),进程1会成为孤儿进程的父进程,对它进行资源回收。孤儿进程与僵尸进程有所区别,僵尸进程是已经结束但是父进程没有对其进行资源回收和管理的进程,而孤儿进程则是因为父进程先于它结束,所以没有了父进程。
孤儿进程的出现通常有以下几种情况:
(1)父进程终止:当父进程结束时,未处理完的子进程可能成为孤儿进程。
(2)父进程崩溃:当父进程崩溃时,它无法完成对子进程的管理工作,导致子进程变成孤儿进程。
(3)子进程快于父进程:当父进程执行速度过慢,而子进程执行速度过快时,可能导致子进程先于父进程结束,从而成为孤儿进程。
孤儿进程对系统资源是有影响的,因为孤儿进程没有父进程进行资源回收和管理,所以它占用着系统资源而无法释放,如果孤儿进程数量太多,会导致系统资源的浪费和枯竭。因此,需要及时对孤儿进程进行处理。
处理孤儿进程的方法通常有两种:
(1)父进程等待子进程:在父进程中,可以使用wait()或waitpid()等函数等待子进程结束,从而完成资源回收和管理。
(2)让孤儿进程成为进程1的子进程:由于进程1(init)是所有进程的祖先进程,因此可以将孤儿进程的父进程ID设置为1,从而让进程1来对其进行资源回收和管理,该方法通常由系统自动执行。
最后编写demo39_orphan.c程序代码,对孤儿进程进行举例,具体代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main()
{pid_t pid = fork(); // 使用 fork() 函数创建子进程if (pid == 0) // 子进程{printf("Child process is running.\n"); // 输出提示信息printf("child process wake pid:%d ppid:%d\n", getpid(), getppid()); // 输出子进程的进程 ID 和父进程的进程 IDsleep(10); // 子进程休眠 10 秒printf("Child process is exiting.\n"); // 输出提示信息printf("child process wake pid:%d ppid:%d\n", getpid(), getppid()); // 再次输出子进程的进程 ID 和父进程的进程 ID} else if (pid > 0) // 父进程{printf("Parent process is running.\n"); // 输出提示信息sleep(2); // 父进程休眠 2 秒printf("Parent process is exiting.\n"); // 输出提示信息}else // fork() 失败{printf("Fork failed.\n"); // 输出提示信息exit(1); // 退出进程}return 0; // 返回 0
}
当运行这段代码时,会创建一个子进程,首先会运行父进程,父进程进入休眠后,子进程开始运行,打印当前PID和父进程PID,随后进入10秒钟的休眠,父进程始2秒钟的休眠结束之后退出,而此时子进程仍处在休眠之中,此时子进程就成为了孤儿进程,交由init 进程领养。保存退出之后,使用以下命令对demo39_orphan.c 文件编译,编译完成如下图所示:
gcc -o demo39_orphan demo39_orphan.c
然后使用“./demo39_orphan”命令运行该程序,如下图所示:
子进程的ppid在休眠之前为2650,休眠结束之后变成了孤儿进程,由init进程领养,其父进程的进程号就变成了1。
最后对僵尸进程和孤儿进程进行对比,对比内容表格如下所示:
僵尸进程 | 孤儿进程 | |
定义 | 已经终止但其父进程尚未回收资源的进程 | 父进程异常退出或者被终止 |
产生原因 | 父进程未及时回收子进程 | 父进程异常终止或被杀死 |
状态 | 不再执行,但仍占用系统资源 | 正常运行但没有父进程 |
处理方法 | 父进程通过wait()系统调用回收 | 由init进程接管处理 |
影响 | 进程过多会导致系统资源不足 | 对系统资源影响较小 |
至此,关于孤儿进程相关的内容就讲解完成了。
7.7 1号进程-init
守护进程创建成功之后其父进程的PID为1,僵尸进程产生之后会变成init的子进程,从而被清理,而孤儿进程在产生之后会被init进程领养,同样会成为init的子进程,那这个init进程到底是从何而来呢,该进程的作用又是什么呢,就让我们一起进入本章节的学习吧。
init进程是在Linux系统中启动所有进程的第一个进程,它是所有其他进程的祖先进程。在Linux系统中,所有进程都是由其他进程(即父进程)创建的,但init进程是唯一的例外。当Linux系统启动时,init进程是由内核直接创建的,它并不由其他进程创建。init进程的进程ID(PID)始终为1,它是所有其他进程的祖先进程。因此,如果一个进程的父进程已经退出或被终止,它就会变成孤儿进程,并由init进程接管。
init进程的主要责任是启动系统中的各种进程和服务。在Linux系统中,系统服务和进程通常由init脚本控制。这些脚本存储在/etc/init.d/目录下,并由init进程在系统启动时读取和执行,init进程的作用如下表所示:
作用 | 描述 |
系统初始化 | 初始化系统,启动所有的系统服务和守护进程。 |
进程管理 | 监控所有进程的运行情况,负责对进程进行启动、停止、重启等 |
系统服务管理 | 管理系统所有的服务,如网络服务、时间服务、日志服务等 |
系统资源管理 | 管理系统所有的资源,如CPU、内存、硬盘、网络等 |
系统状态监测和维护 | 检测系统运行状态,维护系统稳定性和安全性 |
崩溃恢复和系统重启 | 当系统出现崩溃时,init进程可以负责恢复系统,并重新启动 |
系统日志管理和记录 | 管理系统的日志信息,记录系统的运行状况和事件 |
系统自动维护和更新 | 自动维护和更新系统软件和配置信息 |
系统启动和关机控制 | 控制系统的启动和关机,保证系统正常启动和关闭 |
系统服务级别切换管理 | 根据需要切换系统的运行级别,以达到不同的运行需求 |
关于Linux系统的启动这里就不进行过多的说明,有兴趣的同学可以去了解一下Linux操作系统的启动流程,最后对init进程实现方式的发展进行简单的概述
1.init进程实现方式一:SysV init
SysV init是最初的init进程实现方式,它采用脚本文件来配置系统服务,通过执行不同的脚本文件来实现不同的操作。SysV init的工作流程大致如下:
- 系统启动,内核启动init进程;
- init进程读取/etc/inittab文件,启动运行级别对应的脚本文件;
- 根据脚本文件的配置,启动和关闭各种系统服务。
SysV init的缺点是启动时间较长,因为需要逐个启动每个服务,而且管理比较复杂,需要手动编写启动脚本。
2.init进程实现方式二:Upstart
Upstart是Ubuntu Linux发行版中使用的一种init进程实现方式,它将任务管理分为事件与任务两个部分。当系统出现特定的事件时,Upstart会启动相应的任务来处理事件。Upstart的工作流程大致如下:
(1)系统启动,内核启动init进程;
(2)init进程读取/etc/init目录下的Upstart配置文件,按照配置文件中的定义启动和管理服务。
Upstart能够加快系统启动速度,提高可靠性。Upstart使用基于配置文件的方式来管理服务,支持与SysV init脚本兼容。然而,由于Upstart在系统初始化过程中的启动顺序问题,其被systemd取代。
3.init进程实现方式三:systemd
systemd是目前Linux操作系统中最常用的init进程实现方式,它主要负责管理系统进程和系统服务。systemd采用单一进程管理所有的进程和服务,通过采用并行启动、按需加载、进程监控等技术,提高了系统的启动速度和稳定性。systemd的工作流程大致如下:
- 系统启动,内核启动systemd进程;
- systemd读取/etc/systemd/system目录下的配置文件,按照配置文件中的定义启动和管理服务。
systemd 是一个功能强大、高度集成的系统管理器,它的设计目标是简化系统管理,并提供更好的性能和可靠性
总体来说,随着Linux操作系统的不断发展,init进程的实现方式也在不断演变和完善。不同的init进程实现方式采用不同的技术和方法来管理系统服务和进程,用户可以根据自己的需求选择不同的方式来管理。