找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

581

积分

0

好友

75

主题
发表于 4 天前 | 查看: 16| 回复: 0

Linux 进程的学习是一个相当长的过程,所以事不宜迟,让我们先简单了解一下什么是进程。

Linux 进程基础

在介绍进程之前,我们先来弄清楚什么是“程序”。

简单来说,程序是存储在硬盘上的代码或机器指令,它由程序员为完成某些特定任务根据特定的语法规则编写而成,程序本身是“静态”的,只是安静地放在硬盘上等待被执行。

而当程序真正被操作系统加载到内存中并开始运行时,它就变成了一个 进程(Process)。可以看出,程序是一个“静态”的实体,而进程是程序的“动态”实例。

程序与进程区别示意图

举个简单的例子,假设你有一本食谱。食谱就像是一个“程序”,它只是记录了做菜的步骤和材料,仅供参考。而当你真正开始动手做菜时,从准备食材、加热锅具到烹饪的整个过程,就相当于将“程序”变成了“进程”。

就像做菜需要各种材料一样,一个进程在执行时也需要系统资源,比如 CPU 时间、内存(如 RAM 或磁盘空间),以及虚拟内存(如交换空间)。

每个进程在系统中都有一个唯一的标识符,称为进程 ID(PID),操作系统通过 PID 来跟踪和管理不同的进程。

进程信息示意图

Linux 上的进程初始化

在 Linux 中,进程的创建与管理都是由 Linux 内核(Kernel) 完成的。要理解系统中的进程是如何产生的,我们需要先了解系统启动时的初始化过程。

默认情况下,当您启动 Linux 系统时,Linux 内核首先会被加载到内存中,随后,系统会在内存中创建一个临时的虚拟文件系统,称为 initramfs。这个虚拟文件系统为系统提供了一个初始运行环境,用来执行关键的启动命令和系统初始化任务。

在这些初始化任务中,其中一个非常重要的任务就是启动第一个进程。在早期的 Linux 系统中,这个进程被称为 init,它的进程号(PID)永远是 1。它是所有其他进程的祖先,因为系统中的其他所有进程都是从这个进程派生出来的。

然而,在许多较新的 Linux 发行版中,传统的 init 已被更先进的 systemd 所取代。

systemd 不仅承担初始化进程的角色,还集成了许多系统管理功能,例如服务控制、日志记录和依赖管理等。与传统的 init 相比,systemd 启动速度更快,模块化程度更高。

为了证明这一点,您可以在您自己的主机上运行以下命令:

$ ps -aux | head -n 2

ps命令查看进程截图

在输出中,systemd 进程的PID 为 1。如果您使用树形显示来打印系统上的所有进程,您会发现所有进程都是 systemd 进程的子进程。

$ pstree

pstree命令查看进程树截图

最后需要说明的是,所有这些初始化步骤(除了启动初始进程)都是在一个被称为内核空间(Kernel space)的保留空间中完成的。

内核空间是专门为内核(Kernel)保留的内存区域,用于运行关键的系统代码和底层服务。

与内核空间相对应的是用户空间(User space),这是普通用户程序运行的地方。

为了保证系统的稳定和安全,用户空间与内核空间是隔离的,这样即使某个用户程序崩溃了,也不会影响内核的正常运行。

用户空间中的进程无法直接访问内核空间的资源。如果一个进程需要访问硬件、读取文件或获取进程 ID 等系统资源,就必须通过系统调用(System Call)向内核提出请求。理解这种内核空间与用户空间的交互是理解操作系统底层原理的关键。

用户空间与内核空间交互示意图

使用 fork 和 exec 创建进程

在 Linux 中,当你运行一个程序时,通常涉及两个主要步骤:fork 和 exec。

fork操作

fork 是一种“克隆”操作,简单来说,Fork 会把当前运行的进程(称为父进程)复制一份,生成一个新的进程(称为子进程),新创建的子进程拥有自己独立的进程 ID(PID)。

当 fork 发生时,父进程的堆栈、堆内存以及文件描述符(如标准输入、标准输出和标准错误)都会被复制到子进程中。

Fork操作示意图

这张图上部分展示了两个进程,左边是父进程,右边是子进程。父进程的 PID 是 123,fork出的子进程 PID 是 456,这个子进程的父进程 ID (PPID) 是 123,也就是左边进程的 PID。

图的中间部分显示了内存空间的复制过程。父进程的堆 (Heap) 和栈 (Stack) 都会被完全复制到子进程中。

图的底部展示了文件描述符的复制过程。父进程的所有文件描述符(如标准输入 fd[0]、标准输出 fd[1] 和标准错误 fd[2])都会被复制到子进程中。

exec操作

在 Linux 中,exec 用于将当前进程的执行代码替换为另一段程序的代码。简而言之,exec 让一个已经存在的进程,在保留现有资源(如内存空间和进程 ID)的情况下,执行一段全新的程序指令。

回顾之前的步骤:当我们使用 fork() 时,系统会创建一个几乎与父进程完全相同的子进程。

然而,这个子进程最初执行的仍然是父进程的代码。通常,我们希望子进程去运行一个全新的程序——这时就需要使用 exec。

在下图中,左边是父进程,其 PID 是 123,它的内存空间中存放了当前要执行的一些指令(也就是“Set of Instructions”);右边是子进程,子进程的 PID 是 456,是通过 fork 操作创建的。

当执行 exec 操作后,子进程的内存空间(包括代码段和数据段)会被新的程序内容替换掉,这样一来,它执行的代码已经完全变成了新的内容。需要注意的是,子进程的 PID 保持不变。

Exec操作示意图

举个例子,如果你在终端中执行以下命令:

$ exec ls -l

则你的 shell 会在命令完成后立即终止,因为你当前的进程映像(bash 解释器)将被替换为你尝试启动的命令程序的上下文(ls)。

从系统层面来看,当你跟踪进程创建的系统调用时,会发现 exec 是继 fork 之后调用的第一个核心函数。

strace跟踪系统调用截图

在 Shell 环境中创建进程

Shell 本身其实也是一个进程——它负责等待用户输入命令,并在你按下回车后执行相应的操作。

当你在终端中输入一条命令并按下回车键时,Shell 会通过 fork() 创建一个新的子进程,用来执行这条命令,原本的 Shell 进程被称为 父进程,它暂时进入等待状态(wait),直到子进程(也就是正在执行命令的进程)完成任务后才会恢复运行。

在这个过程中,子进程会继承父进程的文件描述符,例如标准输入、标准输出和标准错误,这样,无论你在子进程中运行什么命令,输出的结果都会显示在终端中。

接下来,子进程会调用 exec(),将自己当前的进程映像替换为你所输入的命令对应的程序。从这一刻起,这个子进程已经不再是 Shell 的复制体,而变成了一个全新的程序进程——比如 ls、cat 或 grep 等。

当该命令执行完毕后,子进程会通过标准输出(从 Shell 继承而来)把结果打印到屏幕上,然后退出。此时,父进程(Shell)从等待状态中返回,重新显示命令提示符,准备接收下一条命令。

Shell命令执行流程图

现在你对 Linux 中的进程创建有了基本了解,接下来让我们看看有关进程的一些细节以及Linux上和进程有关的一些命令。

查看 Linux 上正在运行的进程

在 Linux 上查看正在运行的进程最简单方法是运行 ps 命令。

$ ps

ps命令默认输出截图

默认情况下,ps 命令会显示当前用户所拥有的进程列表。从命令的输出结果可以看出,antoine 用户此时只运行两个进程:一个是当前的 Bash 解释器,另一个就是执行中的 ps 命令 本身。

这里需要理解一个重要概念:进程所有者(Process Owner)。通常,一个进程的所有者就是最初启动该进程的用户。为了说明这一点,我们可以通过另一种格式来查看进程的详细信息:

# 列出系统上前十个进程
$ ps -ef | head -n 10

ps -ef命令输出截图

你会注意到,这里前十个进程都由用户“root”拥有。这些系统级进程通常在系统启动时由内核或 systemd 自动创建,它们在进程管理和信号交互时尤为重要。

如果你只想查看当前登录用户的所有进程,可以使用以下命令:

$ ps u

ps u命令输出截图

ps 命令有很多选项可供使用,你可以通过运行 man ps 查看详细的帮助文档。

$ man ps

根据经验,在这么多选项中,有两组选项是平时使用最多的。

#bsd的格式输出
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND

#POSIX格式输出
$ ps -ef
UID  PID  PPID C STIME TTY  TIME CMD

现在我们已经了解了什么是进程以及如何查看它们,接下来我们看看前台进程和后台进程的区别。

后台和前台进程

什么是后台进程?什么是前台进程?这两个概念其实非常容易理解,它们的名字已经说明了一切。

Linux 上的后台进程是在后台运行的进程,它不需要用户通过终端(或 shell)直接管理。而前台进程是与用户直接交互的进程,你可以在终端中输入命令和它进行交互。

举例来说,假设你在 shell 终端中输入以下命令:

$ sleep 10000

这条命令会让系统“休眠” 10000 秒。在这 10000 秒内,终端会被这个进程占用,你不能再输入其他命令。这就是前台进程。如果你想中断或暂停它,可以按下 Ctrl + Z,这会向进程发送一个“停止”信号,使其暂时挂起。

前台进程被暂停截图

但是,有一种方法可以在后台执行sleep。要在后台执行某个进程,只需在命令末尾添加“& ”符号。

$ sleep 10000 &

这样,进程会在后台执行,同时在终端显示一个作业号(Job ID)和PID。此时,sleep 命令仍在执行,但可以在终端继续输入其他命令,这就是后台进程典型的特点。

后台进程启动截图

聊聊 Job

这里再介绍一个与进程有关的概念—作业(Job),作业指的是在当前终端会话中启动的一组命令或进程。

注意,进程和作业的关系:有可能一个进程完成作业,也有可能需要几个进程共同完成一个作业,例如:

# 这是一个作业,只需启动一个进程即可。
指令: ls -l

# 这也是一个作业,但却同时启动3个进程
指令:cat f1|grep "file"|wc -l

Linux中提供了 jobs 命令用来查看系统中作业运行情况。

$ jobs

jobs命令输出截图

从上面的示例中看到,输出结果通常包含三列信息:

  • 作业 ID(Job ID)
  • 作业状态(如 Running、Stopped)
  • 对应的命令

使用 bg 和 fg 命令

在 Linux 中,我们可以通过 bg 和 fg 命令与后台作业进行交互。

bg 命令用于将作业放到后台继续执行,语法如下:

$ bg %<job_id>

fg 命令则用于将一个后台作业重新放到前台执行。

$ fg %<job_id>

回到上一个示例的作业列表,如果我想将作业 3 放到前台,执行以下命令即可。

$ fg %3

使用fg命令将作业带到前台

在前台执行时,我们可以通过 Ctrl + Z 暂停该进程。暂停后,如果希望它继续在后台运行,只需使用 bg 命令即可:

使用Ctrl+Z暂停后再用bg命令放入后台

现在我们对后台进程和前台进程有了更好的了解,下面让我们看看如何使用信号与进程进行交互。

使用信号与进程交互

在 Linux 中,信号是一种进程间通信的方式,它允许我们向正在运行的进程发送异步通知,用来告诉进程发生了某些事情。

举个例子:当用户在终端按下 Ctrl + C 时,系统会向当前正在运行的进程发送一个名为 SIGINT 的信号,这个信号通常用于让进程立刻停止。

为了向指定进程发送信号,可以使用 kill 命令。其基本语法如下:

$ kill -<signal number> <pid>|<process_name>

例如,为了强制终止 一个 PID 为 123 的 HTTPD 进程,可以运行:

$ kill -9 123

其中,-9 对应的是 SIGKILL 信号,表示“强制终止”。

信号类别说明

Linux 提供了许多不同类型的信号,每种信号代表一种特定的控制行为。下面列出了一些最常见的信号及其用途:

  • SIGINT:前面已经介绍过,该信号用于让进程立刻停止。
  • SIGHUP:当关闭终端时,系统会发送这个信号给当前的前台进程,通常会导致进程终止。
  • SIGKILL:这个信号强制终止一个进程,无论进程是否能够正常停止。SIGKILL 是不可忽略的,进程接收到这个信号后会立即停止,除了 init 进程(或最近发行版中的 systemd 进程)外。
  • SIGQUIT:这个信号通常用于用户想要退出或结束当前进程。可以通过按 Ctrl+D 来发送,常用于终端 shell 或 SSH 会话。
  • SIGUSR1,SIGUSR2:这些信号是用户自定义信号,主要用于在程序中实现自定义的处理器,开发人员可以在自己的程序中定义某种行为,当接收到 SIGUSR1 或 SIGUSR2 信号时执行特定的操作。
  • SIGSTOP:这个信号让进程暂停执行,但不会终止它。进程会等待继续执行的信号或完全被杀死。例如在终端运行一个进程时,按 Ctrl+Z 会发送 SIGSTOP 信号,使进程暂停。
  • SIGCONT:如果进程已经被暂停,这个信号会让进程继续执行,例如可以在暂停的进程上使用 fg 或 bg 命令,实际是发送了 SIGCONT 信号让它继续运行。

如果想查看系统支持的全部信号列表,可以使用以下命令:

$ kill -l

 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT
17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU
25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH
29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN
35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4
39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6
59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

可以看出,每个信号都有一个唯一的编号,很多与信号相关的命令都支持通过这个编号来操作其对应的信号。

进程状态

我们已经知道了如何中断、终止或停止进程,那么现在是时候了解进程状态了。进程有许多不同的状态,但从实际意义上讲,除了 new 和 terminated 状态外,进程主要有以下三种状态:

  • 正在运行(RUNNING):进程当前正在占用CPU,执行其任务。
  • 可运行(Runnable):进程已准备好执行,但可能由于调度策略或其他因素未能获得 CPU ,处于就绪状态的进程只要获得CPU,它就会进入运行状态。
  • 等待(WAITING):进程正在等待某些事件的发生或资源的获取(例如I/O操作)。

下图展示了不同进程状态之间的转换关系:

进程状态与信号转换图

现在我们对进程状态有了更多了解,接下来,让我们看看两个常用的命令:pgrep 和 pkill 。

使用 pgrep 和 pkill 操作进程

在 Linux 上,只需使用 ps 命令就可以做很多事情。您可以将搜索范围缩小到某个特定进程,然后使用 PID 将其彻底终止。

但是还有更高效的选择:pgrep 和 pkill,这两个命令可以理解为 ps 的简化版本:

  • pgrep = ps + grep,用于查找进程
  • pkill = ps + kill,用于结束进程

使用 pgrep 查找进程

pgrep 命令用于查找匹配某个模式的进程,其基本语法如下:

$ pgrep <options> <pattern>

#等价于
$ ps <options> | grep   <pattern>

例如,如果要在主机上搜索名为“bash”的所有进程,则可以运行以下命令:

$ pgrep bash

pgrep 命令默认不限于当前用户拥有的进程。如果另一个用户运行 bash 命令,它也会出现在你的 pgrep 命令输出中。

pgrep查找进程截图

此外,pgrep 还支持通配符匹配:

pgrep使用通配符截图

使用 pkill 终止进程

与 pgrep 类似,pkill 命令是 ps 命令与 kill 命令的组合。pkill 命令用于根据进程的 ID 或名称向进程发送信号。pkill 命令的语法如下:

$ pkill <options> <pattern>

例如,如果你想关闭主机上所有正在运行的 Firefox 进程,可以运行以下命令:

$ pkill firefox

如果想更精确地控制,可以加上 -u 参数,指定目标用户。例如,以下命令会终止所有以 “fire” 开头、且属于当前用户或 root 用户的进程:

$ pkill -u antoine,root fire*

当权限不足时,系统会在终端中提示错误信息。

pkill权限不足截图

pkill 还支持通过信号编号或信号名称来执行特定操作。例如要暂停 Firefox 进程而不是终止它,可以使用 SIGSTOP 信号:

$ pkill -19 firefox

使用 nice 和 renice 调整进程优先级

在 Linux 上,并非所有进程都具有相同的优先级,这看起来不公平,但这是合理的,任务紧急的进程具有高的优先级,而不那么急的任务给它一个低优先级就可以了,这样可以保证系统资源能够更有效地被分配和利用。

在 Linux 中,进程的优先级是通过 Nice 值 来设置的。Nice 值的范围是 -20 到 19,其中:

  • nice 值越低,进程的优先级越高。
  • nice 值越高,进程的优先级越低。

这可以简单记住为:“你越‘友善’(nice),就越愿意与其他进程分享资源”,即进程的优先级会降低。

Linux系统Nice值刻度图

如果你希望在启动某个进程时为它设置一个特定的优先级(nice 值),可以使用 nice 命令:

$ nice -n <level> <command>

例如,如果你希望压缩文件的任务在后台慢慢执行,而不影响系统的响应速度,可以设置一个较高的 nice 值(即较低的优先级):

$ nice -n 19 tar -cvf test.tar file

如果进程已经在运行,也可以通过 renice 命令动态调整它的优先级,命令格式如下:

$ renice -n <priority> <pid>

例如,假设有一个正在运行的进程,PID 为 123,则可以使用 renice 命令将其优先级设置为给定值。

$ renice -n 18 123
nice 命令权限限制

如果您不是 sudo 组的成员(或基于 Red Hat 的发行版上的 wheel 组的成员),那么使用 nice 命令时会受到一些限制。为了说明这一点,先尝试以非 sudo 用户身份运行以下命令:

$ nice -n -1 tar -cvf test.tar file

nice: cannot set niceness: Permission denied

普通用户设置负nice值被拒绝

这表明普通用户无法将 nice 值设置为负数。换句话说,非管理员用户只能将进程的 nice 值设为 0 或更高的值,不能将已运行进程的优先级调低。

为了验证这一点,我们可以做个小实验。

首先,在后台启动一个 sleep 进程,并将其 nice 值设置为 2:

$ nice -n 2 sleep 10000 &

接下来,找出刚刚创建的进程其 PID。

查找sleep进程PID

现在,尝试将进程的nice设置为低于您最初指定的值2(如从 2 调到 1)。

$ renice -n 1 8363

普通用户调低nice值失败

你会注意到,系统不允许执行这个操作。普通用户只能将 nice 值调高(即降低优先级),而不能反向调整。

但是现在如果您选择以 sudo 身份执行命令,您将能够将nice设置为较低的值。

使用sudo成功调低nice值

现在我们已经了解了 nice 和 renice 命令,下面让我们看看如何在 Linux 上实时监控进程。

监控 Linux 上的进程

在 Linux 上使用 top

在日常运维或开发中,了解系统的实时运行状况是非常重要的,而 top 命令正是最常用的进程监控工具之一。

top 是一个交互式命令,任何用户都可以运行,用于查看系统上当前正在执行的进程列表及资源使用情况。

要启动它,只需在终端中输入:

$ top

top 会以交互模式运行,并实时刷新系统的进程信息,默认每隔三秒更新一次。如果希望它运行固定的次数后自动退出,可以通过 -n 参数指定迭代次数,例如:

$ top -n <number>

top命令界面截图

top 命令输出的顶部显示系统的一些统计信息,例如正在运行的任务数、CPU 使用的百分比或内存消耗。

紧接着这些统计信息下方是当前主机上所有进程的实时列表。这个列表会每隔三秒自动刷新一次,以显示最新的系统活动。不过,你可以通过在 top 界面中按下 d 键,手动更改刷新频率,并输入新的时间间隔。

top命令调整刷新频率

top命令还有很多玩法,可以 man top 查看帮助文档了解。

在 Linux 上使用 htop

虽然 top 已经能很好地展示系统的实时状态,但它的交互体验相对原始。如果你希望以更直观、更友好的方式查看和管理系统进程,那么可以试试 htop 命令。

htop 提供了一个彩色的、图形化的终端界面,能够直观显示 CPU、内存、以及交换分区(Swap)的使用情况。相比传统的 top,它不仅信息更丰富,还支持通过键盘进行上下滚动、筛选和终止进程等交互操作,让系统监控更高效。

需要注意的是,htop 并不是所有 Linux 发行版中默认安装的工具,因此如果系统提示找不到该命令,需要先手动安装。

对于基于 Debian/Ubuntu 的系统,可运行:

$ sudo apt-get update
$ sudo apt-get install htop

而在 Red Hat / CentOS 等基于 RPM 的系统上,则可以使用以下命令:

$ sudo yum -y install epel-release
$ sudo yum -y update
$ sudo yum -y install htop

最后,要运行 htop,只需要执行以下命令就可以了,可以不带任何选项。

$ htop

htop命令界面截图

你会发现,htop 的界面与 top 十分相似,但显示效果更直观:不同颜色代表不同类型的系统资源使用情况,进程列表可以通过方向键自由滚动,还能实时筛选或终止进程。

希望这篇文章能帮助你系统地理解 Linux 进程管理。如果你想了解更多系统层面的知识或与其他开发者交流,可以访问云栈社区获取更多技术资源。




上一篇:Spring Boot 3.2与ShardingSphere-JDBC:高并发分库分表实战指南
下一篇:深入解析YOLOv8模型输出、后处理流程及DFL计算
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 00:26 , Processed in 1.264879 second(s), 44 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表