
管理Linux服务器这些年我遇到过太多人在任务、进程、程序这三个词上栽跟头了。最典型的场景就是用SSH登录服务器跑起来一个服务关闭终端窗口结果服务也跟着一起断掉或者进程怎么都杀不死kill -9 都快按烂了也不管用再或者明明看到系统里一堆不明进程却不知道该怎么把它们找出来、判断是什么任务。标题里的“任务、进程、程序”看着像一回事实际在Linux里是完全不同的三层东西。这篇就围绕“查看正在运行的后台程序”和“终止任务”这两件事把我自己的排查方法和踩过的坑一起整理出来保证看完能直接上手用。1. 程序、进程、任务先把三个概念掰扯清楚很多教程一上来就丢命令但如果你不清楚自己在管理什么命令组合得再熟练也容易出事故。我习惯用一个生活化的比喻来解释这三者程序就是菜谱进程就是照着菜谱做菜的过程任务则是你在这个厨房终端会话里排的队。1.1 程序是静态的进程才是动态的程序Program就是磁盘上那些可执行文件以及它们依赖的库文件、配置文件。它躺在硬盘上什么都不做只是等待被运行。你可以把程序看作一个菜谱写满了步骤但如果不进厨房动手它就只是一张纸。当内核把程序加载到内存里给它分配资源、设置好运行状态之后才叫进程Process。进程是动态的它有自己的PID进程ID、内存空间、打开的文件描述符、当前状态运行、睡眠、停止、僵尸等等。举我实际工作里的例子nginx这个程序它在磁盘上就是/usr/sbin/nginx这个文件加上一堆配置。一旦启动系统里会出现一个master进程加多个worker进程每个都有自己的PID占着不同的内存和CPU这才是进程。看进程用ps、top看程序文件用which、ls这俩千万别混。1.2 任务和终端会话绑定在一起的作业任务Job这个概念最容易搞混。它其实是指Shell会话里的作业也就是你从同一个终端窗口启动的一个或多个进程组成的进程组。jobs命令看到的不是全系统进程而是“当前Shell会话专属”的后台任务。不同终端窗口之间看不到对方的jobs因为jobs表是挂在Shell进程上的。这点和ps完全不一样ps看到的是整个系统的进程视图。这里还要提一下线程Thread。一个进程内部可以跑多个线程它们共享进程的内存空间但各自有独立的执行栈。top里按H可以切换线程视图ps -eLf也能看到线程。你可以把进程想成一家公司线程是公司里的员工共用公司的办公室和资源。1.3 为什么要区分这三者因为排查问题的方向和手段完全不一样程序出问题是文件损坏、权限不对、依赖缺失解决方向是文件系统层面。进程出问题是资源占用高、状态卡死、互相打架解决方向是内存、CPU、信号。任务出问题是终端挂了、后台作业丢失、SIGHUP误伤解决方向是作业控制机制。我见过有人查程序端口被占用第一反应居然是去删二进制文件这是典型的概念不清。真实案例是启动服务时提示端口被占用正确做法是用lsof或ss找到占端口的那个PID看它是什么进程再决定是等待还是杀掉而不是去改配置文件或者重装程序。2. 查看正在运行的后台程序按场景选工具别只会用ps很多人一上来就敲ps -ef然后在一堆输出里翻滚着找自己要的信息。不是说ps不好而是它有自己的适用场景看后台任务、看动态状态、按名字找人、按端口找人都应该用不同的工具。2.1 当前Shell的后台任务用jobs如果你只是想知道当前终端里有哪些命令放到了后台jobs是最直接的工具$ sleep 300 [1] 12345 $ sleep 400 [2] 12346 $ jobs [1]- Running sleep 300 [2] Running sleep 400 输出里有个号表示最近放到后台的任务-表示第二新的任务。这个表只属于当前Shell。一旦你退出终端这个表也就没了那些后台任务会成为孤儿进程但进程本身不一定会退出具体机制第三章会细说。2.2 全系统层面的进程用ps需要看全系统进程时我通常用ps aux或ps -ef两者区别不大aux是BSD风格-ef是标准风格。比较关键的是知道每一列在说什么$ ps aux | head -5 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.4 168776 13704 ? Ss Feb10 0:07 /usr/lib/systemd/systemd root 2 0.0 0.0 0 0 ? S Feb10 0:00 [kthreadd]STAT这一列是进程状态的浓缩体S是睡眠sleepingR是运行runningT是停止stoppedZ是僵尸zombieI是空闲idle。后面的小写字母表示附加信息s是会话领导者l多线程在前台进程组。如果我没记错新手最容易忽略的是TTY这一列?表示这个进程不关联任何终端通常就是守护进程或者由systemd拉起的服务pts/0表示它挂在某个伪终端下你退出那个SSH会话就可能影响到它。这列信息对判断“关闭窗口后任务会不会死”很有用。2.3 按名字找PID用pgrep和pidof已知进程名想找PID没必要ps aux | grep再手动找第二列直接用pgrep$ pgrep -a nginx 1234 /usr/sbin/nginx -c /etc/nginx/nginx.conf-a参数能同时显示命令行。pidof nginx则更简洁只输出数字。这两个命令在我写运维脚本时非常常用因为脚本里需要动态获取PID做操作。2.4 查看进程的CPU、内存实时变化用top/htopps只是一瞬间的快照想看实时变化必须用top。top里面几个常用操作按P按CPU排序按M按内存排序按k可以直接输入PID发信号按H切换线程视图。如果你的系统装了htop那体验会更好支持鼠标操作和树状视图对刚入门的人更友好。2.5 进程和网络占用的关联从端口找进程热搜词里有一条“centos怎么看进程的网络占用”这个问题我几乎每个月都会碰到。服务器上有个端口莫名其妙被占或者怀疑某个进程在偷偷往外面发包这时候要先从网络出发找进程。$ ss -tlnp | grep :8080 LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:((java,pid8888,fd63))ss的-tlnp组合很经典显示TCP监听、带进程信息。没有ss的老系统可以用netstat -tlnp。注意需要root权限才能看到别人的进程名。反过来已知进程想看它开了哪些端口和连接用lsof$ lsof -i -P -n | grep java java 8888 user 63u IPv6 12345 TCP *:8080 (LISTEN)-P不解析端口名-n不解析主机名速度更快也更清晰。2.6 进入进程内部/proc目录这是很多教程不会细讲但实际排查特别有用的地方。每个PID在/proc/pid/下都有一个目录里面几乎包含了进程的所有信息cat /proc/8888/cmdline查看完整的命令行参数比ps更全ls -l /proc/8888/cwd看进程的工作目录ls -l /proc/8888/fd看进程打开的每一个文件描述符包括socketcat /proc/8888/status看进程内存、状态、父子进程ID有一次线上服务一直报磁盘找不到文件配置文件里明明写了绝对路径我当时就是用/proc/pid/cwd发现进程实际工作目录跟预期不一致根因是启动脚本里cd到了别的地方。这种问题光看ps是真的看不出来的。3. 让程序正确在后台运行为什么命令加个还是会被杀标题里提到“查看正在运行后台程序”但很多人连“后台程序怎么产生的”都含糊。最常见的误解是在命令后面加一个程序就会一直在后台跑着。实际上完全不是这么回事。3.1 只是把任务丢到当前Shell的后台sleep 300 的效果是把任务放到当前Shell的作业表里让你可以继续输入其他命令。但它仍然属于这个终端会话。当你关闭终端时Shell会给它的作业发送SIGHUP挂断信号默认行为就是终止进程。这就是为什么很多人第一次用启动服务关掉SSH窗口后服务就没了。我在文章开头说的那个场景根因就是这个。3.2 nohup的本意是忽略HUP信号nohup的全称是no hang up不挂断它的作用是让启动的程序忽略SIGHUP信号nohup ./myserver /tmp/myserver.log 21 这个写法的意思是启动myserver忽略终端挂断信号把标准输出和标准错误都重定向到日志文件然后放到后台。注意nohup只解决“终端关闭导致进程被杀”的问题不解决进程退出后无法自动拉起的问题也不解决系统重启后进程丢失的问题。3.3 让任务彻底脱离终端setsidnohup已经能应对大部分场景了但它还没有完全脱离会话进程还是原会话的成员。想要更彻底地“断绝关系”可以用setsid启动一个新会话setsid ./myserver /dev/null /tmp/myserver.log 21 setsid会让进程成为新会话的领导者完全脱离控制终端。更加注意即使父Shell退出、会话关闭进程也能安然无恙地活着。还有个更简便的方式是双层fork在Shell里跑一个脚本脚本内部再setsid或nohup启动真正的程序。3.4 已经启动的任务想脱手disown已经跑起来的前台任务或后台任务如果当时没加nohup也不用慌。可以先用CtrlZ把前台任务暂停再用bg丢到后台继续运行最后用disown把它从Shell的作业表里移除这样Shell退出时就不会给它发SIGHUP了$ ./myserver ^Z [1] Stopped ./myserver $ bg [1] ./myserver $ disown -h %1这里%1是作业号作业号可以从jobs命令看到。disown -h是标记该作业忽略HUP信号作业仍然在作业表里不带-h则是直接把作业从表中移除。3.5 更可靠的后台方案screen / tmux / systemd到了生产环境用nohup和setsid管理服务依然有问题进程挂了你不知道不会自动重启也没法轻易再进入它的控制台。这时候用tmux或screen会方便得多$ tmux new -s myserver $ ./myserver # 在tmux会话内运行然后按 Ctrlb 再按 d 脱离会话 $ tmux attach -t myservertmux实际上是一个终端复用器它在后台维护一个常驻的会话就算SSH断开会话和里面运行的程序都还在重新登录后还能再连回去看输出。这一点和nohup有本质区别nohup只是让进程忽略信号tmux是让进程始终有一个“终端”可以附着。如果是长期稳定的系统服务我更推荐直接配置成systemd服务由init进程托管自动重启、开机启动、日志收集全都能搞定已经是现代Linux的主流方案了。4. 终止任务的正确姿势kill信号机制的完整认知“终止任务”听起来像一键关闭但Linux里kill命令本质上是发信号不是直接“杀掉”。理解信号机制你才能真正安全地终止任务。4.1 kill不是杀是发信号kill这个名字误导了太多人。它真正的作用是把一个信号发送给指定PID的进程。进程收到信号后的行为取决于它是否注册了对应的信号处理函数以及信号本身的默认行为。最常用的信号是这些信号编号默认行为典型触发方式SIGHUP1终止进程终端关闭、nohup忽略它SIGINT2中断进程CtrlCSIGQUIT3终止并生成coreCtrl\SIGKILL9强制终止不可捕获kill -9SIGTERM15终止进程kill命令默认SIGSTOP19暂停进程不可捕获CtrlZ用SIGTSTP稍不同SIGCONT18恢复运行kill -CONTSIGCHLD17子进程退出时通知父进程内核自动发送这里必须强调一点SIGKILL9和SIGSTOP19是例外进程无法捕获也无法忽略它们。其他信号都可以被进程自定义处理这也是“为什么kill -15有时候杀不死”的根源。4.2 优雅终止优先默认应该先kill -15kill PID发的是SIGTERM15这是“请求终止”而不是“强制终止”。为什么说优雅优先因为SIGTERM给了进程一个清理资源的机会释放数据库连接、保存临时状态、删除锁文件。一个设计良好的服务都应该监听SIGTERM做优雅停机。我处理Java进程特别有感直接kill -9干掉JVM有时候会留下各种临时文件和监听端口的残留状态而且Spring Boot之类应用正在写的数据可能直接损坏。正确做法是先kill即15等待5~10秒如果进程还在再用kill -9兜底。很多运维在自己写的脚本里会这么写#!/bin/bash PID$(pgrep -f myserver.jar) kill $PID for i in $(seq 1 10); do if ! kill -0 $PID 2/dev/null; then echo 进程已退出 exit 0 fi sleep 1 done echo 优雅退出超时强制终止 kill -9 $PID这个脚本里kill -0 $PID不是发信号而是检测进程是否存活很实用的技巧。4.3 按名字终止killall和pkill指定PID杀进程是精准打击但有时候PID是动态的或者是一组进程比如worker进程一个一杀太低效。这时候可以用killall或pkill$ killall nginx $ pkill -f myserver.jarpkill是按进程名或命令行模式匹配pgrep的兄弟命令。-f参数表示匹配完整命令行灵活性更高。用-9参数就是pkill -9 -f和kill -9同理危险等级也更高因为可能误匹配到不相关的进程。一个安全习惯先跑pgrep -a确认匹配范围再跑pkill。我曾经用pkill -f test想杀测试程序结果把命令行里带“latest”的进程全误杀了教训深刻。4.4 其他需要掌握的终止场景杀掉某用户的所有进程pkill -u username按终端终止pkill -t pts/1SSH会话断开但有残留进程时的清理完整终止进程组kill -- -PGIDPGID前面要带负号ps -o pid,pgid可以看到进程组ID杀所有进程千万别乱用kill -9 -1表示向当前用户能信号覆盖的所有进程发SIGKILL我见过有人把这写在脚本里直接用后果是整个会话直接断开4.5 端口被占用的标准终止流程结合热词里那条“dpkg前端锁”的问题场景举一个典型排查链路。假设我要启动一个服务提示bind: address already in use$ ss -tlnp | grep 8080 LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:((java,pid8888,fd63))看到PID是8888。先确认这到底是什么进程别误杀了系统服务$ ps -fp 8888 UID PID PPID C STIME TTY TIME CMD root 8888 1 0 10:23 ? 00:00:13 /usr/bin/java -jar /opt/apps/old-server.jar确认是旧版本的服务一直在占用端口。如果这个进程允许被杀先kill 8888等几秒再检查端口是否释放没释放再kill -9 8888。我从来不在第一步就杀。5. 踩坑记录僵尸进程、杀不掉的任务和Shell管理误区前面把基本命令走了一遍但实际系统中总会遇到“不管怎么kill都不生效”的情况。这一章把我遇到过的三类典型问题拆开讲每个问题都给出完整的排查过程和最终解法。5.1 僵尸进程为什么kill -9都杀不掉僵尸状态在ps aux里显示为STAT为Z。很多人看到僵尸就慌了执行kill -9 PID发现没用更慌了。原理是这样的进程退出时内核会保留一个最小限度的记录PID、退出码、资源使用统计直到它的父进程调用wait()系统调用取走这个退出状态记录才会被彻底清除。如果父进程本身不调用wait最常见于父进程写得有bug或者父进程已经挂了还没被init进程收编这个进程就会进入僵死状态但它的PID和进程表项仍然存在。僵尸进程已经死了不可能再被杀死。kill -9对僵尸没用的原因就在这它是死尸不是活物。要清理僵尸只能处理它的父进程找到僵尸进程的PPIDps -o ppid -p ZombiePID看父进程是什么ps -fp PPID如果是父进程的bug导致它不回收子进程要么重启父进程要么杀死父进程让僵尸进程被initPID 1接管并回收如果父进程就是PID 1系统在容器里常见僵尸只能等整机重启生产环境里我们遇到过Java应用频繁fork子进程但不wait最后整个系统进程表堆满了僵尸新的进程都fork不了。排查方向重点在应用代码不在僵尸本身。5.2 不可中断睡眠D状态的进程怎么处理比僵尸更让运维头疼的是D状态进程。D表示不可中断睡眠一般是进程在内核态等待某种IO操作比如等待NFS服务器响应、等待磁盘IO完成、等待设备驱动。这期间进程不处理任何信号包括SIGKILL。典型的场景是NFS挂载突然失联客户端进程访问挂载目录时往里一卡直接进入D状态kill -9毫无反应。此时最有效的处理顺序是先确认是不是整个共享存储失联df -h | grep 挂载点如果NFS失联尝试恢复挂载让IO请求返回恢复无望时等内核把IO超时耗尽时间可能很长期间不要反复重启keepalived等服务那会加剧问题极端情况下只能重启机器但重启时也可能卡住需要在重启前先把异常挂载卸载掉D状态进程一般跟应用本身无关更多是存储和底层环境的问题。强行杀掉一个D状态进程是做不到的别浪费时间应该去追根因。5.3 任务管理的一个隐蔽坑关闭窗口后进程依然活着怎么办前面说“关闭终端可能杀掉后台进程”反过来还有一个坑有些进程明明挂着某个终端关掉终端后它却死活还在。这主要是因为进程已经完全脱离了原来Shell的进程组和会话Shell退出时的HUP信号找不上它。我自己遇到过的实际场景是用了setsid或者在脚本里把子进程重新用nohup启动SSH断开后进程还占着CPU。此时通过ps -ef | grep找到进程杀掉它是一方面更关键的是找到它的父进程链把它重新拉回systemd或者其他守护进程来管理否则下次还会发生同样的“野进程”问题。我清理野进程的习惯是$ ps -eo pid,ppid,stat,cmd | grep myserver看PPID是否是1孤儿进程。如果是1说明已经是init接管杀掉即可如果不是1得先处理它父亲否则你可能杀掉后它又被父进程拉起。5.4 dpkg前端锁等特殊锁文件问题的排查思路热搜词里那条“另外一个进程已经为dpkg前端锁加锁”也很典型。这个不能直接杀因为可能是合法的系统升级任务正在执行。完整排查链路$ ps aux | grep -E dpkg|apt|unattended如果有apt或dpkg进程在运行等待它结束就行。如果没有相关进程只是锁文件残留需要确认后手动清理锁。这一步我通常会再查一下进程的启动时间和日志确定不是被什么自动化任务占用了。这里想说一个更通用的方法是遇到“锁文件冲突”“端口占用”“PID文件已存在”这类问题不要急着删文件先找出是谁持有锁、谁写的PID文件。杀进程、删锁、重启的顺序错的话可能伤害到正在正常运行的服务。5.5 几个保命经验总结几条实操经验都是我用时间换来的杀进程之前先列出进程完整信息和在线时长确认不是业务核心。尤其是共享服务器上别人起的长任务你别顺手就kill了。能等一下的尽量等一等。有一些任务看起来没反应实际上在等资源或IO杀掉反而把现场破坏了。写自动化脚本时所有kill之后都加状态检查和超时兜底避免kill命令本身优雅退出失败导致更长的阻塞。用pgrep -f的时候注意正则转义pkill -f test.jar是安全的但如果模式是pkill -f test就可能误杀一大堆尽量用完整进程名或者加路径。不要迷信kill -9。它确实是最终手段但只该在SIGTERM确实无法完成清理时使用。用一次kill -9付出的代价可能是几十分钟手工恢复数据。6. 个人使用体会一套顺手的命令组合在每台Linux机器上我习惯提前把下面这组命令记熟用于日常任务和进程管理。组合起来基本能覆盖所有查看和终止场景# 查看当前会话后台任务 jobs # 查看全系统进程带完整命令行 ps -ef # 动态监控CPU内存 top htop # 按名字查询PID pgrep -a 服务名 # 按端口反查进程 ss -tlnp | grep 端口号 # 查看进程详细信息 ls -l /proc/PID/fd | head cat /proc/PID/status | grep -E State|PPid # 终止任务先TERM超时再KILL kill PID kill -9 PID这套命令覆盖了我90%以上的日常运维操作。剩下10%涉及更复杂的任务隔离、资源限制cgroup、进程追踪strace那是更深一层的话题但工具掌握到这里你已经能解决大部分“查看正在运行后台程序”和“终止任务”的实际问题了。尤其是“遇到异常进程”我个人的习惯是先用ps -eo pid,ppid,stat,cmd --sort-%cpu | head按CPU倒序看谁在作妖再判断是业务进程还是挖矿程序然后决定终止手段。这一步顺序颠倒容易误杀。