爆笑教程 《看表情包学Linux》 猛戳订阅
写在前面:上一章我们讲解了 inode,为文件系统收了尾,这几章我们充分地讲解完了文件系统的知识点,现在我们开始开始学习软硬链接了。如果没有文件系统的铺垫,想直接理解软硬链接难免有些困难。但我们讲完了文件系统再去理解软硬链接,你就会发现没有那么难,因为我们是从底层开始,向上去学习的!让我们开始吧。
本章目录:
Ⅰ. 软硬链接
0x00 Linux 下的快捷方式:软链接
0x01 创建软链接
0x02 创建硬链接
0x03 软硬链接的删除
0x04 硬连接数
0x05 思考一些问题
Ⅱ. 动静态库
0x00 引入:什么是动静态库?
0x01 动态链接(Dynamic Linking)
0x02 为什么需要静态库?
0x03 生成静态库:ar -rc
0x03 生成动态库
0x04 同时生成动态库与静态库
0x05 使用静态库和动态库
上一章我们介绍完了 inode ,我们再回顾一下 元数据:
七列,分别是模式、软硬连接数、文件所有者、组、大小、最后修改时间和文件名。
上图中,红色圈出的就是 软硬连接数 了,我们刚才说了,可以使用 stat 文件名查看更多:
我们可以看到,我们的 mytest.c 文件的软硬连接数是 1。
软硬链接的区别:
我们可以通过如下命令,创建一个文件的软硬链接:
$ ln -s 文件名 链接文件名 # 创建软连接
$ ln 文件名 链接文件名 # 创建硬链接
(下面我们先来讲软连接的创建,再讲硬链接的创建)
我们创建一个软连接,可以使用下面的指令:
$ ln -s 文件名 链接文件名 # 创建软连接
比如我们创建一个 my.txt 文件,我们像创建一个 my.txt 文件的软链接,我们可以:
这就是软连接,my.txt 和 my.txt.soft 的 inode 是不同的:
下面我们来举一个实际的例子来体验软链接有什么实际的用途:
代码演示:vim mytest.c
#include
int main(void) {
printf("hello, soft link...\n");
printf("hello, soft link...\n");
printf("hello, soft link...\n");
printf("hello, soft link...\n");
printf("hello, soft link...\n");
printf("hello, soft link...\n");
return 0;
}
运行结果如下:
程序正常运行,这里我们在 d1/d2/d3 下直接 ./mytest.exe 就可以运行。
但是,如果我们如果想在外面运行这个程序就会很累,因为它的路径有点深:
太麻烦了,所以这里我们就可以给它建立一个软连接,解脱双手:
$ ln -s ./d1/d2/d3/mytest.exe my.exe
这是不是有点像 Windows 下的 快捷方式?没错!
" 软链接就是 Linux 下的快捷方式 "
上面我们演示的是让软链接链接一个可执行程序,未来我们可以用它来链接头文件、库文件,动静态库,这样就可以不需要让我们冗余的在去某些地方找这些库了。
对我们来说,硬链接是什么呢?硬链接其实非常简单!我们创建一个硬链接:
$ ln 文件名 链接文件名 # 创建硬链接
my.txt 和 my.txt.hard 映射的是同一个 inode:
硬链接就是单纯的在 Linux 指定的目录下,给指定的文件新增文件名和 inode 编号的映射关系!
删除的话可以直接 rm,但是我们还是建议使用专门的 取消链接 的指令:unlink
$ unlink 链接文件名 # 取消链接
举个例子,我们把刚才创建的软链接和硬链接用 unlink 把它们扬了:
这个 unlink 就是用来取消链接的,但它也可以用来删文件。
我们先打道回府,重新创建一个硬链接,然后我们重点观察一下下面的 "数字":
我们可以再多建立几个硬链接,你可以看到这个数字的变化:
❓ 什么是硬链接数?
你看这个 inode 编号,是不是有点像指针的概念?
硬链接本质就是该文件 inode 属性中的一个计数器 count。用来标识就几个文件名和我的 inode 建立了映射关系。简而言之,就是有自己文件名指向我的 inode (文件本身) 。
" 软链接就是 Linux 下的快捷方式 "
既然是一个独立的文件,inode 是独立的,软连接的文件内容保存的是指向文件的所在路径。
❓ 思考:为什么创建普通文件,硬链接数默认是 1 ?
因为 普通文件的文件名本身就和自己的 inode 具有映射关系,而且只有一个!
所以默认的硬链接数为 1。那为什么目录是 2 呢 ?
我们知道,任意一个目录一定存在一个点或两个点: . ..
那么 ./ 为什么表示的是当前路径呢?因为 . 表示的就是 mydir,当前所处的路径!
默认一个空目录创建一个 自己的名字 和 一个点,所以两个文件名指向它,所以是 2。
那么 .. 又是什么呢?.. 指向的是上级路径!
这就是为什么我们 cd .. 可以回到上级目录的原因,因为它可以指向上级目录。
动静态库 —— 即 动态库 (Dynamic Library) 与 静态库 (Static Library) 。
下面我们来分别介绍一下这两种库:
① 动态库 .so:程序在运行的时才去链接动态库的代码,多个程序共享使用库的代码。
② 静态库 .a:程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为 动态链接 (dynamic linking) 。
动态库可以在多个程序间共享,所以 动态链接使得可执行文件更小,节省了磁盘空间。 操作系统采用虚拟内存 (VM) 机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
测试程序:
/add.h/
#ifndef __ADD_H__
#define __ADD_H__
int add(int a, int b);
#endif // __ADD_H__
/add.c/
#include "add.h"
int add(int a, int b)
{
return a + b;
}
/sub.h/
#ifndef __SUB_H__
#define __SUB_H__
int sub(int a, int b);
#endif // __SUB_H__
/add.c/
#include "add.h"
int sub(int a, int b)
{
return a - b;
}
///main.c
#include
#include "add.h"
#include "sub.h"
int main(void)
{
int a = 10;
int b = 20;
printf("add(10, 20)=%d\n", a, b, add(a, b));
a = 100;
b = 20;
printf("sub(%d,%d)=%d\n", a, b, sub(a, b));
}
我们先站在设计库的工程师的角度,学如何形成静态库。
我们直接实操式地讲解,下面我们会写一段简单的、包含头文件和源文件的代码。
代码演示:mymath.h
#include
#include
/* [from, to] -> 累加 -> result -> return */
extern int addToVal(int form, int to);
代码演示:mymath.c
#include "mymath.h"
int addToVal(int from, int to) {
assert(from <= to);
int result = 9;
int i = 0;
for (i = from; i <= to; i++) {
result += i;
}
return result;
}
这是一个再简单不过的实现累加功能的代码,现在,我们想把这个小功能制作成库给人用。
❓ 思考:库里面需不需要 main 函数?
我们可不敢在库里带 main 函数,用户到时候会写 main 函数的,带了编译出现冲突就不好了。
所以库里面不要带 main 函数,也不需要带 main 函数。
代码演示:test.cpp
#include "mymath.h"
int main(void)
{
int from = 10;
int to = 20;
int result = addToVal(from, to);
printf("result = %d\n", result);
}
编译结果如下:
下面我们来形成一个静态库。我们先用命令行来写一下。
目前只有 2 个,不够丰富,所以我们再添加一个 myprint 功能,打印时间。
代码演示:myprintf.h
#include
#include
extern void Print(const char* msg);
代码演示:myprintf.c
#include "myprint.h"
void Print(const char* msg) {
printf("%s : %lld\n", msg, (long long)time(NULL));
}
现在我们有两组方法,一个是累加一个是打印时间,我们想把它们打包成库。
首先,我们将所有的 .c 文件翻译成 .o 文件:
mymath.o : mymath.c
gcc -c mymath.c -o mymath.o
myprint.o : myprint.c
gcc -c myprint.c -o myprint.o
.PHONY : clean
clean:
rm -f *.o
我们知道,链接 就是把所有的 .o 链接形成一个可执行程序。
❓ 思考:如果我把所有的 .o 给别人,别人能链接使用吗?可以!
只需要把程序变为 .o 就可以让被人链接用起来了,但是我们 .o 如果很多这会带来不方便。
所以我们给这些 .o 做一个 "打包",这就是静态库的作用。
下面我来学习如何形成静态库:
$ ar -rc [静态库] [.o]
ar 是 gnu 归档工具,rc 的意思是 replace and create (把原来打包的 .o 替换下)。
库的命名以 lib 开头,静态库以 .a 结尾,我们写进 Makefile:
libmymath.a : mymath.o myprint.o
ar -rc libmymath.a mymath.o myprint.o
mymath.o : mymath.c
gcc -c mymath.c -o mymath.o
myprint.o : myprint.c
gcc -c myprint.c -o myprint.o
.PHONY : clean
clean:
rm -f *.o *.a # 删除.a文件
此时我们就有了静态库,所谓了静态库就是曾经的源文件最终将它翻译成 .o 打包起来的东西而已。而别人用我们的库,就是在库里找到 .o 然后丢到而可执行程序里就行。
clean 部分我们把 *.a 加进去就行了,这样我们就可以 make clean 了:
现在,我们的 libmymath.a 就生成出来了,下面我们要做的是发布:
libmymath.a : mymath.o myprint.o
ar -rc libmymath.a mymath.o myprint.o
mymath.o : mymath.c
gcc -c mymath.c -o mymath.o
myprint.o : myprint.c
gcc -c myprint.c -o myprint.o
.PHONY : static
static:
mkdir -p lib-static/lib
mkdir -p lib-static/include
cp *.a lib-static/lib
cp *.h lib-static/include
.PHONY : clean
clean:
rm -f *.o *.a libmypath.a # 删除
结果如下:
动态库比静态库要复杂一些,在形成时原理跟静态库基本是一样的。
gcc -shared
区别在于 形成 .o 的时候是需要加上 gcc -fPIC 的,这是为了产生 与位置无关码。
libmymath.so : mymath.o myprint.o
gcc -shared -o libmymath.so mymath.o myprint.o
mymath.o : mymath.c
gcc -fPIC -c mymath.c -o mymath.o
myprint.o : myprint.c
gcc -fPIC -c myprint.c -o myprint.o
.PHONY:clean
clean:
rm -f *.o *.so
结果如下:
此时我们 make 的时候就会先根据 gcc -fPIC 形成与位置无关的 .o,
然后通过 gcc -shared 的选项生成 .so 文件,此时就有了动态库。
动态库的交付:
libmymath.so : mymath.o myprint.o
gcc -shared -o libmymath.so mymath.o myprint.o
mymath.o : mymath.c
gcc -fPIC -c mymath.c -o mymath.o
myprint.o : myprint.c
gcc -fPIC -c myprint.c -o myprint.o
.PHONY:dyl
dyl:
mkdir -p lib-dyl/lib
mkdir -p lib-dyl/include
cp *.so lib-dyl/lib
cp *.h lib-dyl/include
.PHONY:clean
clean:
rm -f *.o *.so dyl
结果如下:
那我们直接把两个 Makefile 合到一起看看:
.PHONY:all
all: libmymath.so libmymath.a
# 动态库
libmymath.so : mymath.o myprint.o
gcc -shared -o libmymath.so mymath.o myprint.o
mymath.o : mymath.c
gcc -fPIC -c mymath.c -o mymath.o
myprint.o : myprint.c
gcc -fPIC -c myprint.c -o myprint.o
# 静态库
libmymath.a : mymath.o myprint.o
ar -rc libmymath.a mymath.o myprint.o
mymath.o : mymath.c
gcc -c mymath.c -o mymath.o
myprint.o : myprint.c
gcc -c myprint.c -o myprint.o
# 发布
.PHONY : lib
lib:
mkdir -p lib-static/lib
mkdir -p lib-static/include
cp *.a lib-static/lib
cp *.h lib-static/include
mkdir -p lib-dyl/lib
mkdir -p lib-dyl/include
cp *.so lib-dyl/lib
cp *.h lib-dyl/include
# 清理
.PHONY : clean
clean:
rm -f *.o *.a lib
这样是行的,都要形成 .o,到底是位置有关还是位置无关?最终带来的结果就是不一样。
所以名字要区分开来,你生成你的,我生成我的:
.PHONY:all
all: libmymath.so libmymath.a
libmymath.so : mymath.o myprint.o
gcc -shared -o libmymath.so mymath.o myprint.o
mymath.o : mymath.c
gcc -fPIC -c mymath.c -o mymath.o
myprint.o : myprint.c
gcc -fPIC -c myprint.c -o myprint.o
libmymath.a : mymath_s.o myprint_s.o
ar -rc libmymath.a mymath_s.o myprint_s.o
mymath_s.o : mymath.c
gcc -c mymath.c -o mymath_s.o
myprint_s.o : myprint.c
gcc -c myprint.c -o myprint_s.o
.PHONY : lib
lib:
mkdir -p lib-static/lib
mkdir -p lib-static/include
cp *.a lib-static/lib
cp *.h lib-static/include
mkdir -p lib-dyl/lib
mkdir -p lib-dyl/include
cp *.so lib-dyl/lib
cp *.h lib-dyl/include
.PHONY : clean
clean:
rm -f *.o *.a *.so lib
运行结果:
这样,就既有动态库也有静态库了。
现在我们站在使用的人的角度,学习如何使用静态库和动态库。
代码演示:
#include "mymath.h"
#include "myprint.h"
int main()
{
int start = 0;
int end = 0;
int result = addToVal(start, end);
printf("result: %d\n", result);
Print("Hello, World!");
return 0;
}
代码运行:gcc mytest.c
此时必然是报错的,这样头文件是找不到的。我们来回顾一下头文件的搜索路径:
① 在当前路径下查找头文件
② 在系统路径下查找头文件
我们自己写的库当前的头文件一定不在当前的系统中,你当前的头文件不在当前路径下!
它既不在当前路径,也不在头文件中,这自然是找不到头文件的。
谁在找头文件?编译器在找。系统中的头文件一般在 lib64 路径下,会存着大量的动静态库。
第一种做法:将自己的头文件和库文件拷贝到系统路径下即可。
gcc -l 指定我要链接的库的名称
我们还可以指定头文件搜索路径:
$ gcc mytest.c -o mytest -I ./lib-static/include/
此时链接还是失败的。
$ gcc mytest.c -o mytest -I ./lib-static/include/ -L ./lib-static/lib/ -lmymath
此时就形成了 mytest。
-Ⅰ 表示我们的头文件查找的路径
-L 表示库文件搜索的路径
-l 在-L 指定的路径下你要链接哪一个库
动态库:
gcc mytest.c -o mytest -I lib-dyl/include/ -L lib-dyl/lib/ -lmymath
形成可执行程序之后,已经把需要的代码拷贝到我的代码中,运行时不依赖你的库。不需要运行时查找。
为什么动态库会有这个问题?想办法让进程找到动态库即可。
error while loading shared libraries 解决方案:
① 动态库拷贝到系统路径下 /lib64 安装。
② 通过导入环境变量的方式 —— 程序运行的时候,会在环境变量中查找自己需要的动态库路径 —— LD_LIBRARY_PATH。
③ 系统配置文件来做。
[ 笔者 ] 王亦优
[ 更新 ] 2023.7.20
❌ [ 勘误 ] /* 暂无 */
[ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!
参考资料 C++reference[EB/OL]. []. http://www.cplusplus.com/reference/. Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. . 百度百科[EB/OL]. []. https://baike.baidu.com/. 比特科技. Linux[EB/OL]. 2021[2021.8.31 xi |