Linux 包管理进化史:每个答案,都是上一个问题逼出来的(上篇)17认证网

正规官方授权
更专业・更权威

Linux 包管理进化史:每个答案,都是上一个问题逼出来的(上篇)

上篇 · 包管理的诞生:从手工到自动解依赖

你大概敲过成百上千次 yum install nginx。回车,等几秒,装好了。它顺滑得像呼吸一样,以至于你从没想过去问:这中间到底发生了什么?

可一旦它出错——比如某天它告诉你”两个包在抢同一个文件,事务被中止”——你会突然发现,这个每天都在用的命令,背后藏着一套你从未真正理解的机器。你不知道它凭什么自动找到了依赖,不知道它怎么知道哪个文件归谁,不知道为什么有的软件能装两个版本、有的却水火不容。

这篇文章想带你做一件事:把这台机器拆开,但不是按零件拆,而是按时间拆。

因为 Linux 的包管理,不是某个天才一次设计出来的。它是三十年里,一代工具踩着上一代的尸体长出来的。每一个你今天觉得理所当然的功能,当年都是为了解决一个让人痛不欲生的问题才被逼出来的。而麻烦的是——每解决一个问题,几乎都会催生出一个新的、更隐蔽的问题。 正是这条”问题——答案——新问题”的链条,把包管理一步步推到了今天的模样。

读懂了这条链条,你就不再需要”背”任何命令。你会发现每一个工具的存在,都是顺理成章、甚至是不得不然的。

我们从一切的起点开始——那个还没有”包”的蛮荒时代。

一、蛮荒时代:一切靠手的混沌

最早,在 Linux 上装一个软件,是这样的:

./configure
make
make install

你下载一份源代码,自己编译,然后把编出来的文件,一个个塞进系统该去的地方。可执行文件丢进 /usr/bin,库文件丢进 /usr/lib,配置丢进 /etc,文档丢进 /usr/share/doc……

这套办法能用。但它有三个会随着时间慢慢要你命的毛病。

第一,系统失忆了。 你今天 make install 了一个软件,它把几十个文件散布到了系统各个角落。三个月后,这些文件还在,但没有任何地方记录着”它们是谁、从哪来、属于哪个软件”。系统对自己装了什么,一无所知。

第二,卸载是一场灾难。 正因为没有记录,当你想删掉这个软件时,你根本不知道要删哪些文件。那几十个散落各处的文件,你只能凭记忆一个个去找、去删。删漏了,留下垃圾;删错了,搞坏系统。make install 给了你安装的能力,却没给你干净撤销的能力。

第三,也是最致命的——依赖。 你想装软件 A,编译到一半,它报错:缺少 libssl。于是你去下载 libssl 的源码,编译它,结果 libssl 又说:我需要另一个库。你顺着这条线越挖越深,像在挖一口望不到底的井。等你终于把所有底层库都编译装好,几个小时已经过去了。

这三个毛病的根子,其实是同一件事:软件的”文件”和软件的”身份信息”是分离的。 系统只看到一堆散落的文件,却不知道这些文件合起来是一个有名字、有版本、有出处、有依赖关系的整体。

于是,第一个需求被逼了出来,而且无比清晰:

我们需要一个”盒子”,把一个软件的所有文件,连同它的身份信息(叫什么、什么版本、谁做的、它需要谁),整个打包在一起。安装,就是拆这个盒子;卸载,就是凭着盒子里的清单,把当初装进去的东西原样取走。

这个盒子,就是 .rpm(以及 Debian 世界的 .deb)。

二、盒子纪元:rpm,与一条悄悄立下的铁律

.rpm 文件,本质上就是上面那个”盒子”。把它拆开,里面装着三样东西:

  • 文件载荷:这个软件要装进系统的所有文件,以及它们各自该去的路径。
  • 元数据:这个包叫什么、什么版本、谁打的、它依赖哪些别的包、它和谁冲突、它带来的完整文件清单……
  • 脚本:安装前后、卸载前后要执行的一小段代码(术语叫 scriptlet)——比如创建用户、注册服务。它在排错和安全审计时很值得留意,但不在我们这条主线上,本文不展开。

管理这些盒子的工具,就叫 rpm

rpm -i 软件.rpm      # install,拆盒子装进去
rpm -e 软件名         # erase,凭清单原样卸载

蛮荒时代的前两个毛病,当场被治好了

系统不再失忆——因为 rpm 在安装时,会把盒子里的元数据,完整地登记进一个本地的数据库(在 RPM 世界,它住在 /var/lib/rpm,我们后面会专门请它登场)。

从此你随时能查:这个软件装了哪些文件(rpm -ql)?这个文件到底是哪个软件带来的(rpm -qf)?这台机器上一共装了些什么?

这份登记还藏着一个常被忽略、却在安全场景里极有分量的能力:校验。既然数据库里记着每个文件原本的大小、权限、甚至内容摘要,那 rpm -V(verify)就能把磁盘上文件的现状和当初登记的”标准答案”逐一比对,告诉你哪些文件被改动过了——配置被篡改、权限被悄悄放大、二进制被替换,一查便知。

例如你运行 rpm -V curl 没有返回信息,说明你的系统中 curl 软件包非常健康、原汁原味,不需要进行任何修复或处理

做等保合规、做完整性核查时,这是一把内置的、几乎零成本的尺子。换句话说,这份账本不只记录”装了什么”,还默默守着”它们有没有被动过手脚”。

卸载也不再是灾难——因为有了那份登记在案的文件清单,rpm -e 能精确地知道当初装了哪些文件,原路撤销,一个不多一个不少。

到这里,请你记住一个不起眼、但将贯穿这整篇文章的设计。

为了让”哪个文件属于哪个包”这件事不出乱子,RPM 立下了一条铁律:

系统里的任何一个文件,在同一时刻,只能属于一个包。

这条铁律此刻看起来平淡无奇,甚至理所当然,一个文件当然只能有一个主人,不然卸载的时候,删还是不删?可正是这条不起眼的规则,会在后面亲手制造出我们最棘手的麻烦,也会成为整部进化史里一次次被挑战、被绕开、最终被彻底颠覆的主角。先把它记在心里,我们继续。

盒子治好了失忆和卸载。但你有没有注意到,蛮荒时代那三个毛病里,最致命的第三个,我们还没碰?

依赖。

盒子确实把”我需要 libssl”这条信息,工工整整地写进了元数据里。可问题是——rpm 这个工具,只会读这条信息,却不会替你去做任何事

你 rpm -i 一个需要 libssl 的包,它一看:哦,你缺 libssl。然后呢?然后它就报错、罢工、停在那里。它会忠实地告诉你”缺了什么”,但绝不会主动去帮你找来、装上。

于是你又得自己去翻 libssl 的盒子。装上 libssl,它又说还缺别的。你一个盒子一个盒子地手动追下去——这场景是不是有点眼熟?没错,蛮荒时代那口望不到底的依赖之井,又回来了。 只不过这次,井底躺的是一个个 .rpm 盒子,而不是一份份源码。

这就是无数老 Linux 用户闻之色变的四个字:

依赖地狱(Dependency Hell)。

盒子解决了”单个软件怎么装、怎么卸”,却暴露出一个它本身无力解决的新问题:软件之间是结网的,而 rpm 只会处理一个孤立的点。 它知道每个点需要连到哪些别的点,却不会自己去把这张网走通。

新的痛点,逼出了新的需求,而且同样清晰:

我们需要一个更聪明的工具。它得知道”所有可用的盒子都在哪、各自又需要谁”,然后当我说”我要装 A”时,它能自动把 A 牵连出的整张依赖网都走一遍,算出”要让 A 跑起来,总共得装哪些盒子”,然后一次性、一个不漏地装好。

这个工具,就是 yum(以及它后来的继任者 dnf)。

顺带说一句这个名字的来历,它本身就是一部微缩的传承史:YUM 是 Yellowdog Updater, Modified 的缩写。注意那个 “Modified(修改版)”——它说明 YUM 不是凭空造的,而是改写自一个更早的前身 YUP(Yellowdog UPdater),最初为 Yellow Dog Linux 而生,后来由杜克大学物理系的 Seth Vidal 等人彻底重写,用来管理他们的 Red Hat 系统。名字里就刻着一句话:我是上一代的改良版。

而为了让它能”知道所有盒子在哪”,一个全新的概念,也必须随之诞生——仓库(repository)。

三、大求解时代:yum,与依赖地狱的终结

要终结依赖地狱,光有一个聪明的工具还不够。它得先有”情报”——它必须知道这世上存在哪些盒子、每个盒子又需要谁。否则,再聪明的大脑,面对一片空白也无从算起。

所以 yum 的出现,其实是两个东西捆在一起登场的,缺一不可:

其一,仓库。 这是一个集中存放所有 .rpm 盒子的地方(通常是一台服务器),更关键的是,它还附带一份索引——把所有盒子的元数据(谁需要谁、谁和谁冲突)预先汇总成了一张总表。有了这张总表,工具不必把每个盒子都下载下来拆开看,只需读一遍索引,就掌握了全局的依赖关系。这就是你在 .repo 文件里配置的那些 baseurl——它们指向的,正是一个个仓库。

其二,依赖求解器(resolver)。 这才是 yum 真正的大脑。当你说”装 nginx”时,它做的事情是:从仓库索引里查到 nginx,发现它需要 A、B;再查 A,发现 A 需要 C;再查 B……它顺着这张网一路走下去,直到把所有牵连到的盒子全部找齐,在脑子里拼出一个完整的安装清单,然后才动手,一次性全部装好。

那场困扰了 Linux 用户许多年的依赖地狱,到这里,终于被终结了。 你只需要说出你想要什么,剩下那张错综复杂的依赖网,交给机器去走。

这里顺带解开一个很多人都纳闷过的现象:为什么我只 dnf install 一个小工具,它却”自作主张”地装上了一堆我没要的东西?这是因为依赖并不只有”非装不可”这一种。RPM 把依赖分了强弱:硬依赖(Requires)是”缺了就跑不起来”,必须装;而弱依赖(Recommends)是”装上体验更好、但没有也能跑”——比如某个工具推荐的插件、文档、可选后端。默认情况下,dnf 会连弱依赖一起装,让你开箱即用。那一堆”我没要的东西”,多半就是这些弱依赖。知道了这个,你就明白为什么有时想要个最精简的安装,得加上 --setopt=install_weak_deps=False 把弱依赖关掉。

这是一次巨大的解放。但我想请你停下来,看一眼这个”求解”的过程——因为这里藏着一个绝大多数人从未意识到的真相。

yum 解依赖,看起来只是”顺着箭头找下去”那么简单吗?

不是的。真实世界的依赖网,远比”A 需要 B”复杂。它充满了带版本约束的要求(A 需要 1.2 以上的 B)、互相打架的冲突(C 和 D 势不两立,不能共存)、多条可选路径(满足这个需求的包不止一个,该选哪个?)。在这样一张网里,要找出一个所有约束同时被满足、谁也不和谁矛盾的安装方案,这本质上是一道经典的计算机科学难题——约束求解,与逻辑学里的 SAT(布尔可满足性)问题同源,属于计算复杂度最高的那一类问题之一。

这不是一个煽情的比喻,而是一个被正式证明过的结论。早在 2005 年,就有研究者给出了严格的证明:软件包的安装,是一个 NP 完全问题——他们正是用 SAT 的方式,把 Debian 和 RPM 的依赖关系编码了出来。换句话说,你 install 时机器在做的事,和那些最硬核的算法难题,是同一个量级的。

换句话说:

你每一次轻描淡写的 yum install,背后其实是机器在解一道数学题。

理解了这一点,你就理解了为什么早期的 yum 有时慢得让人抓狂——它的求解算法还很原始,面对庞大复杂的依赖网,它得吭哧吭哧算很久,有时还算不出最优解。

你看,熟悉的剧情又上演了:yum 解决了”依赖要不要手动追”这个老问题,但它解决的方式(求解一道数学难题),本身又变成了一个新问题——这道题怎么解得又快又好?

这个新问题,会把我们引向下一代工具 dnf,以及它背后那个真正的”数学引擎”。

四、引擎革命:dnf,与求解的工业化

如果说 yum 第一次让机器替我们解依赖这道数学题,那么 dnf(Dandified YUM)做的事,就是把这道题交给了一个专业的解题引擎

这个引擎叫 libsolv

而 dnf 这个名字更是把血缘写在了脸上:它是 Dandified YUM 的缩写——”Dandified” 是”精装、翻新”的意思,而后半截直接就是 YUM。也就是说,它的全名等于明牌承认:我是 YUM 的翻新继任者。(有些文档还会俏皮地把三个字母拆成 DaNdiFied YUM,把 D、N、F 都从 Dandified 里抠出来——属于事后凑的趣味写法,不是正式词源。)

它不是 dnf 团队随手写的一段查找逻辑,而是一个独立的、专门用来求解”包依赖”这类约束问题的库——本质上,它就是一个为软件包量身定做的 SAT 求解器。还记得我们刚说的吗:解依赖,本质是解一道布尔可满足性问题。既然如此,那就请真正擅长解这类问题的算法来上场。

换上 libsolv 之后,变化是肉眼可见的:求解更快,内存占用更省,而且面对那些有多条可选路径的复杂局面,它能给出更合理的方案,而不是早期 yum 那种磕磕绊绊、有时还把自己绕进死胡同的笨办法。

所以你今天在 RHEL 9、Rocky、openEuler、Kylin 上敲的 yum,其实几乎都是 dnf 在背后干活——yum 如今多半只是一个指向 dnf 的别名,保留这个名字,只是为了照顾几代人的肌肉记忆。这本身就是进化史的一个温柔注脚:新引擎换上了,旧名字留着,谁也没察觉。

到引擎革命为止,我们似乎已经把”怎么把软件弄进系统”这件事做到极致了:盒子能装能卸,依赖能自动求解,求解还又快又准。

而且 dnf 还顺手带来了一个被严重低估的能力——时光倒流。它把每一次安装、升级、卸载都当成一笔”事务”完整地记录下来(dnf history 能看到这本流水账),更关键的是,它允许你整笔撤销:dnf history undo <ID> 能把某一次操作连根拔起(那次装了什么就卸掉、卸了什么就装回来),dnf history rollback <ID> 甚至能把系统”回退”到某次操作之后的整体状态。你 dnf update 完发现某个服务起不来了?一条 undo 就能退回去。这背后正是”事务”这个思想在撑腰——操作不是一去不回的泼水,而是可追溯、可回滚的账目。

不过它也有边界,得心里有数:内核、glibc、selinux 这类核心包的降级不在支持之列;而且一旦仓库里旧版本的包已经被删掉、找不到了,回滚就会失败——这恰恰说明,在严肃的生产环境里,留存一份带历史版本的本地源有多重要。

故事讲到这里,似乎可以圆满收场了。安装、依赖、升级、回滚——软件管理的方方面面,都被这套越来越精密的机器照顾到了。一个 Linux 管理员,似乎再没有什么可发愁的。

但请你回头看一眼,我们这一路走来,关注的始终是同一个方向——怎么把东西”弄进来”。从蛮荒时代手动编译,到盒子,到 yum,到 dnf,我们解决的全都是”安装”这个动作。

而真正的风暴,恰恰不在”怎么装进来”,而在”装进来之后,这台机器怎么记住这一切、又怎么守住秩序”。

有一个角色,从盒子纪元诞生那一刻起,就一直沉默地站在幕后。我们提过它一次,然后就把它搁下了。它不声不响地记着每一笔账,平时你几乎感觉不到它的存在——可一旦出事,所有的腥风血雨,主角都是它。

接下来你将看到:一条三十年来从未被打破的铁律,如何因为它而存在;一次再普通不过的 yum install,又如何因为它而当场崩盘,逼停整个系统。

那个沉默的主角,就是——数据库

转自:张先生的深夜课堂

版权申明:内容来源网络,版权归原创者所有,如有侵权请联系删除

想了解更多干货,可通过下方扫码关注

可扫码添加上智启元官方客服微信👇

未经允许不得转载:17认证网 » Linux 包管理进化史:每个答案,都是上一个问题逼出来的(上篇)
分享到:0

评论已关闭。

400-663-6632
咨询老师
咨询老师
咨询老师