Linux 文件系统与库开发
重点
- 基础:inode 与 Linux 文件系统模型
- 文件引用:硬链接 vs 软链接
- 代码复用:静态库(.a)与动态库(.so)
1. 基础:inode 与 Linux 文件系统模型
1.1 核心概念
Linux 文件系统中,文件名 与文件本体是分离的,核心组成如下:
- inode:文件的唯一标识(数字编号),存储文件元数据(权限、属主、时间戳、数据块指针),不包含文件名。同一文件系统内 inode 号唯一。
- 目录项:目录中的条目,存储「文件名 + 对应 inode 号」,是用户访问文件的入口。
- 引用计数:inode 内置计数器,记录指向该 inode 的目录项数量,计数为 0 时,文件数据才会被内核真正删除(释放磁盘空间)。
1.2 实操示例:查看 inode 与文件信息
步骤1:创建测试文件
bash
echo "inode test content" > test_inode.txt
步骤2:查看 inode 号与文件详情
使用 ls -li 命令(-i 显示 inode 号,-l 显示文件元数据):
bash
ls -li test_inode.txt
输出结果(解读)
1234567 -rw-r--r-- 1 root root 20 Sep 10 10:00 test_inode.txt
- 第一列
1234567:该文件的 inode 号 - 第五列
1:引用计数(当前只有 1 个目录项指向该 inode) - 后续列:文件权限、属主、大小、修改时间、文件名(目录项内容)
步骤3:验证「文件名不影响文件本体」
bash
# 重命名文件(仅修改目录项,不改变 inode 和文件数据)
mv test_inode.txt test_inode_rename.txt
# 再次查看 inode,与之前一致
ls -li test_inode_rename.txt
2. 文件引用:硬链接 vs 软链接
2.1 核心对比表
| 对比维度 | 硬链接(Hard Link) | 软链接(Symbolic Link) |
|---|---|---|
| 本质 | 同一 inode 的多个目录项(别名) | 独立文件,存储目标文件路径(类似快捷方式) |
| inode | 与源文件完全相同 | 拥有独立 inode,与源文件不同 |
| 跨文件系统 | 不支持 | 支持 |
| 链接目录 | 不支持(系统禁止,防止目录循环) | 支持 |
| 源文件删除 | 链接仍可用,文件数据保留(引用计数>0) | 链接失效,变为「死链接」 |
| 引用计数 | 增加源文件 inode 引用计数 | 不影响源文件引用计数 |
| 创建命令 | ln 源文件 硬链接名 |
ln -s 源文件 软链接名 |
2.2 硬链接:实操示例
步骤1:创建硬链接
bash
# 1. 创建源文件
echo "hard link test content" > source_hard.txt
# 2. 创建硬链接
ln source_hard.txt hard_link.txt
# 3. 查看 inode 与引用计数(二者 inode 相同,引用计数变为 2)
ls -li source_hard.txt hard_link.txt
步骤2:验证硬链接特性
bash
# 1. 修改硬链接,源文件同步更新(共享数据块)
echo "append content to hard link" >> hard_link.txt
cat source_hard.txt
# 2. 删除源文件,硬链接仍可正常访问(引用计数变为 1,未归 0)
rm source_hard.txt
cat hard_link.txt
# 3. 查看硬链接引用计数(此时为 1)
ls -li hard_link.txt
2.3 软链接:实操示例
步骤1:创建软链接
bash
# 1. 创建源文件
echo "soft link test content" > source_soft.txt
# 2. 创建软链接(-s 选项指定创建软链接)
ln -s source_soft.txt soft_link.txt
# 3. 查看(文件类型为 l,有 -> 标识指向源文件,inode 与源文件不同)
ls -li source_soft.txt soft_link.txt
步骤2:验证软链接特性
bash
# 1. 访问软链接,读取源文件内容
cat soft_link.txt
# 2. 删除源文件,软链接失效(变为死链接)
rm source_soft.txt
cat soft_link.txt # 报错:No such file or directory
# 3. 验证目录软链接(支持目录链接)
mkdir source_dir
ln -s source_dir soft_link_dir
ls -l soft_link_dir/
# 4. 查看软链接真实路径(readlink 命令)
readlink soft_link_dir
2.4 常见坑点:软链接删除注意事项
bash
# 错误:加 / 会删除源目录内的文件,而非软链接本身
rm -rf soft_link_dir/
# 正确:直接删除软链接文件名,不添加 /
rm -rf soft_link_dir
3. 代码复用:静态库(.a)与动态库(.so)
3.1 核心对比表
| 对比维度 | 静态库(.a) | 动态库(.so) |
|---|---|---|
| 后缀 | Linux:.a;Windows:.lib | Linux:.so;Windows:.dll |
| 链接时机 | 编译时(链接阶段) | 运行时 |
| 代码复制 | 复制库中有用代码到可执行文件 | 不复制代码,仅记录库引用信息 |
| 可执行文件体积 | 较大(包含库代码) | 较小(仅包含引用信息) |
| 运行依赖性 | 无依赖,可独立运行 | 依赖动态库,缺失则无法启动 |
| 库更新影响 | 库更新后,需重新编译整个项目 | 兼容接口下,直接替换动态库即可,无需重新编译 |
| 核心命令 | 编译:gcc -c;打包:ar rcs |
编译:gcc -c -fPIC;打包:gcc -shared |
3.2 准备工作:基础代码(数学工具库)
创建 3 个核心文件,实现加法和减法功能:
1. 头文件:math_utils.h(暴露函数声明)
c
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
// 加法函数声明
int add(int a, int b);
// 减法函数声明
int sub(int a, int b);
#endif // MATH_UTILS_H
2. 加法实现:add.c
c
#include "math_utils.h"
int add(int a, int b) {
return a + b;
}
3. 减法实现:sub.c
c
#include "math_utils.h"
int sub(int a, int b) {
return a - b;
}
4. 测试程序:main.c(使用库的代码)
c
#include <stdio.h>
#include "math_utils.h"
int main() {
int a = 10, b = 5;
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", sub(a, b));
return 0;
}
3.3 静态库(.a):创建与使用
步骤1:编译生成目标文件(.o)
bash
# -c:只编译不链接,生成目标文件
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
# 查看生成的目标文件
ls *.o
步骤2:打包为静态库(.a)
使用 ar rcs 命令(r:插入文件;c:创建归档;s:创建索引):
bash
# 格式:ar rcs 静态库名.a 目标文件1.o 目标文件2.o
ar rcs libmath.a add.o sub.o
# 查看生成的静态库
ls libmath.a
步骤3:链接静态库,生成可执行文件
两种链接方式,效果一致:
bash
# 方式1:直接指定静态库文件
gcc main.c libmath.a -o main_static
# 方式2:-L 指定库路径,-l 指定库名(省略 lib 前缀和 .a 后缀)
gcc main.c -lmath -L./ -o main_static
步骤4:运行与验证(静态库独立性)
bash
# 赋予执行权限
chmod +x main_static
# 运行可执行文件
./main_static
# 验证:删除静态库和目标文件,可执行文件仍能正常运行
rm libmath.a *.o
./main_static
输出结果
a + b = 15
a - b = 5
3.4 动态库(.so):创建与使用
步骤1:编译生成位置无关目标文件(.o)
动态库要求位置无关代码(PIC),使用 -fPIC 选项:
bash
gcc -c add.c -o add.o -fPIC
gcc -c sub.c -o sub.o -fPIC
# 查看目标文件
ls *.o
步骤2:打包为动态库(.so)
使用 gcc -shared 选项,指定生成动态共享库:
bash
# 格式:gcc -shared -o 动态库名.so 目标文件1.o 目标文件2.o
gcc -shared -o libmath.so add.o sub.o
# 查看生成的动态库
ls libmath.so
步骤3:链接动态库,生成可执行文件
bash
# 方式1:直接指定动态库文件
gcc main.c libmath.so -o main_dynamic
# 方式2:-L 指定库路径,-l 指定库名
gcc main.c -lmath -L./ -o main_dynamic
步骤4:运行与解决动态库加载问题
直接运行会报错(系统无法找到当前目录的动态库),提供 3 种解决方案:
方案1:临时设置环境变量 LD_LIBRARY_PATH(当前终端有效)
bash
# 添加当前目录到动态库搜索路径
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
# 运行可执行文件
./main_dynamic
方案2:永久复制到系统默认库路径(系统级有效)
bash
# 复制动态库到 /usr/lib(需要 root 权限)
sudo cp libmath.so /usr/lib
# 刷新系统库缓存
sudo ldconfig
# 运行可执行文件
./main_dynamic
方案3:编译时绑定运行时库路径(永久绑定到可执行文件)
bash
# 重新编译,指定运行时动态库搜索路径为当前目录
gcc main.c -lmath -L./ -Wl,-rpath=./ -o main_dynamic
# 直接运行
./main_dynamic
步骤5:验证动态库依赖性
bash
# 删除动态库
rm libmath.so
# 未绑定 rpath/未复制到系统路径时,运行报错
./main_dynamic # 报错:无法找到 libmath.so
输出结果
a + b = 15
a - b = 5
3.5 实用辅助命令
- 查看可执行文件依赖的动态库:
ldd main_dynamic - 查看库中的符号(函数):
nm libmath.a(静态库)、nm libmath.so(动态库) - 动态库版本管理:创建软链接切换版本(如
ln -s libmath.so.1.0.0 libmath.so)
核心知识点总结
- inode 是文件唯一标识,存储元数据,文件名仅为目录项中的别名,重命名/硬链接不改变 inode。
- 硬链接是同一 inode 的多别名,无路径依赖;软链接是独立文件,存储路径,支持跨文件系统和目录。
- 静态库编译时嵌入可执行文件,独立运行但体积大;动态库运行时加载,可共享但存在依赖。
- 库链接核心命令:
-L指定库路径,-l指定库名,动态库生成需-shared和-fPIC。