pacman的原理
Alexander Hou

作为 Arch Linux 用户,定期清理系统是必经之路。pacman 和 yay(或者其他 AUR 助手)虽然日常都在用,但经常也就是 -Syu 一键流,真正到清理垃圾、处理孤儿包的时候,容易忘掉那些组合参数。这篇文章整理了一些与包管理相关的知识,作为一位Arch Linux爱好者,我认为深入pacman 的过程是可以学到很多东西的。

pacman工作原理

Arch Linux 的 pacman 能够做到如此轻量、快速,是因为它的底层设计极其纯粹。它不像 Ubuntu 的apt或 Fedora 的dnf那样维护复杂的元数据服务器,也不在本地运行繁重的数据库守护进程。pacman 的本质是一个本地 tar 包管理器 + 极其简单的 C 语言依赖求解器。下面我们拆解一下 pacman 的核心工作原理:从它的数据库结构,到你敲下安装命令时,底层究竟发生了什么。

一切皆在 /var/lib/pacman/

pacman 为什么快?因为它的“数据库”不是 SQL,也不是复杂的二进制文件,而是一个纯文本的文件系统目录。如果你进入 /var/lib/pacman/,你会发现两个核心目录,一个是sync/ (远程仓库的镜像本地缓存),另一个是local/ (你本地已安装的数据库),接下来,我们将深入探索这两个目录。

首先便是 sync/ 目录,在 /var/lib/pacman/ 架构中,sync/ 目录扮演着“本地镜像站索引缓存”的角色。它是 Arch Linux 能够实现“秒级”搜索远程仓库的核心所在。如果深入进去看,这个目录里存放的其实是你启用的各个官方仓库(如 core、extra)以及第三方仓库的本地数据库文件。下面我们用最直观的方式拆解这个目录里究竟存了什么、长什么样,以及它是如何工作的。我们进入 /var/lib/pacman/sync/ ,我们会看到以下文件:

1
2
3
alexander-arch-linux% cd /var/lib/pacman/sync/
alexander-arch-linux% ls
core.db extra.db multilib.db

现在我们以 extra.db 为例,使用 file 命令去看看这些文件的真面目。

1
2
alexander-arch-linux% file extra.db
extra.db: gzip compressed data, from Unix, original size modulo 2^32 35645440

根据输出,这些 .db 后缀的文件,表面上叫“数据库”,其实全都是用 gzip 压缩的普通 .tar.gz 归档文件。现在我们用 tar 去解压它,去看看它里面有什么。

1
2
3
4
5
6
7
8
9
10
alexander-arch-linux% mkdir ~/test && cd ~/test
alexander-arch-linux% cp /var/lib/pacman/sync/extra.db ./extra.tar.gz
alexander-arch-linux% ls
extra.tar.gz
alexander-arch-linux% mkdir extra_extracted
alexander-arch-linux% tar -xf extra.tar.gz -C ./extra_extracted
alexander-arch-linux% ls
extra_extracted extra.tar.gz
alexander-arch-linux% cd extra_extracted
alexander-arch-linux% ls -l | head -n 20

执行完 ls 之后,会看到一长串以“软件名-版本号-构建号”命名的文件夹。我们直接拿 aarch64-linux-gnu-gcc 作为例子,这是一个交叉编译器,它的实际作用是:让你在运行 x86_64(英特尔/AMD)架构的 Arch Linux计算机上,编译出能跑在 ARM64(比如树莓派、手机、RISC 架构服务器)上的程序,不过这其实不重要。我们直接切入 aarch64-linux-gnu-gcc 的元数据文件夹。

1
2
3
alexander-arch-linux% cd aarch64-linux-gnu-gcc-*       
alexander-arch-linux% ls -al
.rw-r--r-- 1.3k alexander 26 4月 00:06 desc

我们看到里面只有 desc 这一个纯文本文件。现在,我们直接查看这个文件的内容。

1
more desc

接下来,我们将要拆解 desc 中的每一个字段,这些代码块的内容都来源于文件内容。

1
2
%FILENAME%
aarch64-linux-gnu-gcc-15.2.0-1-x86_64.pkg.tar.zst

%FILENAME% 的含义是对应的物理软件包文件名,后缀 .pkg.tar.zst 再次印证了 Arch 的本质:它就是一个用 zstd 算法压缩的 tar 归档。这个字段扮演着“远程文件精准定位器”的角色。简单来说,它的核心作用是:告诉 pacman 去镜像站下载该软件时,那个真正的 .pkg.tar.zst 压缩包在服务器上叫什么名字。当我们敲下 sudo pacman -S aarch64-linux-gnu-gcc 时,pacman 本地并没有这个软件的二进制实体,它必须去联网下载。说到联网,就不得不提到 pacman 核心架构中最漂亮的设计之一:静态 URL 映射机制。

简单来说,pacman 能够如此高效,是因为它把全世界成百上千个镜像站的复杂目录结构,抽象成了一套像拼积木一样的标准公式。它不需要去问服务器“请问某个软件在哪”,它自己算一算就知道路径。首先便是镜像站的“千篇一律”,即标准目录树,不管是中科大源(ustc)、清华源(tuna),还是国外的官方源,只要它们宣称自己是 “Arch Linux 镜像站”,它们的服务器上就必须建立一套一模一样的文件夹结构。这套结构通常长这样:

1
2
3
4
5
6
7
archlinux/                 <-- 镜像站的根目录
├── core/
│ └── os/
│ └── x86_64/ <-- 存放核心系统包和 core.db
└── extra/
└── os/
└── x86_64/ <-- 存放常用扩展包和 extra.db

因为这个结构是定死的,所以 pacman 只需要知道三样东西,就能用 C 语言的字符串拼接函数(比如 sprintf)瞬间组合出完整的服务器路径。现在,我们将会探究这三块“积木“。

  • 积木1:镜像站底座(Base URL)

    我们在 /etc/pacman.d/mirrorlist 里面写了什么,它就用什么。比如我们启用了清华源,它就在内存里准备好字符串:https://mirrors.tuna.tsinghua.edu.cn/archlinux/

  • 积木2:仓库名字(Repository)

    当我们在命令行运行 pacman -S aarch64-linux-gnu-gcc 时,pacman 会去本地的 sync/ 目录下翻账本。它发现这个包的 desc 文件是在 extra.db 里面解压出来的。 于是它立刻断定:这个软件属于 extra 仓库。它把 extra/ 贴到上一段字符串后面:https://mirrors.tuna.tsinghua.edu.cn/archlinux/extra/

  • 积木3:系统架构与固定后缀(OS & Architecture)

    由于我们运行的是标准的 Arch Linux,机器架构是 x86_64 。pacman 会读取自身的配置文件,自动加上固定的补全路径 /os/x86_64/。 于是就得到了https://mirrors.tuna.tsinghua.edu.cn/archlinux/extra/os/x86_64/

完成了这三块积木的拼接,pacman 的手里就拿到了这个软件所在的远程文件夹通票。接下来,就是%FILENAME% 字段发挥作用的时候了。pacman把从 desc 里读到的文件名无缝贴上去。得到了:

1
https://mirrors.tuna.tsinghua.edu.cn/archlinux/extra/os/x86_64/aarch64-linux-gnu-gcc-15.2.0-1-x86_64.pkg.tar.zst

最终,pacman 拿着这个长得像面条一样的完整网络 URL,直接丢给底层的网络下载引擎,数据流就开始顺着网线源源不断地下载到 /var/cache/pacman/pkg/ 目录下了。

我们可以看到,pacman 极度缺乏热情,它从来不和远程服务器进行“智能对话”,它所有的网络下载,全都是靠读取本地配置和文本数据库,在本地硬生生把目标 URL “拼”出来的。 这种不依赖服务器动态查询的设计,就是它速度飞快的硬核保障。

1
2
3
4
5
%NAME%
aarch64-linux-gnu-gcc

%BASE%
aarch64-linux-gnu-gcc

这两个字段触及到了 Arch Linux 打包制度中非常核心的 “单一源码包多重分裂” 机制。在 Arch Linux 中,打包一个软件需要编写一个叫 PKGBUILD 的脚本。通常情况下,一个源码包编译出来后,只能得到一个同名的软件。此时,%NAME%%BASE% 是一模一样的。但是,有一些软件体积庞大,或者组件非常多。如果把它们全塞进一个包里,用户就算只想要一个小功能,也必须下载整套“全家桶”,这违背了 Arch 的轻量哲学。于是,Arch 引入了 “分割包(Split Packages)” 机制:允许一个源码包(Base),在编译完成后,分裂成好几个不同的独立软件包(Name)分别上架。

%NAME% 是包的唯一标识名,%BASE%是源码包(PKGBUILD)的名称。可以将 %NAME% 理解为“孩子”出生后拿到的独立身份证号/姓名,而%BASE% 是它所属的“家族”或“父项目”的名字。在 Arch 中,一个源码包可以分裂出多个子包(比如 gcc 源码可以同时产出 gccgcc-fortrangcc-objc 等)。

1
2
3
4
5
%VERSION% 
15.2.0-1

%DESC%
The GNU Compiler Collection - cross compiler for ARM64 target

%VERSION% 是软件版本号15.2.0加上 Arch 官方的第 1 次打包构建。%DESC 则是软件一句话简介,表明了它“面向 ARM64 目标的交叉编译器”的身份。

1
2
3
4
5
%CSIZE%
87248449

%ISIZE%
398046252

这两个字段的含义是空间占用,单位是字节(Byte)%CSIZE%(Compressed Size)表示压缩包约 83.2 MiB(下载时耗费的流量)。%ISIZE%(Installed Size)表示解压后约 379.6 MiB(在硬盘上实际吃掉的空间)。

1
2
%SHA256SUM%
3300aefb936e523747f8620594fa9e415089c4c199815bee3ab23a73d34e7752

这个字段表示下载校验和。包下载完成后,pacman 内部会运行哈希算法计算下载文件的 SHA256 值。如果和这一行对不上,说明下载过程丢包了,或者遇到了中间人攻击,pacman 会果断报错并拒绝安装。

1
2
%PGPSIG%
iQIzBAABCgAdFiEEJipY7GxR9...

这是极为重要的 PGP 数字签名。这是打包者用私钥对这个包做的签名。当你的 Arch 运行 pacman 时,它会用你本地信任的 Arch 密钥环(archlinux-keyring)去解密验证。只有 SHA256 和 PGP 签名双重通过,一个包才会被允许写入根目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
%BUILDDATE%
1777123867

%PACKAGER%
Robin Candau <antiz@archlinux.org>

%URL%
https://gcc.gnu.org/

%LICENSE%
GPL
LGPL
FDL

这是编译时间戳。转换成人类可读时间大约是 2026 年 4 月。

这是维护并编译这个包的 Arch 官方开发者(Trusted User / Developer)。这里是 Robin Candau 大佬。

项目官方网站。当你用 pacman -Qi 查看时就会显示这个。

软件开源许可证。GCC 核心组件采用 GPL,运行时库采用 LGPL,文档采用 FDL。

1
2
%ARCH%
x86_64
  • 核心理解: 注意!这里写的是 x86_64,而不是 aarch64 * 为什么?因为这个编译器是运行在你的 Intel/AMD 电脑上的。它的“执行主体”是 x86_64 代码,只是它“吐出来的二进制文件”是 ARM64 代码。不要被名字里的 aarch64 迷惑了,它依然是一个不折不扣的 x86_64 原生程序。

三、 图论交织:依赖树的核心

这里是整个文件技术含金量最高、也是最有趣的地方:

Plaintext

1
2
3
4
5
6
7
%DEPENDS%
aarch64-linux-gnu-binutils
aarch64-linux-gnu-glibc
libmpc
zlib
libisl
zstd
  • 含义: 运行时依赖。
  • 深度拆解: 1. 你会发现它依赖 aarch64-linux-gnu-binutils(ARM64 的汇编器和链接器 asld)和 aarch64-linux-gnu-glibc(ARM64 的 C 标准库头文件和库)。 2. 编译器编译代码时,必须要知道目标机器的 C 语言基本函数(如 printf)在 ARM64 下是怎么定义的,所以它把这两个交叉工具链组件作为强依赖。 3. libmpclibislzstd 则是 GCC 本身运行需要的高级数学库和压缩库。

Plaintext

1
2
3
%MAKEDEPENDS%
gmp
mpfr
  • 思考题:等等!你上一把不是说 desc 里一般没有 %MAKEDEPENDS% 吗?这里怎么冒出来了?
  • 硬核真相: 在 Arch 官方的 extra 仓库设计中,如果你看到 desc 里依然保留了 %MAKEDEPENDS%,通常有两种可能:
    1. 打包自动化工具的残留: 官方工具在生成仓库数据库时,部分复杂的交叉编译包或多阶段构建包(Bootstrap 包),其构建依赖被直接索引进了元数据里。
    2. 用于给源码构建(如 makepkg -s)或某些特殊元数据追踪工具提供参考: 让高级用户或链式构建工具知道,如果想从源码重新编译这个交叉编译器,本地必须先安装 gmpmpfr 这两个多精度浮点运算库。

当你运行 pacman -Sy 时,pacman 会根据 /etc/pacman.conf 里的镜像源地址,下载最新的 core.dbextra.db 等文件。

  • 这些 .db 文件表面上是数据库,实际上只是压缩过的 .tar.gz 纯文本包
  • 解压后,每个软件对应一个文件夹,里面只有几个名为 desc(描述信息、版本、依赖、体积)和 depends(依赖列表)的纯文本文件。
  • 原理: pacman 找软件时,不联网查询,而是直接用 C 语言的字符串匹配算法,在这些本地文本文件里翻找。

这里记录了你电脑上当前安装的所有软件。

  • 每个安装的软件都有一个目录,里面除了 desc,还有一个至关重要的文件:files
  • files 文件里精确列出了这个软件在你的系统里创建的每一个文件、每一个软链接、每一个目录的绝对路径(例如 usr/bin/bash)。
  • 原理: 为什么 pacman -R 能删得干干净净?为什么 pacman -Qo /path/to/file 能一秒查出某个文件属于哪个包?就是因为 pacman 只需要去遍历 local/ 下这些文本文件里的路径列表。

深度剖析:pacman -S <package> 的执行全流程

当你敲下安装命令并回车时,底层会经历 5 个阶段:

1
[输入命令] ──> 1. 依赖求解 (ALPM) ──> 2. 冲突检测 ──> 3. 下载 Tarball ──> 4. 解压与文件释放 ──> 5. 挂钩 (Hooks) 触发

1. 依赖求解(Dependency Resolution)

pacman 调用的核心底层库叫 libalpm (Arch Linux Package Management library)。

  • 它会在本地的 sync/ 目录中找到目标包的 desc 文件,读取它的 DEPENDS 字段。
  • 如果有依赖,它会递归地去把依赖的 desc 也找出来,在内存中构建一棵有向无环图 (DAG)
  • 利用拓扑排序(Topological Sort),计算出一个完美的安装顺序(必须先安装底层依赖,最后安装目标软件)。

2. 检查冲突(Transaction Preparation)

在真正动手之前,pacman 会做两项严格的检查:

  • 盘点磁盘文件冲突: 它会把所有即将安装的软件的 files 列表拿出来,和本地文件系统对比。如果发现某个文件已经存在,且不属于任何已安装的包(或者是别的包生成的),pacman 会立刻报错终止(报错提示:exists in filesystem),绝对不盲目覆盖。
  • 检查互相排斥(Conflicts): 比如你装了 openssl,另一个包声明了 conflicts = openssl,它会提示你是否替换。

3. 下载软件包(Downloading)

pacman 默认调用内建的下载函数(也可以在配置里改成 aria2curl),去镜像源下载对应的软件包。

  • Arch 的软件包后缀通常是 .pkg.tar.zst
  • 它其实就是一个用 zstd 高效压缩过的普通 tar 归档包。

4. 解压与释放(Extraction)

下载完成后,pacman 开始“解包”。

  • 它会按照计算好的顺序,直接把 .pkg.tar.zst 解压到系统的根目录 / 下。可执行文件直接落入 /usr/bin/,库文件落入 /usr/lib/
  • 同时,它会把这个包的元数据和文件列表写入 /var/lib/pacman/local/ 中,完成本地数据库的注册。

5. 运行挂钩(Pacman Hooks)

文件复制完后,系统还没完全准备好(比如新装了字体,系统还不知道)。这时 pacman 会触发 hooks(位于 /usr/share/libalpm/hooks/)。

  • 比如触发 ldconfig 刷新动态链接库缓存。
  • 触发 fontconfig 刷新字体缓存。
  • 触发 mkinitcpio 重新构建内核镜像(如果是更新了内核)。

三、 为什么 Arch 会发生“滚烂系统”的情况?

理解了原理,你就明白为什么 Arch 如果久不更新,一更新就容易“滚烂”(System Breakage)了:

  1. 激进的无版本隔离: pacman 不像 Debian 那样会保留旧版本的动态链接库(比如 libssl.so.1.1libssl.so.3 共存)。Arch 永远只有一个最新版。如果 A 软件依赖 libssl.so.3,而你更新时因为某些原因漏掉了 A,A 在运行时就会因为找不到旧库直接崩溃(shared object file not found)。
  2. 纯文本数据库的脆弱性: 如果你在 pacman 正在写入 /var/lib/pacman/local/ 时突然断电强行关机,数据库文件可能会损坏或变成 0 字节。此时 pacman 就会“失忆”,不知道本地装了什么,导致下次更新时疯狂报 exists in filesystem 冲突。

四、 总结:Pacman 的设计哲学

“Keep It Simple, Keep It Lightweight.”

pacman 的强大和高效,正是因为它没有二心:它不帮你做复杂的容器隔离,不搞花哨的图形界面驱动,它只做一件事——安全、快速地把一个远程的 tar 压缩包,解压到你的根目录下,并用纯文本账本记下来。 正因如此,它才能成为 Linux 社区中最快、最受极客喜爱的包管理器之一。