dnf 的前世今生
这篇文章主要介绍 dnf 的前世今生:它不是凭空出现的,而是叠在 RPM 几十年演进之上的一层。
先记一条主线:
RPM —— 管本机:装了什么、文件落在哪、本地依赖元数据
↑
yum / dnf —— 管远程:仓库索引、拉包、版本依赖图、冲突求解
dnf 原本是 yum 的升级版本。今天的 dnf 仍然基于 RPM:RPM 负责管理本地包数据库;dnf 负责索引远程仓库,并在把远程包装到本机时,做版本依赖的图分析、用算法优化冲突一类问题。同时,它也负责在本机视角管理「有哪些远程仓库」。
一、先有 RPM
为什么不是一步到位的「远程包管理器」?因为九十年代要先解决的是:本机软件怎么记账。
1. 本机记账
九十年代中期,Linux 发行版刚开始把软件做成「包」。Red Hat 这边先后试过几代工具:
-
RPP(Red Hat Package Manager 的前身之一)
最早的 Red Hat Linux 预览版 / 1.0(约 1994–1995)用的是 RPP,还不是后来的 RPM。 -
PMS / PM
Rik Faith 等人在 BOGUS 发行版上做了 PMS;后来 Faith 与 Doug Hoffman 又为 Red Hat 做过 PM。这些工具已经有「一条命令装/卸」「安装前后脚本」「校验」「查询」等想法,但数据库设计偏弱,对多架构支持也不够。 -
RPM(1995,Marc Ewing & Erik Troan)
总结前几代教训后,第三代方案就叫 Red Hat Package Manager。Red Hat Linux 2.0 起正式采用。早期 1.x 用 Perl 快速落地;随后整套用 C 重写,并把包数据库格式重新设计,查询更快、也更可靠。名字后来变成递归缩写:RPM Package Manager。
这一阶段要解决的核心问题很朴素:
- 一个包里装了哪些文件?
- 卸掉时怎么干净撤回?
- 怎么校验文件有没有被改过?
- 本机现在到底装了哪些包、提供了哪些能力(provides)?
答案是:给本机维护一份专用的包数据库(传统路径在 /var/lib/rpm)。
RPM 把「包头元数据 + 已安装文件清单」当成系统真相源;查询(rpm -q)、校验、安装、卸载都围绕这份库转。
这就是今天分工的底色:RPM 从诞生起就是「本机账本」,不是「网上商店」。
2. RPM 不做的事
有些能力 RPM 故意不做,也长期做不好——正因如此,上面才需要再叠一层。
RPM 能装一个本地 .rpm 文件,也能读出它声明的依赖;但早期它不会自动帮你:
- 去远程仓库找齐依赖包;
- 在成千上万个候选版本里做全局最优(或至少一致)的求解;
- 维护「本机订阅了哪些仓库、元数据何时过期」。
网络还慢、仓库规模也小的年代,这可以接受:管理员自己下包、自己理依赖。等发行版包数量涨到成千上万,再靠人手拼依赖就不可持续了——于是上面必须再叠一层「仓库客户端 + 依赖求解器」。
二、从 yum 到 dnf
中间那一层补上了远程仓库与自动依赖,再由 dnf 把求解引擎现代化。
1. yum
给 RPM 补上「远程仓库 + 自动依赖」。
大约 1999–2001,面向 PowerPC / Mac 的 Yellow Dog Linux 做了 YUP(Yellowdog Updater):从 RPM 头里抽依赖,拼依赖树,自动化安装与维护。
2002 年前后,Duke 大学物理系系统管理员 Seth Vidal(后来还有 Michael Stenner 等人)觉得 yup 不够快、也不够通用,于是重写成 yum——Yellowdog Updater, Modified。yum 仍然操作 RPM 格式的包,仍然把最终安装写进 RPM 数据库,但把「仓库元数据、下载、依赖解析」收到自己这边。
之后 yum 成为 Fedora / RHEL 系的默认高层包管理器,几乎成了「人们口中的包管理」——尽管底层记账的仍是 rpm。
2. dnf
yum 的现代化重写,全称 Dandified YUM。
yum 用久了,暴露出几类结构问题:依赖求解偏迭代、内存与速度吃紧、Python API 长年有机生长、难给 Anaconda / PackageKit 等非 Python 客户端复用。
2012 年左右,项目从 yum 3.4 fork 出 dnf,核心变化包括:
- 依赖求解改走 libsolv(经 hawkey 等封装)的 SAT 求解思路,冲突与版本选择更可预期;
- 仓库访问、comps 等拆成可复用的底层库;
- 更明确的 API,并兼顾 Python 2/3;
- 代码更瘦,便于长期维护。
Fedora 从 Fedora 22 起把 dnf 定为默认;RHEL 8 一代也切到 dnf。命令行刻意兼容大部分 yum 习惯,所以很多人感觉「还是那个 yum」,但引擎已经换代。
于是今天的图景是:
| 层 | 管什么 | 典型命令 |
|---|---|---|
| RPM | 本机包数据库、本地 .rpm 的装/卸/查/验 |
rpm -qa、rpm -ivh foo.rpm |
| dnf(前身 yum) | 远程仓库索引、本机仓库配置、跨包依赖图与冲突、事务历史 | dnf install、dnf upgrade、dnf repolist |
dnf 没有取代 RPM;它是站在 RPM 之上的编排层。真正改动系统文件、改写 /var/lib/rpm 的,最后仍落到 RPM 事务。
三、职责切分
现在为什么「必须是这样」?一次安装把两层各自的活摊开就清楚了。
可以按数据流理解一次 dnf install foo:
- dnf 读本机仓库配置(例如
/etc/yum.repos.d/),刷新或使用缓存的仓库元数据; - dnf 结合「已安装集合」(来自 RPM DB)与「仓库候选集合」,建依赖图,求解要装 / 升级 / 淘汰哪些包;
- dnf 下载选定的
.rpm; - RPM 执行实际安装:解包、跑脚本、更新本机包数据库。
所以:
- RPM:本机真相源——「这台机器现在是什么状态」。
- dnf:网络与策略层——「从哪里拿、拿哪一版、和现有状态如何兼容」。
查询也会分成两套气质不同的入口,对应「给人看」和「给脚本用」:
dnf list:偏人类友好。按已安装 / 可安装 / 可升级等分区展示,终端里可读性好。dnf repoquery:偏批处理与精确查询。更像「对远程(也可对已安装)做rpm -q」,可用--qf/--queryformat定制字段,适合管道和自动化。
本地侧同理:日常用 rpm -q / rpm -qa 查账本;需要机器可读字段时同样靠 queryformat。
四、查询与写入
特别值得一提:dnf(以及底层的 rpm)把「查」和「写」当成两套原则——查可以放宽,写必须收紧。
1. 查询
普通用户和管理员都可以。
- dnf 允许普通用户查询远程仓库里有什么包、本机仓库配置指向哪里(读元数据、读缓存)。
- rpm 也允许普通用户查询本地软件包数据库(读
/var/lib/rpm一类信息)。
只读不改系统,所以不必提权。这对「看看有没有某库」「脚本探测已装版本」很重要。
2. 写入
改的是系统级状态,必须管理员。
一旦要:
- 安装 / 升级 / 删除包;
- 改仓库启用状态并影响后续系统变更;
- 直接用
rpm写入或重建包数据库;
就涉及系统级写入:往 /usr、/etc、/var 落文件,并更新 RPM 数据库。这和「普通用户在家目录装东西」不是一类操作,因此 dnf 的写入类命令、rpm 的写入类命令,都需要管理员权限(root 或等价的提权)。
可以记成:
读(query / list / repoquery / rpm -q)→ 用户可做
写(install / remove / 改 rpmdb) → 必须管理员
这不是 dnf 独有的「刁难」,而是 RPM 从一开始把「已安装软件」定义成整机共享状态的自然结果:账本在系统目录里,写入账本就等于改系统。
五、收束
回看整条时间线:
- 1995 前后:RPM 在 RPP/PMS/PM 的教训上长出来,核心是可靠的本机包数据库。
- 2000 年代:包与仓库规模爆炸,yum 补上远程索引与自动依赖;RPM 继续只管本地落地。
- 2010 年代至今:dnf 用更好的求解器与库边界接过 yum,但分层没变——RPM 记账,dnf 找货与算依赖。
所以今天用 Fedora / RHEL 系时,不必把 dnf 和 rpm 对立起来:日常装软件用 dnf;排障、查本机真相、处理单个 .rpm 时仍常落到 rpm。理解「谁管本地账本、谁管远程与图」,再看 list / repoquery 以及「能查不能乱写」的权限模型,整条链路就顺了。