
🎉博主首页: 有趣的中国人
🎉专栏首页: 操作系统原理

本文一次性讲清:
软链接 / 硬链接 的本质区别,为什么目录硬链接会被限制;
静态库 / 动态库 的生成与链接命令,运行时如何找到.so;以及 ELF + 加载器 + mm_struct + PC 这条"从可执行文件到真正跑起来"的链路。
文章目录
- [1. 软链接 & 硬链接](#1. 软链接 & 硬链接)
-
- [1.1 基本命令](#1.1 基本命令)
- [1.2 硬链接:共享同一个 inode](#1.2 硬链接:共享同一个 inode)
-
- [✅ 非常重要:硬链接删除不等于数据消失](#✅ 非常重要:硬链接删除不等于数据消失)
- [1.3 软链接:单独 inode,存的是"路径字符串"(类似快捷方式)](#1.3 软链接:单独 inode,存的是“路径字符串”(类似快捷方式))
-
- [✅ 非常重要:软链接可以跨文件系统](#✅ 非常重要:软链接可以跨文件系统)
- [1.4 跨文件系统:为什么硬链接不行?](#1.4 跨文件系统:为什么硬链接不行?)
- [1.5 目录与硬链接:为什么一般禁止?](#1.5 目录与硬链接:为什么一般禁止?)
-
- [✅ 非常重要:目录硬链接的限制,本质是"避免路径环"](#✅ 非常重要:目录硬链接的限制,本质是“避免路径环”)
- [1.6 一张表总结软硬链接](#1.6 一张表总结软硬链接)
- [2. 静态库:`.a` 的本质与用法](#2. 静态库:
.a的本质与用法) -
- [2.1 静态库是什么?](#2.1 静态库是什么?)
- [2.2 如何生成静态库](#2.2 如何生成静态库)
- [2.3 如何使用静态库](#2.3 如何使用静态库)
-
- [✅ 非常重要:静态链接会"拷贝代码进可执行文件"](#✅ 非常重要:静态链接会“拷贝代码进可执行文件”)
- [3. 动态库:`.so` 的生成、链接与加载](#3. 动态库:
.so的生成、链接与加载) -
- [3.1 如何生成动态库(PIC + shared)](#3.1 如何生成动态库(PIC + shared))
- [3.2 链接动态库](#3.2 链接动态库)
-
- [✅ 非常重要:不加 `-static` 时,优先用 `.so`](#✅ 非常重要:不加
-static时,优先用.so)
- [✅ 非常重要:不加 `-static` 时,优先用 `.so`](#✅ 非常重要:不加
- [3.3 运行时怎么找到 `.so`?](#3.3 运行时怎么找到
.so?) -
- [方式 1:放到系统默认库目录](#方式 1:放到系统默认库目录)
- [方式 2:设置环境变量(临时)](#方式 2:设置环境变量(临时))
- [方式 3:写入配置并刷新缓存(更推荐永久)](#方式 3:写入配置并刷新缓存(更推荐永久))
- [方式 4:做软链接到系统路径(不推荐滥用)](#方式 4:做软链接到系统路径(不推荐滥用))
- [3.4 为什么动态库能省内存?](#3.4 为什么动态库能省内存?)
-
- [✅ 非常重要:共享的是"只读代码页",不是可写数据页](#✅ 非常重要:共享的是“只读代码页”,不是可写数据页)
- [4. ELF、加载器、mm_struct、PC:程序到底怎么跑起来?](#4. ELF、加载器、mm_struct、PC:程序到底怎么跑起来?)
-
- [4.1 ELF 描述了什么?](#4.1 ELF 描述了什么?)
- [4.2 exec 启动时发生什么(主流程)](#4.2 exec 启动时发生什么(主流程))
-
- [✅ 非常重要:PC 存的是"虚拟地址"](#✅ 非常重要:PC 存的是“虚拟地址”)
- [4.3 动态库是何时装载的?](#4.3 动态库是何时装载的?)
-
- [✅ 非常重要:动态库装载进入内存,同样受 OS 管理](#✅ 非常重要:动态库装载进入内存,同样受 OS 管理)
- [4.4 PIC 与"装载到哪儿都能跑"](#4.4 PIC 与“装载到哪儿都能跑”)
- [5. 总结](#5. 总结)
1. 软链接 & 硬链接
1.1 基本命令
bash
# 硬链接
ln file1 file1_hard
# 软链接(符号链接)
ln -s file1 file1_soft
1.2 硬链接:共享同一个 inode
硬链接的核心只有一句话:
硬链接 = 目录项增加一条 "name → inode" 映射,和原文件共享同一个 inode。
你可以用 ls -li 观察 inode:
bash
ls -li file1 file1_hard
会看到 inode 号一致,并且链接计数(link count)变大。
✅ 非常重要:硬链接删除不等于数据消失
rm file1的本质:删除一个目录项(少了一条 name→inode 的映射)- inode 内部有 链接计数:每多一个硬链接,计数 +1
- 只有计数变为 0,inode 才会被回收,data block 才会真正释放
结论:
只要还有任意一个硬链接存在,文件内容就还在。
1.3 软链接:单独 inode,存的是"路径字符串"(类似快捷方式)
软链接更像"跳转":
软链接本身是一个独立文件(有自己的 inode),内容是目标文件的路径字符串。
所以:
ls -li会看到软链接 inode 和目标 inode 不同- 访问软链接时,内核会先读出路径,再做一次路径解析找到目标 inode
✅ 非常重要:软链接可以跨文件系统
因为软链接存的是 路径字符串,不依赖 inode 编号所在分区。
1.4 跨文件系统:为什么硬链接不行?
硬链接不能跨文件系统(不能跨分区/设备)
原因很简单:
- inode 编号只在本分区有意义
- A 分区的 inode=1234,到了 B 分区完全不对应同一个对象
- 所以硬链接无法把"另一个文件系统里的 inode"拿来复用
1.5 目录与硬链接:为什么一般禁止?
目录结构本质上希望是"树",但目录硬链接会让它变成"图",容易出现环。
- 目录里天生存在
.和..(就是系统层面维护的链接关系) - 如果允许用户随意给目录做硬链接,就可能形成环形路径
- 一旦形成环,
find、备份、遍历、回收都会出现死循环或语义混乱
✅ 非常重要:目录硬链接的限制,本质是"避免路径环"
1.6 一张表总结软硬链接
| 对比项 | 硬链接(hard link) | 软链接(symbolic link) |
|---|---|---|
| inode 是否相同 | ✅ 相同(共享 inode) | ❌ 不同(有自己的 inode) |
| 存的是什么 | 真实数据(同一份) | 目标路径字符串 |
| 跨文件系统 | ❌ 不支持 | ✅ 支持 |
| 目标被删后 | ✅ 仍能访问(只要计数>0) | ❌ 变成悬空链接 |
| 目录支持 | 一般禁止(防环) | ✅ 可指向目录 |
2. 静态库:.a 的本质与用法
2.1 静态库是什么?
静态库 = 把多个
.o目标文件打包成一个归档文件(archive)命名约定:
libxxx.a
2.2 如何生成静态库
bash
# 1) 先把源文件编译成 .o
gcc -c a.c b.c c.c
# 2) 再用 ar 打包成静态库
ar -rc libmylib.a a.o b.o c.o
2.3 如何使用静态库
bash
gcc main.c -I/path/include -L/path/lib -lmylib -o main
-I:头文件路径(编译阶段)-L:库路径(链接阶段)-lxxx:链接libxxx.a或libxxx.so
✅ 非常重要:静态链接会"拷贝代码进可执行文件"
链接时,链接器会把需要的那部分 .o 从 .a 里抽出来合并进最终的 main:
- 优点:运行时不依赖
.a - 缺点:可执行文件变大;多个程序各拷贝一份库代码,内存也更占
3. 动态库:.so 的生成、链接与加载
3.1 如何生成动态库(PIC + shared)
bash
# 1) 生成位置无关代码(PIC)的 .o
gcc -fPIC -c a.c b.c c.c
# 2) 打包成共享库 .so
gcc -shared -o libmylib.so a.o b.o c.o
-fPIC:位置无关代码(Position Independent Code)-shared:生成共享库
3.2 链接动态库
bash
gcc main.c -I/path/include -L/path/lib -lmylib -o main
✅ 非常重要:不加 -static 时,优先用 .so
常见规则:
-
若同时存在
libmylib.so和libmylib.a
默认优先链接.so -
想强制静态链接:
bashgcc main.c -static -L/path/lib -lmylib -o main_static
3.3 运行时怎么找到 .so?
动态库是"运行时装载",所以必须能找到 .so。
常用方式(从临时到永久):
方式 1:放到系统默认库目录
/lib、/usr/lib、/usr/local/lib
方式 2:设置环境变量(临时)
bash
export LD_LIBRARY_PATH=/your/so/path:$LD_LIBRARY_PATH
./main
方式 3:写入配置并刷新缓存(更推荐永久)
-
新建配置文件:
/etc/ld.so.conf.d/mylib.conf- 内容写一行你的库路径:
/your/so/path
-
刷新:
bashsudo ldconfig
方式 4:做软链接到系统路径(不推荐滥用)
bash
sudo ln -s /your/so/path/libmylib.so /usr/lib/libmylib.so
3.4 为什么动态库能省内存?
动态库的代码段(只读)可以被多个进程共享到同一份物理页。
- 每个进程 VA 中会映射一段
.so的代码区 - 页表可以让它们指向同一份物理内存
- 可写数据(全局变量、静态变量)一般不会共享,会各自一份
✅ 非常重要:共享的是"只读代码页",不是可写数据页
4. ELF、加载器、mm_struct、PC:程序到底怎么跑起来?
4.1 ELF 描述了什么?
ELF 中有程序头(Program Header),描述:
- 哪些段需要装载到内存(
PT_LOAD) - 每段在文件里的偏移/大小
- 每段映射到进程虚拟地址空间的 VA
- 权限(R/W/X)
结论:
ELF 里写的是"应该映射到的虚拟地址布局",并不是最终物理地址。
4.2 exec 启动时发生什么(主流程)
当执行 ./main 时,内核大致做:
- 创建新的地址空间描述(
mm_struct) - 按 ELF 描述把
.text/.data/.bss等映射进 VA - 初始化堆、栈等区域
- 设置入口点:把 CPU 的 PC(
EIP/RIP)指向 ELF 的entry - 切回用户态执行
✅ 非常重要:PC 存的是"虚拟地址"
CPU 取指时会经过 MMU:
VA →(查页表)→ PA → 取指执行
4.3 动态库是何时装载的?
动态链接程序会依赖动态链接器(loader),典型是:
/lib64/ld-linux-x86-64.so.2(不同架构不同)
流程概括:
- 主程序 ELF 装载后,发现存在动态段(
.dynamic) - loader 读取依赖库列表(
DT_NEEDED) - 为每个
.so找一段 VA 区域进行映射 - 建立
link_map等结构记录"已装载哪些库、基地址是多少" - 处理重定位(relocation),修正符号地址(GOT/PLT 等)
- 最终跳转到
main开始执行
✅ 非常重要:动态库装载进入内存,同样受 OS 管理
映射到进程的 VA 空间,并由页表完成 VA→PA。
4.4 PIC 与"装载到哪儿都能跑"
为什么动态库常要求 -fPIC?
位置无关代码让库不依赖固定装载地址,装载到不同 VA 也能通过相对方式/重定位正确运行。
这也使得多个进程可以:
- 每个进程映射到自己的 VA(可能不同)
- 但仍能共享同一份物理代码页(只读)
5. 总结
- 硬链接:多个目录项指向同一 inode;link count=0 才释放数据;不能跨文件系统;目录硬链接一般禁止防环。
- 软链接:独立 inode;内容是路径字符串;可跨文件系统;源文件删了会悬空。
- 静态库
.a:.o打包;链接时拷贝进可执行文件;文件更大但运行不依赖库。 - 动态库
.so:运行时装载;默认优先.so;用LD_LIBRARY_PATH/ldconfig解决找不到库;代码段可共享物理页。 - ELF + loader:ELF 描述 VA 布局;PC 取的是 VA;MMU 查页表得到 PA;动态库通过 loader 映射并重定位后再执行 main。