【Linux操作系统】简学深悟启示录:动静态库

文章目录

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 filedentry(含文件名)→ 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

c 复制代码
#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

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

bash 复制代码
lib=libmymath.a

$(lib):mymath.o
	ar -rc $@ $^
mymath.o:mymath.c
	gcc -c $^

.PHONY:clean
clean:
	rm -f *.o *.a lib

.PHONY:output
output:
	mkdir -p lib/include
	mkdir -p lib/mymathlib
	cp *.h lib/include
	cp *.a lib/mymathlib

变量 lib 依赖于 libmymath.a 静态库文件,$(lib) 表示引用该变量(即 libmymath.a),ar -rc $@ $^表示将 .o 文件全部归档打包成库,-rc 表示 replacecreat,打包成的库即有则覆盖无则创建,然后将所有 .c 文件编译成 .o 文件,最后等待用户一起连接即可,为什么要等待用户呢?

反正库里那么多文件都要编译,不如都编译成 .o 文件,因为太多了就打包成 libxxx.a 这样的库,等 main.c 编译成 .o 文件,再一起链接生成可执行文件

output 表示发布该静态库,将所有的 .h.o 文件整理成一个静态库文件方便发布

2.2 执行静态库

现在我们站在使用者的角度来使用该静态库

main.c

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 里面,系统找不到,此时就需要指定查找

bash 复制代码
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

c 复制代码
#pragma once

#include <stdio.h>

void Print();

myprint.c

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

bash 复制代码
dy-lib=libmymethod.so

$(dy-lib):myprint.o
	gcc -shared -o $@ $^
myprint.o:myprint.c
	gcc -fPIC -c $^

.PHONY:clean
clean:
	rm -rf *.o *.so mylib

.PHONY:output
output:
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp *.h mylib/include
	cp *.so mylib/lib

gcc -fPIC -c $^ 表示将 .c 文件编译为 .o 文件,-fPIC 是与位置无关码,简单来说就是不依赖于具体内存加载地址,而是计算相对位置的机器码,后续会详细介绍,-shared 表示生成动态库

2.4 执行动态库

现在我们站在使用者的角度来使用该动态库

bash 复制代码
gcc main.c  -I mylib/include -L mylib/lib -lmymethod

同样执行和静态库一样的编译 main.c 的命令

bash 复制代码
zzh@iv-ye51qmh4owxjd1uhrjzq:~/libraries/test$ ldd a.out
	linux-vdso.so.1 (0x00007ffe01f82000)
	libmymethod.so => not found
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f662ece1000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f662ef17000)

发现依旧无法执行,执行 ldd a.out 命令查看可执行文件的状态,发现系统依旧无法找到动态库,这是因为编译器知道了动态库在哪,但是加载器还不知道啊,静态库在编译的时候把源码都包含进去了所以不需要额外加载,该有的代码都在里面了,而动态库需要到共享区去执行代码,所以要用到加载器

解决加载找不到动态库的四种方法:

  1. 拷贝到系统默认的库路径 /usr/lib64/
  2. 在系统默认的库路径 /usr/lib64/ 下建立软连接
  3. 将自己的库所在的路径,添加到系统的环境变量 LD_LIBRARY_PATH
  4. /etc/ld.so.conf.d 建立自己的动态库路径的配置文件,然后重新 ldconfig 即可

🔥值得注意的是: 通常第一种用的最多也是最方便的,在 centos 下拷贝到系统默认路径下即可,Ubuntu/Debian 系列还需要在 /etc/ld.so.conf.d 配置 /usr/lib64 路径的 config 文件,不然系统找不到,这是因为不同系统的配置逻辑不同,centos 天生认识 /usr/lib64,但 Ubuntu/Debian 不把 /usr/lib64 当默认路径


希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

相关推荐
web安全工具库3 小时前
Linux ls 命令进阶:从隐藏文件到递归显示,成为文件浏览大师
linux·运维·服务器·c语言·开发语言
_清浅3 小时前
计算机网络【第二章-物理层】
服务器·网络·计算机网络
我要成为c嘎嘎大王3 小时前
【Linux】进程的概念和状态
linux·运维·服务器
0xCode 小新4 小时前
【C语言内存函数完全指南】:memcpy、memmove、memset、memcmp 的用法、区别与模拟实现(含代码示例)
linux·c语言·人工智能·深度学习·机器学习·容器·内存函数
EEE1even4 小时前
VScode通过跳板机连接内网服务器
服务器·ide·vscode
hweiyu004 小时前
Linux 命令:scp
linux·运维·服务器
cpsvps4 小时前
容器内部DNS解析针对美国服务器微服务的调试指南
运维·服务器·微服务
特种加菲猫4 小时前
Linux之线程池
linux·笔记
8K超高清4 小时前
汇世界迎全运 广州国际社区运动嘉年华举行,BOSMA博冠现场展示并分享与科技全运的故事
运维·服务器·网络·数据库·人工智能·科技