目录
1.软硬链接
1.1软硬链接的语法
1.2理解软硬链接
1.3目录文件的硬链接
1.4应用场景
1.5ACM时间
2.动静态库
2.1认识库
3.制作静态库
3.1静态库打包
3.2静态库的使用
4.制作动态库
4.1动态库打包
4.2动态库的链接使用
4.3动态库的链接原理
总结:
前言:
在学习动静态库知识之前,我们先理解一下软硬链接的知识,本章节我们需要使用上一章节的inode知识,配合软硬链接制作动静态库。
1.软硬链接
1.1软硬链接的语法
语法:ln 【选项】【源文件】【新建链接】
对文件进行软硬链接非常简单,只需要通过 ln -s
或 ln
对文件进行链接即可,生成的链接文件类型为 l
(普通文件为 -
,目录文件为 d
)
对目录下的test2.txt进行软连接
对目录下的file.txt进行硬链接
解除链接语法
unlink 【链接】
注意:
目录可以进行软连接但是无法进行硬链接,需要结合链接原理配合inode知识进行讲解,会在下面进行介绍。
1.2理解软硬链接
inode和文件是一一对应的关系,但是从下图我们清晰的看到硬链接之后,两个文件的inode都是1706050 ,软连接之后,产生两个inode ;可以肯定的知道硬链接之后,没有创建新的文件,软连接是创建新的文件了。
软连接之后新的文件就会有新的属性和内容,内容里放的就是指向文件的路径。
硬链接则是在特定目录的数据块中新增,文件名和指向文件的inode的映射关系 。
所以我们看到硬链接的连接数(引用计数为2)当我们删除硬链接或者文件的的时候,引用计数会减减,当引用计数为零的时候文件才会被删除。
文件系统中我们也知道,文件的部分属性在struct file中,但是大部分属性都在inode结构中包括引用计数
1.3目录文件的硬链接
不是说,文件不允许硬链接吗,为什么打开的目录我们能看到硬链接数为2?
这是因为新建目录之后,该目录下有两个隐藏文件 一个是.一个是..一个指向该目录一个指向上级目录,.这个文件就建立了和目录文件inode的映射关系。所以我们就看到硬链接数为2
但是明明说好的目录是不可硬链接的,我们也测试了确实无法对目录文件做硬链接,显示not allowed,这个不冲突吗?需要解释一下,这个目录文件硬链接,是系统内部出厂就设置好的,是不允许用户手动建立的,为了避免用户误操作而导致目录环装问题;但是为了方便用户进行路径快速查找,对目标文件进行定位,内置了硬链接。
1.4应用场景
软链接可以当作快捷方式使用,比如快速运行一个藏的很深的可执行程序
而硬链接一是可以用来当作目录移动的工具,二是可以用来给重要的源文件起别名并使用,一旦发生删除等不可逆行为时,可以确保源文件的安全
1.5ACM时间
每一个文件都有三个时间:访问 Access
、修改属性 Change
、修改内容 Modify
,简称为 ACM
时间
可以通过 stat
查看指定文件的 ACM
时间信息
注意:
Access
是高频操作,如果每次查看都更新的花,会导致 IO
效率变低,因此 实际变化取决于刷新策略:查看 N
后次刷新
修改内容一定会导致属性时间被修改,但不一定会导致访问时间被修改,因为可以不打开文件,对文件进行操作。
2.动静态库
2.1认识库
我们在语言层面使用过很多库,常见的库文件:stdio.h
、stdlib.h
、string.h
等
将链接好的文件进行打包就可以生成动静态库,动静态库在不同的操作系统规范也是不同的,我们常见的windos和linux区别如下:
Linux
中,.a
后缀为静态库,.so
后缀为动态库Windows
中,.lib
后缀为静态库,.dll
后缀为动态库- 虽然不同环境下的后缀有所不同,但其工作原理是一致的
我们可以查看一下我们系统上的库文件
find /usr/lib64/libc*
无论是 C语言 还是 C++,在编写程序时,一定离不开库文件,比如之前模拟实现的 FILE 类型,就位于 stdio.h 这个库中,动态库优势比静态库明显,因此在编译代码时,默认采用动态链接的方式,如果想指定为静态链接编译,只需要在 gcc/g++ 语句后面加上 -static 即可(前提是得有静态库)
关于动静态库的优缺点可以看看下面这个表格
注意: 静态库是将所需要的函数代码拷贝到源文件中直接使用,而动态库是通过动态链接的方式,进行函数链接使用
3.制作静态库
通过小demo练习制作库
mymath.h
#pragma once
#include <stdio.h>
extern int myerrno;
int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int x, int y);
mymath.c
#include "mymath.h"
int myerrno = 0;
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
if(y == 0){
myerrno = 1;
return -1;
}
return x / y;
}
main.c
#include "mymath.h"
int main()
{
extern int myerrno;
// printf("1+1=%d\n", add(1,1));
printf("10/0=%d, errno=%d\n", div(10, 0), myerrno);
return 0;
}
3.1静态库打包
静态库的打包主要分为以下两步:
- 将源文件进行 预处理->编译->汇编,生成可链接的二进制
.o
文件 - 通过指令将
.o
文件打包为静态库
将文件编译为 .o
二进制文件
gcc -o mymath.c//-o 不带目标名直接生成同名文件.o
将所有的 .o
文件打包为一个静态库(库名自定义)
其中的 mymath
为库名
ar -rc libmymath.a *.o
ar
是GNU
提供的归档工具,常用来将目标文件打包为静态库
我们还可以使用ar
反向查看静态库中的具体文件ar -tv 静态库文件
我们将.h头文件放进include目录下,将库文件放入mymathlib目录下,先进性目录创建
3.2静态库的使用
方法一:通过指定路径使用静态库
如果直接编译程序,会出现编译失败的情况,因为编译器不认识第三方库(需要提供第三方库的路径及库名)
第一方库:语言提供
第二方库:操作系统提供
第三方库:other 提供的库,比如当前我们直接打包的静态库
对于自己写的的第三库的使用,需要标注三个参数:
-I 所需头文件的路径
需要将所需头文件的路径加上,此处为.lib/include
-L 所需库文件的路径
这里加的是库文件的路径,也为./lib/mymathlib
-l 待链接静态库名
所需要链接的静态库名字,这里为mymath
gcc -o mymath main.c -I./lib/include -L./lib/mymathlib -lmymath
为什么编译
C/C++
代码时,不需要指定路径?
- 因为这些库都是系统级的,
gcc/g++
默认找的就是stdc/stdc++
库
方法二:将头文件和静态库文件安装至系统目录中
除了这种比较麻烦的指定路径编译外,我们还可以将头文件与动态库文件直接安装在系统目录中,直接使用,无需指定路径(需要指定静态库名)
所谓的安装软件,就是将自己的文件安装到系统目录下
sudo cp .lib/include/*.h /usr/include/
sudo cp .lib/mymathlib/*.a /lib64/
注意: 将自己写的文件安装到系统目录下是一件危险的事(导致系统环境被污染),用完后记得手动删除
4.制作动态库
4.1动态库打包
动态库不同于静态库,动态库中的函数代码不需要加载到源文件中,而是通过 与位置无关码 ,对指定函数进行链接使用
动态库的打包也同样分为两步:
- 编译源文件,生成二进制可链接文件,此时需要加上
-fPIC
与位置无关码 - 通过
gcc/g++
直接目标程序(此时不需要使用ar
归档工具)
将源文件编译为 .o
二进制文件,此时需要带上 fPIC
与位置无关码
小demo
mylog.h
#pragma once
#include <stdio.h>
void Log(const char*);
mylog.c
#include "mylog.h"
void Log(const char*info)
{
printf("Warning: %s\n", info);
}
myprint.h
#include "myprint.h"
void Print()
{
printf("hello new world!\n");
printf("hello new world!\n");
printf("hello new world!\n");
printf("hello new world!\n");
}
#pragma once
#include <stdio.h>
void Print();
mylog.c
#include "mylog.h"
void Log(const char*info)
{
printf("Warning: %s\n", info);
}
通过这个小案例,把动静态库都制作了
gcc -c -fPIC *.c
将所有的 .o
文件打包为动态库(借助 gcc/g++
)
gcc -o libmycalc.so *.o -shared
Makefile
dy-lib=libmymethod.so
static-lib=libmymath.a
.PHONY:all
all: $(dy-lib) $(static-lib)
$(static-lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.c
gcc -c $^
$(dy-lib):mylog.o myprint.o
gcc -shared -o $@ $^
mylog.o:mylog.c
gcc -fPIC -c $^
myprint.o:myprint.c
gcc -fPIC -c $^
.PHONY:clean
clean:
rm -rf *.o *.a *.so mylib
.PHONY:output
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h mylib/include
cp *.a mylib/lib
cp *.so mylib/lib
4.2动态库的链接使用
像使用静态库一样使用动态库(指定路径及库名),编译成功,但运行失败!
为什么会出现这种问题?因为当前只告诉了编译器动态库的位置,没有告诉 加载器
通过 ldd
查看程序链接情况
运行时,OS
是如何链接动态库?
环境变量 LD_LIBRARY_PATH (默认没有这个环境变量),将第三方动态库路径添加至此环境变量中(临时方案)
sudo 在 /lib64/ 目录下建立动态库的软链接
更改配置文件 /etc/ld.so.conf.d 这个目录中都是各种动态库配置文件,创建文件 xx.conf 至目录中(文件中存储的是第三方动态库的路径)ldconfig 令配置文件生
方法一:通过环境变量解决
添加动态库路径至 LD_LIBRARY_PATH
环境变量中
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/cmx-108/lesson9/mylib/lib
环境变量 LD_LIBRARY_PATH
是程序在进行动态库查找时的默认搜索路径
注意: 更改环境变量只是临时方案,重新登录后会失效
方法二 复制到系统路径下,略
sudo ln -s /home/lesson9/mylib/lib/libmymethod.so /lib64/libmymethod.so
方法三:更改配置文件中的信息
echo /home/cmx-108/lesson9/mylib/lib > cmx.conf
sudo mv cmx.conf /etc/ld.so.conf.d/
sudo ldconfig
注意: 后两种方法都可以做到永久生效(因为存入了系统目录中),但在使用完后最好删除,避免污染系统环境
4.3动态库的链接原理
程序在链接动态库函数时,是通过 动态库起始地址 + 所链接函数偏移量 的方式进行链接访问的,而这个偏移量就是 fPIC 与位置无关码
地址其实就两种:绝对地址和相对地址,静态链接时,将可链接的二进制文件加载至程序中,直接通过 绝对地址 进行链接,假设函数被调用了多次,就会导致代码冗余等问题;动态链接采用 相对地址 的方式进行链接,同一个函数的 动态库起始地址 + 所链接函数偏移量 值相同,代码只需要加载一份,并且可以任意位置进行函数调用(与位置无关)
动态库中所有地址都是偏移量,默认从 0
开始
只有当一个库被真正映射进地址空间后,它的起始地址才能真正确定
- 链接库中的函数时,通过
动态库的起始地址 + 函数偏移量
的方式链接函数 - 这种方法不论在什么位置,都可以随便链接函数(与位置无关)
- 与位置无关码:动态库中地址,是偏移量
总结:
关于系统对物理地址的管理,包括,进程如和找到page页。将页缓冲区到写到对应页框中,这个知识我还需要好好整理。和本章也不搭。动静态库的整理就到这了。