15.1 什么是例行性工作调度
Linux 的例行性工作调度通过 at 和 crontab 两个工具实现,让我们可以把备份、日志分析、提醒等任务交给系统自动执行,无需手动操作。
15.1.1 Linux 工作调度的种类:at, cron
| 类型 | 工具 | 服务 | 用途 |
|---|---|---|---|
| 一次性(突发性) | at | atd | 执行一次就结束 |
| 循环性(例行性) | crontab | crond | 按分钟/小时/日/月/周循环执行 |
- at:处理仅执行一次的任务。需要 atd 服务支持,CentOS 默认启动。
- crontab:循环执行的任务,可设置分钟、小时、每周、每月或每年。除了
crontab指令外,也可编辑/etc/crontab。
15.1.2 CentOS 上常见的例行性工作
Linux 系统会自动进行很多例行性任务,这些都由 crond 调度:
| 例行工作 | 说明 | 相关指令/文件 |
|---|---|---|
| 登录文件轮替(log rotate) | 防止登录文件无限增大,定期分割归档 | logrotate |
| 登录文件分析 | 主动分析登录信息,root 会收到 logwatch 邮件 | logwatch |
| locate 数据库更新 | 更新 /var/lib/mlocate/ 文件名数据库 | updatedb |
| man page 数据库创建 | 创建快速查询的 man page 数据库 | mandb |
| RPM 软件登录文件创建 | 追踪软件安装/变更产生的文件名差异 | — |
| 移除暂存盘 | 删除超时的暂存盘,释放磁盘空间 | tmpwatch |
| 网络服务分析 | WWW 服务器(apache)日志分析、凭证过期检查等 | 取决于安装的软件 |
系统例行性工作的多少,跟安装了哪些软件有关——很多服务会附带自己的分析工具和 cron 配置。
15.2 仅执行一次的工作调度
15.2.1 atd 的启动与 at 运行的方式
启动 atd 服务:
systemctl restart atd # 重启 atd
systemctl enable atd # 开机自动启动
systemctl status atd # 查看状态(需要看到 enabled + running)at 的运行机制:
- 使用
at指令产生工作,以文本文件写入/var/spool/at/目录 - atd 服务从该目录读取并执行工作
- at 工作独立于当前 bash 环境,由 atd 接管,下达后即可离线——适合长时间任务,不怕断网
安全控制(at.allow / at.deny):
先找 /etc/at.allow → 存在:只有列出的用户能用
→ 不存在:找 /etc/at.deny → 在 deny 中的不能用,不在的都能用
两个文件都不存在 → 只有 root 能用 at
at.allow是严格白名单管理at.deny是宽松黑名单管理(CentOS 默认为空文件 = 所有人可用)- 一个帐号一行
15.2.2 at 命令的语法与操作实务
基本语法:
at [-mldv] TIME
at -c 工作号码 # 查看工作的实际指令内容| 选项 | 说明 |
|---|---|
-m | 工作完成后即使无输出也发 email 通知 |
-l | 相当于 atq,列出当前用户的 at 调度 |
-d | 相当于 atrm,删除调度中的一个工作 |
-v | 用更详细的时间格式列出 at 调度 |
-c | 列出工作号码对应的实际指令内容 |
TIME 时间格式:
| 格式 | 示例 | 说明 |
|---|---|---|
HH:MM | 04:00 | 今日 HH:MM 执行;若已过则明天 |
HH:MM YYYY-MM-DD | 04:00 2015-07-30 | 指定具体日期时刻 |
HH:MM[am|pm] [Month] [Date] | 04pm July 30 | 同指定具体日期 |
now + N [minutes|hours|days|weeks] | now + 5 minutes, 04pm + 3 days | 相对时间(推荐) |
操作实务:
# 5 分钟后把 .bashrc 寄给 root
at now + 5 minutes
at> /bin/mail -s "testing at job" root < /root/.bashrc
at> <EOT> # Ctrl+d 结束输入
# 指定日期关机
at 23:00 2015-08-04
at> /bin/sync
at> /bin/sync
at> /sbin/shutdown -h now
at> <EOT>⚠️ at 中的指令应使用绝对路径,因为 at 执行时的环境变量(PATH)和登录 shell 不同。
💡 at 执行时的工作目录是下达
at指令时所在的目录。例如在/tmp下达at,里面引用./file是/tmp/file。
at 的输出去哪了?
- at 的执行环境与终端机无关——所有 stdout/stderr 都会寄到执行者的 mailbox
- 想在终端机看到输出 → 重导向到终端设备:
echo "Hello" > /dev/tty1 - 指令无输出则默认不发 mail;用
at -m强制发通知
管理 at 工作:
atq # 列出待执行的工作(等同于 at -l)
atrm [jobnumber] # 删除指定工作(等同于 at -d)batch:系统空闲时执行
batch 利用 at 机制,但会在 CPU 工作负载 < 0.8 时才执行任务。CentOS 7 中 batch 不再支持时间参数。
💡 工作负载(load)≠ CPU 使用率。load 是 CPU 在单一时间点负责的工作数量。一只持续运算的程序占满 CPU(使用率 100%),load 约等于 1;同时跑 4 只,load ≈ 4,CPU 在频繁切换工作导致系统忙碌。
# 高负载时下达 batch,任务会等待负载降低后才执行
batch
at> /usr/bin/updatedb
at> <EOT>
# 用 uptime 查看 load,atq 确认工作是否还在等待at 和 crontab 最小时间单位都是分钟——每分钟检查一次,整分(秒为 0)时处理。
15.3 循环执行的例行性工作调度
15.3.1 使用者的设置
安全控制(与 at 类似):
/etc/cron.allow 存在 → 只有列出的用户能用 crontab
不存在 → 看 /etc/cron.deny → 不在 deny 中的都能用
CentOS 默认保留空的 /etc/cron.deny,即所有用户都可用 crontab。
crontab 指令:
crontab [-u username] [-l | -e | -r]| 选项 | 说明 |
|---|---|
-u | 只有 root 可用,帮其他用户创建/移除 crontab |
-e | 编辑 crontab 工作内容(会进入 vi 编辑器) |
-l | 查看 crontab 工作内容 |
-r | 删除全部 crontab 工作(⚠️ 只删一项也用 -e!) |
⚠️
crontab -r是删除整个 crontab!只删某一项请用crontab -e编辑删除。
用户 crontab 存放位置:/var/spool/cron/<帐号名>(不要直接用 vi 编辑该文件,用 crontab -e 才安全)。cron 的执行日志记录在 /var/log/cron。
crontab 格式(6 个字段):
分 时 日 月 周 指令
| 字段 | 范围 | 说明 |
|---|---|---|
| 分 | 0-59 | |
| 时 | 0-23 | |
| 日 | 1-31 | |
| 月 | 1-12 | |
| 周 | 0-7 | 0 和 7 都代表星期天 |
| 指令 | — | 要执行的命令 |
特殊字符:
| 字符 | 含义 | 示例 |
|---|---|---|
* | 任意时刻 | * * * * * = 每分钟 |
, | 分隔多个时段 | 0 3,6 * * * = 3:00 和 6:00 |
- | 时间范围 | 20 8-12 * * * = 8,9,10,11,12 点的 20 分 |
/n | 每隔 n 单位 | */5 * * * * = 每 5 分钟(等同于 0-59/5) |
常用示例:
# 每天 12:00 发信给自己
0 12 * * * mail -s "at 12:00" dmtsai < /home/dmtsai/.bashrc
# 每年 5 月 1 日 23:59 发信(5 月 2 日生日的提醒)
59 23 1 5 * mail kiki < /home/dmtsai/lover.txt
# 每 5 分钟执行脚本
*/5 * * * * /home/dmtsai/test.sh
# 每星期五 16:30 发信提醒
30 16 * * 5 mail friend@his.server.name < /home/dmtsai/friend.txt15.3.2 系统的配置文件:/etc/crontab, /etc/cron.d/*
crontab -e 是个人化的,系统的例行性任务通过编辑 /etc/crontab 来管理。注意 /usr/bin/crontab 是可执行文件,/etc/crontab 是纯文本文件——cron 每分钟读取一次这些配置文件,编辑后不用重启任何服务(但某些 Unix 系统需 systemctl restart crond)。
/etc/crontab 格式(7 个字段):
分 时 日 月 周 使用者 指令
与用户 crontab 的区别:
- 多了一个”使用者”字段——指定以什么身份执行命令
- 可以设置
MAILTO、PATH、SHELL等环境变量
cat /etc/crontab
# SHELL=/bin/bash
# PATH=/sbin:/bin:/usr/sbin:/usr/bin
# MAILTO=root # 错误/输出寄给谁(建议改为自己的邮箱)
# 分 时 日 月 周 用户 指令💡
MAILTO建议改为自己能收到的邮箱(root 无法用 POP3 收信)。
crond 读取配置的三个位置:
| 位置 | 用途 |
|---|---|
/etc/crontab | 系统级例行任务的主配置文件 |
/etc/cron.d/* | 第三方软件的 cron 配置(每个软件一个独立文件) |
/var/spool/cron/* | 每个用户的个人 crontab |
/etc/cron.d/*:如果你开发软件需要定时任务,直接在 /etc/cron.d/ 下放一个带”分时日月周 用户 指令”的配置文件即可,升级时覆盖文件就行,比手动编辑 /etc/crontab 干净得多。
例如 /etc/cron.d/0hourly:
01 * * * * root run-parts /etc/cron.hourly
这行让系统在每整点的第 1 分钟执行 run-parts /etc/cron.hourly——也就是执行 /etc/cron.hourly/ 目录下所有可执行文件。
/etc/cron.*/ 目录:
| 目录 | 触发方式 | 执行时机 |
|---|---|---|
/etc/cron.hourly/ | 由 crond 直接调用 run-parts | 每小时第 1 分起的 5 分钟内随机 |
/etc/cron.daily/ | 由 anacron 调用 run-parts | 每天 |
/etc/cron.weekly/ | 由 anacron 调用 run-parts | 每周 |
/etc/cron.monthly/ | 由 anacron 调用 run-parts | 每月 |
注意:
/etc/cron.hourly/是 crond 直接驱动,而 daily/weekly/monthly 是 anacron 驱动的(见 15.4 节)。
使用场景总结:
| 场景 | 方法 |
|---|---|
| 个人化需求 | crontab -e(安全,不被其他用户看到) |
| 系统维护管理 | vim /etc/crontab(集中管理,易追踪) |
| 第三方软件 | vim /etc/cron.d/newfile(独立配置,方便升级) |
| 固定周期的简单任务 | 直接把可执行脚本丢进 /etc/cron.{hourly,daily,weekly,monthly}/ |
15.3.3 一些注意事项
① 资源分配不均
当多个工作都设为 */5 * * * *,它们会在同一时刻全部启动。解决方案:用逗号把分钟错开。
1,6,11,16,21,26,31,36,41,46,51,56 * * * * root CMD1
2,7,12,17,22,27,32,37,42,47,52,57 * * * * root CMD2
3,8,13,18,23,28,33,38,43,48,53,58 * * * * root CMD3
4,9,14,19,24,29,34,39,44,49,54,59 * * * * root CMD4逗号分隔的时段之间不要加空白字符。
② 取消不要的输出
当 cron 任务有输出或出错时,会将内容发给 MAILTO 指定的帐号。如果某个任务一直出错,你会被邮件轰炸。
# 丢弃所有输出
*/5 * * * * /path/to/script > /dev/null 2>&1③ 安全检查
检查 /var/log/cron 日志,确认是否有非你设置的 cron 在运行——木马常用例行任务方式植入。
④ 周与日月不可同时并存
# ❌ 错误——想设"9月11号且星期五",实际上可能变成:每个周五都执行,或每年9月11日执行
30 12 11 9 5 root echo "just test"⚠️ crontab 中日和周不能同时指定。要么用日期,要么用星期,不要两个都写。
15.4 可唤醒停机期间的工作任务
假设你有一台周末会关机的 Linux 主机,crontab 里的周日凌晨备份任务就因为关机错过了。anacron 就是用来解决这个问题的——它会在开机后补执行错过的任务。
15.4.1 anacron
- anacron 不是替代 crontab,而是弥补 cron 不支持”补执行”的缺陷
- anacron 每小时被 crond 调用一次,然后检测相关任务有没有执行过
- 超出期限的 → 执行;没超的 → 跳过
- 默认以1 天、7 天、一个月为期检测
检测原理:anacron 对比当前时间和时间戳记文件(/var/spool/anacron/*)中记录的上次执行时间,有差异说明关机期间错过了任务。
15.4.2 crond 与 anacron 的关系
anacron 的调用链:
crond 每分钟读取 /etc/cron.d/0hourly
→ 每小时执行 /etc/cron.hourly/0anacron
→ 0anacron 调用 /usr/sbin/anacron -s
→ anacron 读取 /etc/anacrontab
→ 检查并补执行 /etc/cron.{daily,weekly,monthly}/ 中的任务
/etc/anacrontab 格式:
SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
RANDOM_DELAY=45 # 随机最大延迟(分钟),避免多机同时涌入
START_HOURS_RANGE=3-22 # 允许执行的时间范围(3 点 ~ 22 点)
# 天数 延迟(分) 工作名称 实际指令
1 5 cron.daily nice run-parts /etc/cron.daily
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly
anacron 执行流程(以 cron.daily 为例):
| 步骤 | 说明 |
|---|---|
| 1 | 从 /etc/anacrontab 获知 cron.daily 的天数为 1 天 |
| 2 | 从 /var/spool/anacron/cron.daily 读取上次执行的时间戳记 |
| 3 | 比较时间戳记与当前时间,相差 ≥1 天就准备执行 |
| 4 | 根据配置延迟 5 分钟 + 随机最大 45 分钟(RANDOM_DELAY) |
| 5 | 延迟结束后执行 run-parts /etc/cron.daily |
| 6 | 执行完毕,anacron 结束 |
时间戳记文件(/var/spool/anacron/):
$ cat /var/spool/anacron/cron.daily
20150731anacron 命令:
anacron [-sfn] [job] # 执行调度
anacron -u [job] # 仅更新时间戳记,不执行| 选项 | 说明 |
|---|---|
-s | 根据时间戳记依次执行各项 job |
-f | 强制执行,不看时间戳记 |
-n | 立即执行,不等延迟 |
-u | 只更新戳记不执行 |
/etc/cron.hourly/0anacron 文件名前加 0 是为了在 hourly 目录中第一个被执行,从而确保时间戳记最先更新,避免 anacron 误判。
crond 与 anacron 的关系总结:
| 调度方式 | 触发链 | 特点 |
|---|---|---|
| crond 直接调度 | /etc/crontab、/etc/cron.d/*、/var/spool/cron/* | 定时执行,过了就过了 |
| crond → 每小时目录 | /etc/cron.d/0hourly → run-parts /etc/cron.hourly/ | 定时执行 |
| crond → anacron | /etc/cron.hourly/0anacron → /etc/anacrontab → run-parts /etc/cron.{daily,weekly,monthly}/ | 定期执行,错过会补 |
💡 关键区别:放在
/etc/crontab里的任务,过期就过期了。放在/etc/cron.weekly/里的任务,几乎一定会在一周内执行一次——即使关机超过一周,开机几小时内就会被 anacron 补上。
完整执行链:
1. crond 读取 /etc/crontab, /var/spool/cron/*, /etc/cron.d/* → 按时间执行
2. /etc/cron.d/0hourly → 每小时执行 /etc/cron.hourly/ 下的可执行文件
3. /etc/cron.hourly/0anacron → 每小时调用 anacron,读取 /etc/anacrontab
4. anacron 根据 /etc/anacrontab → 检测并按需补执行 /etc/cron.{daily,weekly,monthly}/