进程和计划任务这两件事几乎是每个服务器管理员躲不开的日常。排查线上问题时第一步要搞清楚的就是某个进程到底在干什么、吃了多少资源、谁把它拉起来的而计划任务则是让机器自动干活的关键日志切割、数据备份、监控脚本上报全都靠它按点触发。我接触过的不少同学平时ps和top都会敲但真到进程状态异常、任务到点没执行的时候还是会卡半天原因就在于对底层机制的理解只停在会用命令的层面。这篇文章打算围绕进程管理和计划任务管理两条线展开把一个合格的系统管理员需要掌握的查看、控制、调度和排障手段都过一遍。无论你是刚接触 Linux 的运维新手还是已经被线上事故折磨过几轮的工程师这里面的操作思路都可以直接拿过去用。1. 先把进程长什么样搞清楚1.1 进程不是程序一张图看透动态执行很多人概念上容易混淆觉得程序就是进程。其实程序只是一个躺在磁盘上的静态文件比如/usr/bin/python3它不占什么运行资源。真正跑起来之后内核会把这个程序的可执行代码、依赖的库、数据段和堆栈都装载到内存分配一个进程描述符这才叫进程。进程是一个动态的概念它有自己的生命周期创建、运行、等待、终止。你用python script.py启动脚本Shell 会先fork()一个子进程再通过exec族函数把python3的代码替换进去。这里有个特别容易忽略的点fork()出来的子进程会继承父进程的环境变量、文件描述符和很多属性但它们是两个独立的调度单位。某个进程挂了不影响父进程反过来父进程先退出了子进程就会被托孤给 PID 为 1 的进程通常是 systemd这也是孤儿进程的由来。理解这条线对后面排查进程为什么没人管很有帮助。1.2 进程状态、PID 与父子关系排查从哪入手Linux 的进程状态看着眼花缭乱其实核心就几种。R是运行态说明进程要么在 CPU 上跑要么排在就绪队列里S是可中断睡眠进程在等某个事件最常见的是等 I/OD是不可中断睡眠通常是在等磁盘 I/O 完成这种状态很难用常规手段干掉Z是僵尸态进程已经结束但父进程还没调用wait()回收它的退出码T是被暂停I是空闲的内核线程。排查的时候我习惯先看 PID 和 PPID。echo $$可以看当前 Shell 的 PIDps -eo pid,ppid,stat,cmd能看到每个进程的父进程是谁。举个例子你发现有个nginx进程异常通过 PPID 追上去可能发现它根本不是被主进程拉起的而是某个脚本 fork 出来的野进程那问题基本就定位到了。提示僵尸进程用kill -9是杀不掉的因为它已经死了只是没被回收。真正要处理的是它的父进程——要么等父进程自己清理要么把父进程也停掉让 systemd 接管回收。2. 进程查看与实时监控2.1 不要只知道 ps aux试着组合输出字段ps aux确实是查看进程的第一选择但它有个问题——输出格式是 BSD 风格有些字段不够直观。我常用的命令是ps -eo pid,ppid,user,%cpu,%mem,stat,etime,cmd这样可以把执行时间、状态、CPU 和内存占用一次性拉出来。如果想看某个进程的线程数可以加-L参数想按内存占用排序用ps -eo pid,comm,%mem --sort-%mem加上head取前几条。处理线上问题时我一般先ps -eo pid,ppid,stat,%cpu,%mem,cmd --sort-%cpu | head -20看看是谁在吃 CPU再顺着 PID 查它的完整路径和启动参数。还有个容易被忽略的用法查某个进程的工作目录。进程可能因为当前目录被删或者文件被占用表现得很诡异这时候用ls -l /proc/PID/cwd就能看到它的实际工作目录。/proc/PID/底下是个宝藏/proc/PID/environ能看到启动环境变量/proc/PID/fd/能看到它打开的所有文件描述符。遇到文件被占用删不掉的情况靠的就是这一招。2.2 top 里那些数字到底怎么读top命令是实时监控的标配但很多人只看一眼 CPU 数字和进程列表不理解下面这些指标的含义。%CPU代表进程在一个采样周期内的 CPU 时间占用率。top 默认 3 秒刷一次如果进程是多线程的这个数值可能超过 100%比如 8 线程每个跑满一个核显示的就是 800%。%MEM是物理内存占比但它是按 RES常驻内存算的不要和 VSZ虚拟内存混淆。常有人看到 VSZ 几个 GB 吓得不行其实大部分是共享库和映射并没有真正占物理内存。交互模式下有几个键很实用P按 CPU 排序、M按内存排序、1展开每个 CPU 核心的使用率、H切换线程模式。想看更友好的界面可以直接用htop它带色彩和树形视图还能直接用 F5 看进程树F9发信号。不过生产环境不一定装了htop所以top的基本操作还是要熟练。2.3 用 pgrep、pstree 快速定位目标进程ps aux | grep xxx是个很粗暴的办法而且 grep 自己可能会被匹配上还要加grep -v grep或者用[x]xx技巧去排除。更干净的方式是直接用pgrep。pgrep -u www php-fpm能找出以www用户运行的 PHP-FPM 进程pgrep -af nginx可以匹配完整命令行。pstree则以树形展示进程关系我排查服务启动链时特别爱用直接pstree -ap PID能看到指定进程的所有祖先和后代。3. 进程调度、信号与终止实操3.1 前台、后台、挂起作业控制的核心玩法进程管理和 Shell 的作业控制息息相关。一个命令在终端里直接跑就占住了前台按Ctrl Z可以把它挂起进程状态变成 T用bg让挂起的任务到后台继续跑用jobs查看当前 Shell 管理的任务列表fg 1把第一个后台任务调回前台。这里要注意bg只是把任务放到后台Shell 一退出后台任务还是会收到 SIGHUP 信号而终止。想要任务脱离当前终端得用disown或者nohup。我见过不少新手用Ctrl C杀前台任务后发现重启服务时提示端口被占用其实那个进程是被放到后台的另一个实例或者是个 fork 出去的子进程。收尾工作一定要做干净。3.2 信号才是真正的杀进程神器kill命令不叫杀它准确的职责是向进程发送信号。kill -l能列出所有信号日常用最多的几个SIGTERM15是默认信号请求进程优雅退出给它机会清理临时文件和释放资源。SIGKILL9是强制终止内核直接回收进程来不及做任何清理。SIGHUP1是挂断信号很多守护进程收到它后会重读配置文件而不是退出比如nginx的reload本质就是发 HUP 信号。SIGINT2是键盘中断相当于Ctrl C。正确做法是先用SIGTERM等几秒看进程是否结束不行再用SIGKILL。一上来就kill -9容易留下后遗症比如数据库没落盘、临时文件没清理、子进程变成孤儿。查端口被哪个进程占用时lsof -i :8080能找到 PID再决定发什么信号fuser -k 8080/tcp可以直接把占用端口的进程杀掉但用之前先确认下这命令比较暴力。3.3 后台进程的守护与持久化nohup 和 setsid想让一个进程在终端关闭后继续运行常见方案是nohup command 它会忽略 SIGHUP 信号。但只是在当前 Shell 里后台运行Shell 退出后它依然可能被 SIGHUP 波及所以需要组合使用。更彻底的做法是setsid command它让进程完全脱离当前会话成为新会话的领导者相当于一种简单的守护化。生产环境里我更推荐用 systemd 来托管需要长期运行的业务进程或定时任务由 systemd 统一管理重启策略、日志输出和环境变量比自己写nohup 可靠得多。后面计划任务部分会专门展开讲 systemd timer 的用法。4. 计划任务从一次性任务到周期任务4.1 at一个被低估的一次性任务工具cron解决的是周期性问题但偶尔我们需要的是五分钟后执行一次这种临时任务。这种场景用at最合适。at now 5 minutes进入交互模式输入命令也可以用命令行直接echo /opt/scripts/report.sh | at 22:00。atq查看任务队列atrm 3删除任务编号 3。at任务执行时也会产生邮件通知一般我们在at或cron环境中设置MAILTO来屏蔽。很多人习惯了 cron 就忘了at但实际上临时切换流量、发工单、重启前通知这类一次性倒计时操作用at反而更清晰不会污染 crontab。4.2 crontab 的格式和运行环境crontab 格式是五个时间字段加命令分、时、日、月、周。0 2 * * * /opt/scripts/backup.sh代表每天凌晨 2 点执行。踩坑最多的是周和日同时设置时的 OR 关系如果某天既是每个月的 1 号又是周一两个条件满足一个就会触发不是 AND。我看到不少脚本因为这个逻辑误解在意外时间跑了两次。crontab 的另一个经典坑是环境变量。cron 执行时不是一个登录 Shell它默认的 PATH 非常精简通常是/usr/bin:/bin而/usr/local/bin这些路径往往不在里面。所以脚本里直接用python3可能找不到命令必须写成绝对路径或者在 crontab 文件顶部设置PATH/usr/local/bin:/usr/bin:/bin并在脚本开头source /etc/profile来加载完整环境。crontab 权限管理很重要/etc/cron.allow和/etc/cron.deny控制哪些用户能创建计划任务。生产环境建议只配置cron.allow把不需要用到 crontab 的用户一律挡在外面。不要忽略用户之间的任务隔离root的 crontab 和普通用户的 crontab 是完全独立的查看时要区分开。4.3 systemd timer计划任务的现代替代方案我这两年越来越倾向把新写的周期任务从 crontab 迁移到 systemd timer。它的优势很明显可以设置精准的启动时间、随机延迟、赶不上上次执行时的补跑策略、资源限制和依赖关系而且日志统一由 journald 管理排查问题不用去翻邮件。基本结构是两个文件。.service文件里写要执行的命令.timer文件里写触发规则# /etc/systemd/system/report.service [Unit] DescriptionGenerate daily report [Service] Typeoneshot ExecStart/opt/scripts/report.sh Userroot# /etc/systemd/system/report.timer [Unit] DescriptionRun report daily at 02:30 [Timer] OnCalendar*-*-* 02:30:00 Persistenttrue RandomizedDelaySec120 [Install] WantedBytimers.target启用后执行systemctl daemon-reloadsystemctl enable --now report.timer。用systemctl list-timers查看所有 timer 的下一次触发时间。Persistenttrue很关键如果系统在 02:30 关机了下次开机后 timer 会立即补执行对备份类任务特别有用。RandomizedDelaySec可以避免多个服务在同一时刻挤占资源适合批量执行监控上报的场景。5. 计划任务实战与日志排查5.1 定时脚本怎么设计才不容易翻车计划任务的本质是无人值守所以脚本里一定要考虑异常场景。我写过很多定时备份和清理类脚本总结下来有几个共性要求。第一是绝对路径。脚本内的命令全部用绝对路径避免依赖 PATH。第二是输出重定向。cron 执行时如果有输出到 stdout它会尝试通过 sendmail 发邮件大多数系统没装邮件服务邮件生成失败还可能引起其他问题。所以我统一在脚本里把日志追加到文件log_dir/data/logs/backup_$(date \%F).log所有关键步骤都用 $log_dir 21。第三是要有锁机制。定时任务如果执行时间超出了周期两个实例同时跑会造成资源竞争。最简单的做法是用flockexec 9/var/lock/backup.lock flock -n 9 || exit 0-n参数表示拿不到锁就退出确保同一时刻只有一份任务在跑。第四是宁可失败留痕不可无声消失。脚本最后要输出结束标记和耗时方便事后核对。5.2 任务没跑、跑了没结果常见问题定位计划任务最常见的问题是我明明写了 crontab时间到了它没执行。排查顺序我总结成一个固定套路。先确认服务在不在systemctl status crond或者cron如果服务没启动任务自然不触发。然后确认自己的 crontab 是否加载成功crontab -l看配置注意当前用户是不是操作错了。再检查时间字段和脚本权限ls -l确认脚本有执行权限/etc/crontab或者/etc/cron.d/里的任务还要指定用户千万别漏掉用户名。输出了重定向的直接看日志文件。没重定向的可能被邮件系统吞掉了去/var/spool/mail/root里看看有没有报错内容。systemd timer 的话用journalctl -u report.service -e直接看完整输出。还有一个隐蔽问题cron 里的环境变量只是默认值如果你的脚本依赖用户自定义的环境变量比如JAVA_HOME一定在脚本开头显式声明。我遇到过一次脚本在手动执行时一切正常、cron 执行时装不了 Java 环境的案例追到最后就是缺少JAVA_HOME。5.3 资源限制和安全基线给计划任务加上资源限制是我接手过的项目里做得比较成熟的一项规范。主要是通过 systemd service 里的CPUQuota、MemoryMax、TasksMax来限制防止某个任务失控把整台机器拖垮。[Service] CPUQuota50% MemoryMax512M TasksMax100crontab 的场景下限制方式略少一般借助nice调整优先级或者外部用timeout包一层控制最长执行时间。对于备份、清理这类任务我通常还会给日志目录设置磁盘告警df检查剩余空间不足就提前发通知避免任务跑到一半磁盘满了。安全层面计划任务脚本里如果用到密码类信息不要明文写在脚本里建议放到单独的环境变量文件并限制权限chmod 600 /etc/backup.env然后脚本里source /etc/backup.env。涉及外部传输的尽量用密钥认证而不是密码最大程度降低泄露风险。6. 我建议新手从这几步开始练进程管理和计划任务最怕光看不练。想快速建立手感有几个方向可以上手操作。第一步开一个测试机用ps、top、pgrep观察系统里各进程的状态挑个服务试着用kill -TERM正常停止再对比用kill -KILL强杀看看进程的树形结构有没有变化。第二步给自己写一个 crontab每隔 5 分钟执行一条命令把结果写到文件里观察日志是否正常、时间点是否准确。第三步往 systemd timer 迁移一个真实的脚本任务配置好Persistent和日志输出systemctl list-timers确认激活状态。实操过程中遇到任务没跑、进程杀不掉的时候先别急着搜命令打开/var/log和 journal 日志按照服务状态 → 配置内容 → 脚本日志的顺序查下来。多练几轮对进程的整个生命周期和计划任务的触发链条会建立很扎实的理解。我自己带人的时候常说系统管理的核心不是记住命令而是建立一套稳定的排查顺序先看状态再看日志最后定位根因。只要把这套顺序练成下意识反应再复杂的问题也能被拆成一个个可以验证的小环节。