【Linux操作系统】简学深悟启示录:动静态库
文章目录
- 1.文件系统补充
- 1.1 内存硬盘交互
- 1.2 系统对内存的管理
- 1.3 文件的刷新
- 2.动静态库
- 2.1 静态库自制
- 2.2 执行静态库
- 2.3 动态库自制
- 2.4 执行动态库
- 希望读者们多多三连支持
- 小编会继续更新
- 你们的鼓励就是我前进的动力!
1.文件系统补充
1.1 内存硬盘交互
物理内存与硬盘交互数据时,通常是以 4KB
为单位的,物理内存的每一单位叫页框,硬盘的每一单位叫页帧,但是为什么是以 4KB
为单位,多次一点点传输不好吗,省得浪费空间?
其实设计者也是考虑了很多的,硬盘每次获取数据都要磁头定位,为了提高访问的效率,因此要尽可能减少 IO
次数,再就是当你要获取一块数据时,通常该数据附近的数据后续也很大概率要调用,所以不如一次性都调用出来,即预加载机制
1.2 系统对内存的管理
通常每一个 4KB
的页框就是一个结构体 page
,把这每一个 page
简化成一整个数组,对内存的管理就变成了对数组的管理,每当需要取一个页框的数据时,就对物理地址进行特定的计算获取数组下标即可访问
1.3 文件的刷新
前面我们学习知道进程地址空间会找到 struct file
获取文件数据,但是实际上后续更详细的操作还有待了解
struct file
本身不存储文件名,仅仅包含少量的文件属性,它通过 f_path.dentry
成员指向对应的 struct dentry
(目录项),而 dentry
中会记录文件名(d_name
成员),同时 dentry
又通过 d_inode
成员指向 struct inode
。简单说,三者的关联链是:struct file
→ dentry
(含文件名)→ struct inode
,顺着这条链就能找到文件名与 inode
的对应关系,进而 struct inode
就能够获取更详细的文件属性
当通过 struct file
读写文件内容时,内核会先通过 file->f_inode->i_mapping
找到 address_space
。检查页缓存中是否已有目标数据(通过 address_space
的页树查找)。
如果缓存命中,直接从内存页读取数据;如果未命中,address_space
的操作方法(a_ops->readpage
)会触发从磁盘加载数据到缓存页,再返回给用户。
写操作也类似,通常先写入页缓存(标记为 “脏页”),后续由内核线程通过 address_space
的同步方法(如 a_ops->writepage
)刷新到磁盘
如果把 “页缓存”(物理内存里的一块空间) 比作 “存放文件数据的仓库”,那么 struct address_space
就是 “仓库管理员”—— 它手里有 “仓库货位表”(page_tree
),知道 “哪批数据(文件偏移)放在哪个货位(内存页)”;还负责 “进货”(从磁盘加载数据到仓库)、“出货”(把仓库数据写回磁盘),同时知道 “这个仓库属于哪个文件”(关联 inode
)
简单来说,就记住内核从 inode
结构体查找文件属性,从文件缓冲区查找文件内容
2.动静态库
有时我们需要通过引入第三方库满足自己的开发需求,libxxx.a
叫做静态链接,libxxx.so
叫做动态链接
2.1 静态库自制
站在发布者的角度,当我们自制一个静态库时,有两种方法供他人使用,一种是直接将全部源代码发布给别人使用,另一种方法就是打包成库,库=库+.h
,这个 .h
就相当于说明书,告诉别人怎么使用,这种方法能很好的保护源代码最常用
以下将实现一个加减乘除的自制静态库
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;
}
Makefile
lib=libmymath.a$(lib):mymath.oar -rc $@ $^
mymath.o:mymath.cgcc -c $^.PHONY:clean
clean:rm -f *.o *.a lib.PHONY:output
output:mkdir -p lib/includemkdir -p lib/mymathlibcp *.h lib/includecp *.a lib/mymathlib
变量 lib
依赖于 libmymath.a
静态库文件,$(lib)
表示引用该变量(即 libmymath.a
),ar -rc $@ $^
表示将 .o
文件全部归档打包成库,-rc
表示 replace
和 creat
,打包成的库即有则覆盖无则创建,然后将所有 .c
文件编译成 .o
文件,最后等待用户一起连接即可,为什么要等待用户呢?
反正库里那么多文件都要编译,不如都编译成 .o
文件,因为太多了就打包成 libxxx.a
这样的库,等 main.c
编译成 .o
文件,再一起链接生成可执行文件
output
表示发布该静态库,将所有的 .h
和 .o
文件整理成一个静态库文件方便发布
2.2 执行静态库
现在我们站在使用者的角度来使用该静态库
main.c
#include "mymath.h"int main()
{int ret = div(10, 0);printf("10 / 0 = %d, errno: %d\n", ret, myerrno);return 0;
}
首先将库 lib
放到和 main.c
相同的路径下,gcc
编译后发现报错这是为什么呢?
这是因为当库进行链接的时候,默认在系统路径下或当前目录下查找,但是对应的库和 .h
文件都在 lib
里面,系统找不到,此时就需要指定查找
gcc main.c -I lib/include/ -L lib/mymathlib -lmymath
- -I lib/include/: 告诉编译器在
lib/include/
目录下查找头文件.h
- -L lib/mymathlib: 告诉链接器在 lib/mymathlib 目录下查找库文件
.a
- -lmymath: 指定要链接的静态库名称(实际对应的库文件是
libmymath.a
,-l
选项后省略lib
前缀和.a
后缀,注意不能有空格,依赖多个库就多个-l
指定即可)
🔥值得注意的是: 除了直接执行命令以外,还可以通过将库的路径加入系统路径,或者在系统路径下创建软连接,但是依然需要 gcc main.c -lmymath
指定静态库文件
2.3 动态库自制
myprint.h
#pragma once#include <stdio.h>void Print();
myprint.c
#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");
}
Makefile
dy-lib=libmymethod.so$(dy-lib):myprint.ogcc -shared -o $@ $^
myprint.o:myprint.cgcc -fPIC -c $^.PHONY:clean
clean:rm -rf *.o *.so mylib.PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/libcp *.h mylib/includecp *.so mylib/lib
gcc -fPIC -c $^
表示将 .c
文件编译为 .o
文件,-fPIC
是与位置无关码,简单来说就是不依赖于具体内存加载地址,而是计算相对位置的机器码,后续会详细介绍,-shared
表示生成动态库
2.4 执行动态库
现在我们站在使用者的角度来使用该动态库
gcc main.c -I mylib/include -L mylib/lib -lmymethod
同样执行和静态库一样的编译 main.c
的命令
zzh@iv-ye51qmh4owxjd1uhrjzq:~/libraries/test$ ldd a.outlinux-vdso.so.1 (0x00007ffe01f82000)libmymethod.so => not foundlibc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f662ece1000)/lib64/ld-linux-x86-64.so.2 (0x00007f662ef17000)
发现依旧无法执行,执行 ldd a.out
命令查看可执行文件的状态,发现系统依旧无法找到动态库,这是因为编译器知道了动态库在哪,但是加载器还不知道啊,静态库在编译的时候把源码都包含进去了所以不需要额外加载,该有的代码都在里面了,而动态库需要到共享区去执行代码,所以要用到加载器
解决加载找不到动态库的四种方法:
- 拷贝到系统默认的库路径
/usr/lib64/
- 在系统默认的库路径
/usr/lib64/
下建立软连接 - 将自己的库所在的路径,添加到系统的环境变量
LD_LIBRARY_PATH
中 /etc/ld.so.conf.d
建立自己的动态库路径的配置文件,然后重新ldconfig
即可
🔥值得注意的是: 通常第一种用的最多也是最方便的,在 centos
下拷贝到系统默认路径下即可,Ubuntu/Debian
系列还需要在 /etc/ld.so.conf.d
配置 /usr/lib64
路径的 config
文件,不然系统找不到,这是因为不同系统的配置逻辑不同,centos
天生认识 /usr/lib64
,但 Ubuntu/Debian
不把 /usr/lib64
当默认路径