Linux操作系统基础篇:深度解析进程的常用命令

在日常生活中,你是否常常一边听着音乐,一边编辑文档,还时不时切换到浏览器查阅资料?在计算机的世界里,Linux 系统也在进行着类似的 “多任务” 操作,而这一切的背后,离不开 “进程” 这个关键角色。进程,简单来说,就是正在运行的程序实例。当你在 Linux 系统中启动一个程序,比如打开文本编辑器 Vim,系统就会为这个程序创建一个进程,分配必要的资源,如内存、CPU 时间等,让它能够在系统中 “活跃” 起来。这里要注意区分程序(Program)和进程(Process)。

程序是存储在磁盘上的可执行文件,它是静态的,就像一本写满指令的 “说明书”,静静地等待被执行;而进程则是程序的动态执行过程,它有自己的生命周期,从创建、运行到最终结束,就像一个充满活力的 “执行者”,按照程序的指令在系统中穿梭。进程的动态性体现在它会随着程序的执行而不断变化状态。在执行过程中,进程可能因为等待输入输出、获取资源等原因暂停,也可能在得到 CPU 的调度后继续运行 ,这种动态变化使得 Linux 系统能够高效地管理和调度多个任务。

一、进程控制块(PCB)

在 Linux 系统中,进程的管理离不开进程控制块(Process Control Block,简称 PCB),它就像是进程的 “秘密档案”,记录了进程的各种关键信息,是操作系统对进程进行管理和调度的重要依据。

当一个程序被加载到内存成为一个真正的进程时,操作系统会创建一个 PCB 来描述它。PCB 中存储了进程的标识符、状态、优先级、内存指针、程序计数器、I/O 状态信息等。以进程标识符(PID)来说,它是唯一标识一个进程的数字,就像我们每个人的身份证号码一样,操作系统通过 PID 来识别和管理不同的进程 。进程状态则记录了进程当前是处于运行、就绪、等待等哪种状态,以此决定进程的调度顺序。

除了这些核心字段,PCB 中还有一些其他字段也发挥着重要作用。比如优先级字段,它决定了进程在竞争 CPU 资源时的优先程度,优先级高的进程更容易获得 CPU 时间,从而优先执行;内存指针字段,它指向进程的代码段、数据段和栈段,让操作系统清楚进程的内存布局,便于进行内存管理 。

在 Linux 中,PCB 是通过task_struct结构体来实现的。这个结构体定义在 Linux 内核代码中,包含了与进程相关的所有信息,是内核进行进程管理和调度的核心。如果你对task_struct结构体的具体内容感兴趣,可以通过查看 Linux 内核源代码来一探究竟 。

通过一些命令,我们可以查看进程的相关信息,从而了解 PCB 中的部分内容。比如使用ps -ef命令,可以查看当前系统中所有进程的详细信息,包括进程 ID、父进程 ID、用户、CPU 使用率、内存使用率等;top命令则提供了一个交互式的进程状态监视界面,能实时显示进程的 CPU 使用率、内存使用率等动态信息 ,让你对进程的运行状态一目了然。

二、Linux进程的创建

在 Linux 系统中,创建进程是一个常见且重要的操作,而fork()函数就是实现这一操作的关键 “魔法” 函数。

fork()函数的作用是创建一个子进程,这个子进程是父进程的一个副本。当fork()函数被调用时,操作系统会为子进程分配一个新的进程控制块(PCB),并复制父进程的大部分资源,包括代码段、数据段、堆、栈等 。这里的复制采用了 “写时拷贝”(Copy-On-Write,简称 COW)技术,也就是说,在子进程创建之初,父子进程共享相同的物理内存页面,只有当其中一个进程试图修改数据时,才会真正复制一份数据到新的物理内存页面,这样可以节省内存资源,提高创建进程的效率 。

在代码层面,fork()函数调用一次会返回两次,在父进程中返回子进程的进程 ID(PID),而在子进程中返回 0 。通过判断fork()函数的返回值,我们可以区分父子进程,从而让它们执行不同的代码逻辑。下面是一个简单的示例代码:

复制
#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { // 创建子进程失败 perror("fork error"); return 1; } else if (pid == 0) { // 子进程 printf("I am the child process, my PID is %d, my parents PID is %d\n", getpid(), getppid()); } else { // 父进程 printf("I am the parent process, my PID is %d, and I just created a child with PID %d\n", getpid(), pid); } return 0; }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.

在这个示例中,fork()函数创建了一个子进程。父进程和子进程都会继续执行fork()函数之后的代码,通过判断pid的值,我们可以让父子进程分别打印出不同的信息,展示它们的身份和关系 。

在父子进程共享代码和数据方面,虽然它们在创建之初共享相同的物理内存页面,但这并不意味着它们的数据是完全共享的。当其中一个进程对数据进行写操作时,由于写时拷贝机制,会为该进程分配新的物理内存页面来存储修改后的数据,而不会影响另一个进程的数据 。例如,父子进程都有一个全局变量count,如果子进程修改了count的值,父进程中的count值并不会改变 ,这体现了进程之间数据的独立性。

写时拷贝技术在进程创建中有着显著的优势。它避免了在进程创建时对大量数据的不必要复制,大大提高了进程创建的速度,减少了内存的占用 。尤其是在创建大量进程或者复制大量数据的场景下,写时拷贝技术能够显著提升系统的性能和资源利用率 。比如在服务器应用中,可能需要频繁创建子进程来处理客户端的请求,使用写时拷贝技术可以让服务器更高效地响应请求,减少系统开销 。

除了fork()函数,exec系列函数也是进程创建和控制中的重要角色,它们的作用是用一个新的程序替换当前进程的内存空间,包括代码段、数据段、堆和栈等 。也就是说,当一个进程调用exec系列函数时,它会放弃当前正在执行的程序,转而执行一个新的程序 。

exec系列函数有多个变体,如execl、execv、execlp、execvp、execle、execvpe等 ,它们的主要区别在于参数的传递方式和对环境变量的处理。以execl函数为例,它的函数原型为int execl(const char *path, const char *arg, ...);,其中path是要执行的程序的路径,arg是传递给程序的参数列表,参数列表以NULL结尾 。而execlp函数的原型为int execlp(const char *file, const char *arg, ...);,它会根据环境变量PATH来查找要执行的程序,不需要指定完整的路径 。

在实际应用中,fork()和exec()常常结合使用 。比如在 Shell 中,当我们输入一个命令时,Shell 会先调用fork()创建一个子进程,然后子进程调用exec()函数来执行用户输入的命令 。这样可以保证 Shell 进程在执行命令的过程中不会被阻塞,能够继续接受用户的输入 。下面是一个简单的示例代码,展示了fork()和exec()的结合使用:

复制
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { perror("fork error"); exit(1); } else if (pid == 0) { // 子进程 execlp("ls", "ls", "-l", NULL); // 如果exec执行失败,会执行到这里 perror("exec error"); exit(1); } else { // 父进程 wait(NULL); // 等待子进程结束 printf("Child process has finished.\n"); } return 0; }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.

在这个示例中,父进程调用fork()创建子进程,子进程调用execlp()函数执行ls -l命令,列出当前目录下的文件列表 。如果exec执行成功,子进程的内存空间会被ls程序替换,不会再返回原来的代码;如果执行失败,会打印错误信息并退出 。父进程通过wait(NULL)等待子进程结束,然后打印提示信息 。

三、Linux进程的前台、后台与守护进程

在 Linux 系统中,进程有着不同的 “生活方式”,其中前台进程、后台进程和守护进程是最为常见的三种类型,它们在运行特性、与用户交互方式以及应用场景等方面都有着明显的区别。

3.1 前台进程:与用户直接互动的 “活跃分子”

前台进程是与用户直接交互的进程,它会独占当前的终端(Terminal)。当你在终端中输入一个命令并直接执行,如ls -l,这就启动了一个前台进程。在这个进程运行期间,终端会被它占用,你无法在终端中输入其他命令,直到该进程执行完毕或被你手动终止 。比如,当你运行一个需要用户不断输入数据的程序时,它就是前台进程,你必须专注于与这个进程交互,等待它的响应 。

3.2 后台进程:默默工作的 “幕后英雄”

后台进程则是在后台运行的进程,它不会占用终端的输入输出,你可以在启动后台进程后继续在终端中执行其他命令 。在命令行尾加上 “&” 符号,就可以将一个命令放到后台执行 。例如,如果你要运行一个耗时较长的脚本test.sh,不想让它阻塞终端,可以使用./test.sh &的方式启动它 。

后台进程继承当前会话(Session)的标准输出(stdout)和标准错误(stderr),所以它的输出依然会同步地在命令行下显示,但它不再继承当前会话的标准输入(stdin),你无法向这个任务输入指令 。如果你想查看当前终端后台运行的任务,可以使用 jobs 命令;若要将后台中的命令调至前台继续运行,使用 fg 命令;而 bg 命令则可以将一个在后台暂停的命令,变成在后台继续执行 。

3.3 守护进程:系统稳定运行的 “忠诚卫士”

守护进程(Daemon)是一种特殊的后台进程,它完全脱离控制终端和会话,在系统后台默默地运行,不受用户登录和注销的影响 。它的主要特点包括无控制终端,避免受到终端的干扰;不占用前端资源,允许正常执行其他 bash 命令 。Linux 系统的大多数服务器就是用守护进程实现的,比如 Internet 服务器 inetd、Web 服务器 httpd、邮件服务器 sendmail、数据库服务器 mysqld 等 。

这些守护进程在系统启动时就开始运行,除非强行终止,否则会一直运行到系统关机 。守护进程一般以 root 用户权限运行,因为它们需要使用某些特殊的端口(1 - 1024)或者资源 。而且,守护进程的父进程一般都是 init 进程,它是非交互式程序,没有控制终端,所以任何输出都需要特殊处理,通常会将标准输入、输出、错误重定向到/dev/null(黑洞文件)或者日志文件 。

为了让进程变成守护进程,可以使用nohup命令,它可以在你退出帐户 / 关闭终端之后继续运行相应的进程 。例如,nohup ./test.sh > a.txt 2>&1 &,这个命令将test.sh脚本以守护进程的方式运行,输出重定向到a.txt文件 。也可以在代码层面通过调用daemon函数或者手动实现一系列步骤,如创建子进程、调用setsid创建新会话、改变工作目录、重设文件权限掩码、关闭不需要的文件描述符等 。

以 Web 服务器httpd为例,它作为守护进程,在系统后台持续运行,监听特定的端口(如 80 或 443),等待客户端(如浏览器)的请求 。当收到请求时,它会处理请求并返回相应的网页内容,为用户提供 Web 服务 ,整个过程无需用户手动干预,也不会受到终端操作的影响 。再比如系统日志进程syslogd,它负责记录系统的各种日志信息,从系统启动开始就一直默默运行,不断地将系统产生的日志消息记录到指定的日志文件中,为系统的维护和故障排查提供重要依据 。

四、Linux进程管理常用命令

在 Linux 系统中,熟练掌握进程管理命令是高效运维和开发的关键。这些命令就像是一把把 “瑞士军刀”,在监控和控制进程时发挥着不可或缺的作用。下面,我们就来深入了解一些常用的进程管理命令 。

4.1 ps:进程状态查看利器

ps(Process Status)命令用于查看当前系统中运行的进程状态,它就像是进程世界的 “摄影师”,能为我们拍摄进程的 “快照”,提供有关进程的详细信息 。

该命令将显示进程的详细信息,例如进程 ID、占用 CPU 的百分比、进程的状态、运行时间等等。

图片

常用选项及示例

-ef:以完整格式显示所有进程信息,包括用户 ID(UID)、进程 ID(PID)、父进程 ID(PPID)、CPU 使用率(C)、启动时间(STIME)、终端设备(TTY)、进程运行所占的 CPU 时间(TIME)以及启动该进程的命令(CMD)等 。例如,执行ps -ef,会得到类似如下的输出:

复制
UID PID PPID C STIME TTY TIME CMD root 1 0 0 08:30? 00:00:01 /sbin/init user 101 1 0 09:00 pts/0 00:00:00 bash1.2.3.

-aux:显示所有用户的进程,包括用户(USER)、PID、CPU 使用率(% CPU)、内存使用率(% MEM)、虚拟内存大小(VSZ)、常驻内存大小(RSS)、终端设备(TTY)、进程状态(STAT)、启动时间(START)、进程使用的 CPU 时间(TIME)以及启动进程的命令(COMMAND)等 。例如,ps -aux的输出如下:

复制
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.1 20016 1212? Ss 08:30 0:00:01 /sbin/init user 101 0.1 0.5 12288 5124 pts/0 R+ 09:00 0:00:00 bash1.2.3.
-ef和-aux的区别在于,-ef是 UNIX 风格的选项,更关注进程的关系(如 PPID 和 UID);而-aux是 BSD 风格的选项,更侧重于进程的资源使用(如 % CPU 和 % MEM) 。-C:根据命令名称查找进程,例如ps -C firefox,可以查找出firefox进程的相关信息 。-p:根据进程 ID 查找进程,如ps -p 1234,可以查看进程 ID 为 1234 的进程状态 。

4.2 top:实时监控的动态仪表盘

top命令是 Linux 系统中常用的实时系统监控工具,它就像是一个动态的 “仪表盘”,能够实时展示系统中各个进程的资源占用状况,类似于 Windows 的任务管理器 。

该命令将显示进程的详细信息,例如进程 ID、占用 CPU 的百分比、进程的状态、运行时间等等。您还可以使用 top 命令来查看进程的资源使用情况,例如 CPU、内存和 I/O。

图片

主要功能和用途

实时监控:提供一个实时的、动态的视图,展示系统当前的状态,包括系统运行时间、登录用户数、平均负载等 。平均负载是指在特定时间间隔内,系统处于运行状态和不可中断状态的平均进程数,是衡量系统负载的重要指标 。进程管理:允许用户查看系统中各个进程的运行状态,包括PID、用户、优先级、虚拟内存使用量、物理内存使用量、共享内存量、状态(如运行、睡眠、停止等)、CPU使用率、内存使用率、运行时间以及命令行名称等 。用户可以通过交互命令对进程进行排序、杀死、调整优先级等操作 。资源监控:监控 CPU 的总体使用率、用户空间占用率、系统空间占用率等;展示物理内存和交换空间(swap)的使用情况,包括总量、已用量、空闲量等 。

基本用法:在终端中输入top命令并回车,即可启动top程序。默认情况下,它会显示系统中所有进程的列表,并按照 CPU 使用率进行排序 。

常用选项

-u:仅显示指定用户的进程,如top -u root,只显示root用户的进程 。-n:指定top命令更新的次数,之后自动退出,例如top -n 5,表示更新 5 次后退出 。-d:设置屏幕更新的间隔时间,默认为 3 秒,如top -d 5,表示每 5 秒更新一次屏幕 。-b:以批处理模式运行,通常与重定向结合使用,将输出保存到文件中,例如top -b -n 1 > top_output.txt,将单次更新的数据快照保存到top_output.txt文件中 。-H:以线程模式显示,显示每个线程的详细信息,而非仅显示进程 。

交互命令:在 top 运行时,用户可以通过一系列交互命令来改变显示的内容或排序方式 。例如,按P键以 CPU 使用率排序;按 M 键以内存使用率排序;按T键以时间 / 累计时间排序;按 f 或 F 键进入字段管理界面,允许用户自定义显示的字段;按k键杀死一个进程,需要输入进程的 PID 和信号;按r键重新设定进程的优先级;按 q 键退出top 。

4.3 htop:更强大的交互式监控工具

htop是一款强大的交互式系统监控工具,它在top的基础上进行了增强,提供了更直观的界面和更丰富的操作功能 。该命令将显示进程的详细信息,例如进程 ID、占用 CPU 的百分比、进程的状态、运行时间等等。您可以使用 htop 命令来查看进程的资源使用情况,例如 CPU、内存和 I/O,并且可以使用键盘快捷键来进行交互式操作。

图片

特点和优势

友好的界面:使用彩色显示,信息一目了然,支持鼠标操作,操作更加便捷 。详细的进程信息:不仅显示进程基本信息,还展示每个进程完整命令行,方便用户了解进程的具体执行情况 。强大的交互功能:提供了更多的快捷键操作,例如按F1键显示帮助页面;按F2键进入设置菜单;按F3键搜索进程;按F4键过滤进程;按F5键以树状视图显示进程层次结构;按F6键选择不同的排序方式,如按 CPU 使用率、内存使用率等;按F7和F8键增加或减少进程的nice值;按F9键杀死进程;按F10键退出htop 。

显示界面:htop命令显示的界面主要由标题栏、进程列表、柱状图区域和快捷键提示栏四个部分组成 。标题栏位于界面的顶部,显示系统的整体状态,包括 CPU 使用率、内存占用、进程数等;进程列表位于界面的主要部分,显示当前运行的进程及其相关信息;柱状图区域位于界面的左侧或右侧或顶部,以柱状图的形式展示系统资源的使用情况,如 CPU 使用率、内存占用、磁盘读写等;快捷键提示栏位于界面的底部,显示常用的快捷键操作,帮助用户快速了解和使用htop的功能 。

常用选项

-d:设置更新之间的延迟,例如htop -d 5,表示屏幕更新之间的延迟为 5 秒 。-u:仅显示用户拥有的进程,如htop -u user,只显示user用户的进程 。-p:仅显示具有特定 ID 的进程,如htop -p 1234,只显示进程 ID 为 1234 的进程 。-s:对给定列的进程进行排序,如htop -s %CPU,按 CPU 使用率对进程进行排序 。-t:在命令列的树视图中显示进程层次结构 。--no-color:在单色模式下打开htop,禁用颜色 。

4.4 pgrep:精准查找进程 ID

pgrep是一个用于根据名称、用户、组和其他标准搜索进程的实用程序,它能帮助我们快速找到匹配给定模式的运行进程的进程 ID(PID) 。

常用选项及示例

-u:查找特定用户拥有的进程,例如pgrep -u root,查找root用户拥有的所有进程 。-g:查找特定组中的进程,如pgrep -g group_name,查找属于group_name组的进程 。-P:查找给定父 PID 的子进程,例如pgrep -P 1234,查找父进程 ID 为 1234 的所有子进程 。-f:与完整命令行进行匹配(不仅仅是进程名称),如pgrep -f "python app.py",查找执行python app.py命令的进程 。-l:显示进程名称及其 PID,例如pgrep -l bash,输出结果中会同时显示进程 ID 和进程名称 。-o:仅返回第一个匹配的进程,如pgrep -o firefox,只返回第一个匹配的firefox进程的 ID 。-v:反向搜索(返回与模式不匹配的进程),例如pgrep -v -f "bash",返回所有不是执行bash命令的进程 。-c:打印出匹配的数量,如pgrep -c java,输出当前系统中java进程的数量 。-x:完全匹配(即匹配命令的全名),例如pgrep -x sshd,只匹配进程名为sshd的进程,而不会匹配包含sshd的其他进程名 。-d:指定 PID 的分隔符,默认是换行符,如pgrep ssh -d ,输出的多个ssh进程 ID 之间用空格分隔 。-i:匹配时不区分大小写,例如pgrep -i FIREFOX,可以匹配到firefox进程 。-n:仅选择最新的(最近启动的)匹配进程,如pgrep -n chrome,返回最近启动的chrome进程的 ID 。

4.5 pkill:精准终止进程

pkill命令用于根据进程名称或其他属性终止进程,它结合了pgrep和kill的功能,能够更方便地终止符合条件的进程 。

常用选项及示例

-9:强制杀死进程,这是最常用的选项之一,用于终止那些难以正常结束的进程 。例如,pkill -9 firefox,强制杀死所有firefox进程 。-15:默认信号,用于正常终止进程,尝试让进程优雅地关闭 。例如,pkill -15 apache2,正常终止所有apache2进程 。-u:指定运行用户,例如pkill -9 -u redis redis-server,强制杀死redis用户运行的redis-server进程 。-f:与完整命令行进行匹配,如pkill -f "python app.py",终止执行python app.py命令的进程 。

4.6 kill:灵活的进程信号发送

kill命令用于向进程发送信号,它可以让我们对进程进行各种控制,如终止进程、暂停进程、恢复进程等 。

信号介绍:在 Linux 系统中,信号是一种异步事件通知机制,用于向进程传递各种事件或请求 。常见的信号有:

SIGTERM(15):默认的终止信号,进程收到该信号后,会尝试正常终止,清理资源等 。SIGKILL(9):强制终止信号,进程收到该信号后,会立即终止,不会进行任何清理操作,通常用于终止那些无法正常响应SIGTERM信号的进程 。SIGSTOP(17,19,23):暂停进程,进程收到该信号后,会停止运行,但不会释放资源 。SIGCONT(18,20,25):恢复被暂停的进程,让其继续运行 。

使用示例

要终止进程 ID 为 1234 的进程,可以使用kill 1234(默认发送SIGTERM信号),如果该进程没有响应,可以使用kill -9 1234强制终止 。要暂停进程 ID 为 5678 的进程,可以使用kill -STOP 5678;要恢复该进程,可以使用kill -CONT 5678 。

4.7 killall:按名称批量终止进程

killall命令用于根据进程名称终止一组进程,它可以一次性终止所有符合条件的进程,非常方便 。

使用示例

要终止所有httpd进程,可以使用killall httpd,它会向所有名为httpd的进程发送SIGTERM信号,尝试正常终止它们 。如果需要强制终止,可以使用killall -9 httpd,向所有httpd进程发送SIGKILL信号 。

4.8 pgrep:强大的进程查找

pgrep命令是一个强大的进程查找工具,它可以根据各种条件查找进程 。除了前面提到的选项,还可以结合正则表达式进行更灵活的查找 。示例

要查找所有命令行中包含apache的进程,可以使用pgrep -f apache 。要查找当前用户运行的所有bash进程,可以使用pgrep -u $USER bash 。

4.9 pidof:快速获取进程 ID

pidof命令用于快速获取指定进程名称的进程 ID,它的输出非常简洁,只包含进程 ID 。

使用示例:要获取nginx进程的 ID,可以使用pidof nginx,如果系统中有多个nginx进程,它会输出所有进程的 ID,以空格分隔 。

4.10 nice和renice:调整进程优先级

在 Linux 系统中,进程的优先级决定了它在竞争 CPU 资源时的优先程度 。nice和renice命令用于调整进程的优先级,让我们可以根据实际需求合理分配系统资源 。

nice 命令:nice命令用于以指定的优先级启动一个新进程 。优先级的范围是 - 20(最高优先级)到 19(最低优先级),默认优先级是 0 。例如,要以较高的优先级(-5)启动python程序,可以使用nice -n -5 python my_script.py。这里,-n选项用于指定优先级 。renice 命令:renice命令用于调整已经运行的进程的优先级 。例如,要将进程 ID 为 1234 的进程优先级调整为 10,可以使用renice 10 1234 。如果要调整所有属于user用户的进程优先级为 5,可以使用renice 5 -u user 。

THE END