【操作系统原理】软硬链接与动静态库


🎉博主首页: 有趣的中国人

🎉专栏首页: 操作系统原理

🎉其它专栏: C++初阶 | C++进阶 | 初阶数据结构

本文一次性讲清:
软链接 / 硬链接 的本质区别,为什么目录硬链接会被限制;
静态库 / 动态库 的生成与链接命令,运行时如何找到 .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)
    • [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.alibxxx.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.solibmylib.a
    默认优先链接 .so

  • 想强制静态链接:

    bash 复制代码
    gcc 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:写入配置并刷新缓存(更推荐永久)

  1. 新建配置文件:

    • /etc/ld.so.conf.d/mylib.conf
    • 内容写一行你的库路径:/your/so/path
  2. 刷新:

    bash 复制代码
    sudo 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 时,内核大致做:

  1. 创建新的地址空间描述(mm_struct
  2. 按 ELF 描述把 .text/.data/.bss 等映射进 VA
  3. 初始化堆、栈等区域
  4. 设置入口点:把 CPU 的 PC(EIP/RIP)指向 ELF 的 entry
  5. 切回用户态执行

✅ 非常重要:PC 存的是"虚拟地址"

CPU 取指时会经过 MMU:

VA →(查页表)→ PA → 取指执行


4.3 动态库是何时装载的?

动态链接程序会依赖动态链接器(loader),典型是:

  • /lib64/ld-linux-x86-64.so.2(不同架构不同)

流程概括:

  1. 主程序 ELF 装载后,发现存在动态段(.dynamic
  2. loader 读取依赖库列表(DT_NEEDED
  3. 为每个 .so 找一段 VA 区域进行映射
  4. 建立 link_map 等结构记录"已装载哪些库、基地址是多少"
  5. 处理重定位(relocation),修正符号地址(GOT/PLT 等)
  6. 最终跳转到 main 开始执行

✅ 非常重要:动态库装载进入内存,同样受 OS 管理

映射到进程的 VA 空间,并由页表完成 VA→PA。


4.4 PIC 与"装载到哪儿都能跑"

为什么动态库常要求 -fPIC

位置无关代码让库不依赖固定装载地址,装载到不同 VA 也能通过相对方式/重定位正确运行。

这也使得多个进程可以:

  • 每个进程映射到自己的 VA(可能不同)
  • 但仍能共享同一份物理代码页(只读)

5. 总结

  1. 硬链接:多个目录项指向同一 inode;link count=0 才释放数据;不能跨文件系统;目录硬链接一般禁止防环。
  2. 软链接:独立 inode;内容是路径字符串;可跨文件系统;源文件删了会悬空。
  3. 静态库 .a.o 打包;链接时拷贝进可执行文件;文件更大但运行不依赖库。
  4. 动态库 .so :运行时装载;默认优先 .so;用 LD_LIBRARY_PATH/ldconfig 解决找不到库;代码段可共享物理页。
  5. ELF + loader:ELF 描述 VA 布局;PC 取的是 VA;MMU 查页表得到 PA;动态库通过 loader 映射并重定位后再执行 main。
相关推荐
KingRumn3 小时前
Linux进程间通信之消息队列(POSIX)实现篇
linux·服务器
loosed3 小时前
ubuntu navicat17连接本机msyql8 /run/mysqld/mysqld.sock问题
linux·mysql·ubuntu·adb
小猪佩奇TONY4 小时前
Linux 内核学习(13) --- linux 内核并发与竞态
linux·服务器·学习
倔强的石头1064 小时前
Linux 进程深度解析(四):环境变量 —— 进程的“环境 DNA”
linux·运维·服务器
牛奶咖啡134 小时前
在Linux中搭建本地yum/dnf仓库
linux·搭建yum/dnf本地仓库·添加rpm文件到yum仓库·添加rpm文件到dnf仓库·生成仓库索引·测试本地搭建的yum仓库·搭建http服务并开启目录浏览
大聪明-PLUS4 小时前
优雅的操作系统开发:用现代 C++ 编写操作系统内核(不使用宏)。第一部分——HAL 为王。
linux·嵌入式·arm·smarc
qq_455760854 小时前
Docker - 镜像
linux·运维·docker
m0_534875054 小时前
Ditto局域网同步功能实现宿主机和VMware虚拟机之间的复制粘贴共享
linux·运维·服务器
RisunJan4 小时前
Linux命令-hdparm命令(获取和设置硬盘参数)
linux·运维·服务器