
干运维这些年最大的体会是百分之八十的系统故障排查到最后都绕不开两件事——进程管理和任务计划管理。前者让你知道系统此刻在“干什么”后者决定系统“自动干什么”。这篇就基于Linux实际运维场景把进程全生命周期和定时任务从原理到实战彻底聊透适合刚入门的运维新手、被进程问题折磨的开发以及想系统梳理知识的Linux使用者。1. 先搞懂进程模型看懂进程才能管好进程1.1 进程和程序不是一回事很多人一开始会把“进程”和“程序”混在一起这个认知偏差在排查问题时特别致命。程序是一堆静态的指令和数据躺在磁盘上你不运行它它什么也不做而进程是程序运行起来后的动态实体有自己的生命周期、内存空间、文件描述符和运行状态。打个比方程序是菜谱进程是锅里正在炒的那道菜。菜谱永远不变但菜的火候、状态、什么时候出锅都是动态的。你ps看到的每一个进程都是内核为它创建的一个“运行环境”这个环境包括独立的虚拟内存地址空间、程序计数器、寄存器值、打开的文件表、信号处理设置等。这里有一个关键点同一个程序可以被多次运行产生多个独立进程。比如你开了三个终端跑vim系统里就有三个vim进程它们的代码段是共享的节省内存但数据段、堆栈各自独立。理解这一点后面看ps输出、查端口占用、处理僵尸进程时思路会清晰很多。1.2 进程状态读懂STAT那一列的暗号ps aux输出里有个STAT列新手往往忽略它但这列其实是进程“健康度”的晴雨表。Linux进程主要状态如下状态字符含义常见场景运行R正在CPU上运行或等待调度计算密集任务睡眠S可中断睡眠等待某个事件sleep 100、大部分服务进程深度睡眠D不可中断睡眠通常等IO磁盘/网络IO阻塞停止T被CtrlZ暂停或调试中断调试器挂起僵尸Z进程已结束但未被父进程回收父进程没调用wait()判断依据在于进程当前是否处于“可被信号打断”的状态。S状态的进程你一个kill -9发过去它中断睡眠响应信号但D状态是内核态的IO等待信号根本递达不了所以D状态进程经常表现为“杀不死、杀不掉”。还有Z状态也就是僵尸进程。子进程正常退出后会向父进程发送SIGCHLD信号然后保留一个“残留记录”包括退出码等等父进程调用wait/waitpid来收尸。如果父进程既不调用wait也不处理SIGCHLD这个残留就一直在进程表里变成僵尸。僵尸进程不占CPU、不占内存但占着PID和进程表项如果大量积累可能导致系统无法创建新进程。后面第5章我会详细说怎么处理。1.3 进程家族的树形关系Linux进程不是孤立的整个系统从开机起就有一棵进程树。0号进程是内核自举时创建的调度进程swapper1号进程是/sbin/init现在大多由systemd接管所有其他进程都是它的后代。查看进程树的命令pstree -p你会在输出里看到systemd下面挂着一大串服务、SSH会话、shell再到你运行的各种命令。关于进程家族运维中高频出现两个概念父进程PPID和孤儿进程。如果父进程先挂了子进程会被内核“过继”给1号进程或最近的subreaper由它统一收养。这也是为什么你的命令放到后台跑父shell退出后进程却还活着——它成了孤儿但被systemd接管了。2. 进程查看与管理每天都在用的核心命令2.1 ps静态快照查状态ps是排查问题的第一把刀务必熟练。我常用的三种参数组合ps -ef # 标准格式看得见PPID ps aux # BSD格式看得见CPU/内存占用率 ps -eLf # 查看线程ps aux各列含义不必死记重点看几个PID进程号、CPUCPU占用百分比、MEM内存占用百分比、VSZ/RSS虚拟内存/物理内存、STAT状态、START启动时间、TIME累计占用CPU时间、COMMAND命令行。实际排查场景系统突然变慢先跑ps aux --sort-%cpu | head -5按CPU占用倒排看前三名是谁内存不足就ps aux --sort-%mem | head -5。注意这里%cpu是进程自启动以来平均CPU占用率不是瞬时值如果怀疑偶发高CPU要看实时动态这就轮到top出场了。还有一个容易被忽略的用法查“某个进程还在不在”。比如排查Java进程是否存活ps -ef | grep java | grep -v grep # 更推荐用pgrep避免grep匹配自身 pgrep -a java2.2 top/htop动态监控实时状态top是所有运维必会的交互式监控工具。进去以后按P按CPU排序按M按内存排序按1展开多核CPU使用率按c展开命令行。顶部五行尤其关键load average1/5/15分钟平均值这个值如果超过CPU核数意味着有进程在排队等CPU。四核机器load到4以上说明CPU已经饱和。%Cpu(s)里us用户态和sy内核态比例sy长期偏高说明系统调用太频繁。实战中我更推荐htop交互体验好很多支持鼠标、树形视图、按F5切父子关系、F6排序还能直观看每个CPU核的用量。不过生产环境默认不带用惯了top也不差。这里有个细节top展示的是瞬时动态采样如果怀疑“间歇性高CPU”可以按一次空格暂停刷新或者用top -b -n 2 -d 3批量取两帧快照对比差异。2.3 kill与信号让进程“听话”的正确姿势很多人以为kill就是“杀进程”其实它只是“给进程发信号”。Linux进程间通信IPC体系里信号是一种标准的异步通知机制。kill -l列出全部信号运维常用这几个信号编号行为使用场景HUP1挂起/重读配置让nginx、sshd平滑重新加载配置INT2中断等同CtrlCKILL9强制杀死进程无响应时兜底TERM15终止默认优雅停止进程CONT18继续运行恢复暂停的任务TSTP20暂停等同CtrlZ严格执行顺序是先kill PID发TERM给进程优雅收尾的机会保存数据、清理临时文件等几秒看进程是否退出不行再kill -9 PID。滥用-9是新手最常踩的坑直接发的后果是进程来不及清理现场数据库可能丢事务、配置文件可能写一半、socket文件可能残留在那下次启动报“Address already in use”。批量场景用pkill和killall前者支持按进程名匹配、正则和精确匹配如pkill -f python manage.py-f匹配完整命令行后者按名称精确匹配。用pkill时务必先pgrep -a确认你匹配到的确实是目标进程误杀生产进程的惨案可不少见。2.4 nice/renice管理CPU优先级优先级决定进程在CPU调度队列里的位置。Linux优先级分两层nice值-20到19默认0越小优先级越高和实时优先级。普通进程一般只接触nice值。启动时指定优先级nice -n -5 ./my_server # 提高优先级需要root nice -n 10 ./backup.sh # 后台备份任务降低优先级避免抢业务CPU运行中调整renice -n -5 -p 1234实际经验对CPU密集型的批处理任务日志压缩、数据同步建议把nice值调到5-10保证占据CPU不影响线上业务对交互式前端服务保持默认就行不必过度调优。3. 前台后台切换让任务不因会话退出而中断3.1 从CtrlZ到jobs/fg/bg在终端里跑长任务比如tar解压大包想腾出命令行干别的事操作方法按CtrlZ暂停任务然后用bg放到后台运行。这背后的原理是内核给进程发送了TSTP信号20进程被挂起bg再发CONT信号18恢复执行同时把它放到后台作业表里。前后台管理三兄弟jobs -l # 列出当前shell的后台作业 fg %1 # 把作业1调回前台 bg %1 # 把作业1放到后台继续跑注意一个细节jobs看到的作业属于“当前shell会话”。你关了终端窗口作业一般会收到HUP挂断信号进程就没了。想避免这种“一关窗口就死”的问题往下看。3.2 nohup与setsid脱离终端束缚追问频率最高的就是“怎么让进程在退出SSH后继续跑”。两个核心思路忽略挂断信号脱离会话/进程组。标准做法nohup ./my_task.sh my_task.log 21 分解一下nohup让进程忽略HUP信号 my_task.log 21把标准输出和标准错误都重定向进去最后的放进后台。这样即使终端断开只要系统不重启进程就能一直跑。另一个思路是setsid它会让进程完全脱离当前的会话和进程组成为新会话的领导进程连HUP信号都不会收到setsid ./my_task.sh /dev/null my_task.log 21 两者差异在于nohup只是屏蔽HUP信号进程仍属于原会话setsid则彻底“另立门户”更彻底。生产环境我还常用systemd-run创建临时服务来跑脚本这样能获得完整的systemd管理能力日志、依赖、自动重启。最后提一嘴tmux多路复用终端你的任务跑在tmux会话里随时重新附着SSH断了也不受影响这已经是我日常的标配了。注意无论用哪种方式都记得把标准输出重定向到文件。否则进程输出直接写到已断开的终端可能引发SIGTTIN或IO错误进程反而更容易挂掉。4. 任务计划管理cron与at的完整实战4.1 cron时间格式五个字段的排列组合cron是Linux下最经典的任务调度器它的时间表达式由五个字段组成字段范围说明分钟0-59每小时的第几分小时0-23每天的第几小时日期1-31每月的第几天月份1-12每年第几月星期0-70和7都代表周日最常用的写法*/5 * * * * # 每5分钟执行一次 0 3 * * * # 每天凌晨3点执行 30 22 * * 1-5 # 工作日晚上10点半执行 0 0 1 * * # 每月1号零点执行 0 0-5 * * * # 每天0点到5点每小时整点执行这里有几个坑必须说清楚五个字段缺一不可少写一个会被当成非法表达式cron直接拒绝加载。*和*/n的语义差别*每天个字段都匹配等价于每秒/每分*/n是步长值。分钟和小时字段里*/5不是“从0:00开始每5分钟”而是“每分钟值能被5整除就执行”也就是0、5、10...55。这在你需要“错峰执行”时能派上用场。cron的精度是分钟级的想跑“每30秒执行一次”怎么办标准cron做不到常见方案是写一个循环脚本配合sleep 30自己控制节奏让cron每分钟拉起来一次# 每30秒执行一次的小技巧 */1 * * * * /bin/bash /opt/scripts/half_minute.shhalf_minute.sh内写循环两次中间sleep 30。实测稳定可靠避免为这种小需求引入独立调度器。4.2 crontab -e与系统级定时任务普通用户配置定时任务crontab -e # 编辑当前用户任务 crontab -l # 查看当前用户任务 crontab -r # 清空当前用户任务 crontab -u other_user -e # root编辑其他用户任务所有用户的任务文件都集中在/var/spool/cron/crontabs/下以用户名命名。编辑后用systemctl status cron或service cron status检查cron服务是否正常运行——服务没启动任务写再多也不会执行。系统级定时任务看/etc/crontab和/etc/cron.d/格式和用户级几乎一样但多了个“用户”字段第6列表示以哪个身份执行# /etc/crontab 示例 0 4 * * * root /usr/local/scripts/backup.sh另外Linux发行版默认带了一组方便目录/etc/cron.hourly/、/etc/cron.daily/、/etc/cron.weekly/、/etc/cron.monthly/。把脚本往对应用录里一放系统会按周期自动跑无需写crontab表达式。讲一下anacron的适用场景如果服务器半夜关机标准的cron任务会错过执行时间。anacron记录上次执行时间开机后自动补跑错过的任务。桌面系统、笔记本电脑上用anacron比纯cron靠谱得多。4.3 at一次性定时任务别用cron硬撑有些任务只需要执行一次——比如“3小时后重启服务”“下午5点发个警告邮件”用cron就得手动写删除逻辑非常麻烦。这种场景用atat now 2 hours # 进入交互式输入模式 systemctl restart nginx CtrlD 结束 # 非交互式写法 echo systemctl restart nginx | at 17:00 echo backup.sh | at 3:30 tomorrowatq查看待执行的任务队列atrm 5删除任务编号5。底层实现用的是atd守护进程记得确认服务在跑。4.4 实战写一个日志清理与进程守护的完整方案纸上谈兵到此为止给一个能直接上生产的方案一台业务服务器每天凌晨3点清理7天前的日志、每周日凌晨4点做数据库备份同时监控关键进程nginx、mysql宕了自动拉起并告警。第一步写清理脚本/usr/local/scripts/clean_logs.sh#!/bin/bash # 清理7天前日志 LOG_DIR/var/log/myapp find $LOG_DIR -name *.log -mtime 7 -delete echo $(date %Y-%m-%d %H:%M:%S) cleanup done /var/log/clean_logs.logfind -delete比-exec rm {} \;高效且安全但务必先本地测试。第二步写备份脚本/usr/local/scripts/backup_db.sh#!/bin/bash # 保留最近4周备份 BACKUP_DIR/data/backup/mysql mkdir -p $BACKUP_DIR mysqldump --single-transaction --quick -uxxxx -pxxxx mydb | gzip $BACKUP_DIR/mydb_$(date %Y%m%d).sql.gz find $BACKUP_DIR -name *.sql.gz -mtime 28 -delete第三步写进程守护脚本/usr/local/scripts/process_guard.sh用cron每分钟拉起来检查#!/bin/bash PROCESS_NAMEnginx URLhttp://127.0.0.1/healthz # 1. 进程是否存在 if ! pgrep -x $PROCESS_NAME /dev/null; then systemctl start nginx echo $(date %F %T) $PROCESS_NAME was down, started /var/log/guard.log fi # 2. 健康检查进程在但服务无响应 if ! curl -sf $URL /dev/null; then systemctl restart nginx echo $(date %F %T) $PROCESS_NAME unhealthy, restarted /var/log/guard.log fi第四步写crontabcrontab -e # 分钟小时日期月份星期 0 3 * * * /usr/local/scripts/clean_logs.sh 0 4 * * 0 /usr/local/scripts/backup_db.sh * * * * * /usr/local/scripts/process_guard.sh为什么选择cron而不是systemd timer实现自动备份systemd timer确实更现代、支持精确到秒、日志管理更完善但cron的优点在于轻量、普适、每个Linux发行版都有脚本跑在哪里都一样。生产环境我习惯先用cron搭建基础调度等任务复杂度高了再考虑迁移timer。关于日志脚本里务必输出执行结果和时间戳重定向到日志文件。这样排查“到底跑没跑”就有据可查。5. 常见问题与排查技巧实录5.1 端口被占用定位占用进程场景启动服务报错Address already in use或者Cant bind to port 8080。排查流程ss -ltnp | grep 8080 # 现代推荐ss # 或 lsof -i :8080 # 进程级的端口占用视图ss -tlnp里Recv-Q/Send-Q若看到LISTEN状态但进程显示不出来的情况多半是权限不够加sudo再看。拿到PID后用ps -fp 12345看具体是什么进程判断是重启、换端口还是清残留。如果端口被僵死的进程占用kill -9后还是提示占用注意是否存在TIME_WAIT状态连接。TIME_WAIT是TCP四次挥手的正常残留几秒后自动消失别误杀别的进程。5.2 进程杀不掉D状态与内核IO等待kill -9下去进程还在STAT状态是D这通常是进程阻塞在不可中断的内核IO上NFS故障、磁盘坏道、网卡异常。这种状态下信号无法递达唯一路径是等待IO超时恢复。处理建议优先级先等一会看IO是否恢复检查NFS挂载、磁盘SMART状态如果磁盘故障一时无法恢复考虑重启系统。生产环境里D状态持续超过几分钟先排查存储层比反复kill有意义得多。5.3 僵尸进程处理别慌多数无害僵尸进程是父进程没调用wait的结果严格说它已经不“活”了。处理思路先看数量少量僵尸一般不影响系统不用过分紧张。如果太多比如上百个找到父进程PIDps -o ppid -p 僵尸PID。对父进程操作如果是bashexit退出这个shell父进程结束僵尸会被systemd收留并清理如果父进程是常驻服务重载或重启它。最坏情况父进程是个长期不更新的老服务那就看看它上级进程是谁沿进程树往上找总有一个能重启。关键理解僵尸体质上不是“病”是“没有及时处理的尸体”。系统存在的意义是让父进程通过wait获取子进程退出状态你没有及时wait它就一直在。只要源头父进程重来一次僵尸自然清除。5.4 cron任务不执行按这五步排查我遇到的cron问题九成出在这五个环节服务没起systemctl status cron没起就start并enable。时间写错/时区不对分时日月周五个字段和系统时区必须匹配。服务器时区用date确认cron匹配的是系统时区不是UTC。曾碰到一台服务器写0 2 * * *但系统时区是UTC结果每天比预期晚8小时执行折腾半天才发现是配置时区时漏改了。脚本权限/路径问题cron执行环境不加载.bashrcPATH很干净脚本里要用绝对路径写命令。另外脚本必须是可执行的chmod x。脚本里环境变量没带计划任务里的PATH、JAVA_HOME等环境变量都得显式设置很多脚本在终端能跑、cron不跑就是环境变量差异导致。脚本开头export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin是个好习惯。输出日志看不到cron任务的标准输出随机丢弃半个字不看排查非常被动。给crontab每行加 /var/log/my_task.log 21至少能确认“任务被执行了但脚本报错”。5.5 CPU高但找不到进程线程与瞬时任务场景top看到CPU被占满但翻遍进程列表找不到耗CPU的进程。可能原因线程占用高但进程显示不明显top -H查看线程模式或用ps -eLf看哪条线程占资源。短周期重复任务高CPU由很短的批量任务引起。用top -b -n 30 -d 1连续采样30次再把输出文件做排序统计揪出高频出现的短命进程。内核线程/软中断top里看si软中断和hi硬中断占比如果持续偏高往往是网卡中断、磁盘IO密集引发的这时要查的不是某个进程而是整个IO路径iostat、sar。这里分享一个有参考价值的实际排查某次线上CPU一直100%top里看到一个Java进程占97%但GC日志很正常。用top -H -p 7810看到是某个线程持续满跑然后jstack 7810 thread_dump.txt去查堆栈定位到是一个死循环的定时器线程。整个排查链路就是进程→线程→代码栈逐层下钻。5.6 后台任务突然消失明明用nohup跑的进程第二天就没了。排除系统重启后最常见的坑是终端进程组挂断。nohup解决了HUP信号但如果你的命令是通过某个shell启动的shell退出时可能连带清理整个进程组。解决方法是配合setsid或者干脆放进systemd管理。另外注意磁盘空间日志输出写满磁盘进程可能在写入失败后自动退出。查一下df -h和对应日志文件大小。写在最后的一点经验进程管理和任务计划看起来是两套内容实际在运维工作中是密不可分的组合拳cron负责“定时拉起”进程状态负责“确认真的拉起来了”而进程的异常状态又反过来驱动你调整任务计划的执行策略。我自己常用的组合是cron每分钟进程巡检 nohup跑后台长任务 top/ps实时定位三条线互相兜底基本覆盖了日常运维80%的“进程类”问题。最后送大家几个习惯杀进程前先ps -ef | grep确认PID写cron任务务必带上日志重定向改了crontab之后用crontab -l复核遇到D状态和僵尸进程先查存储和父进程别急着kill。这些细节平时看着不起眼关键时刻真的能少熬好几个夜。