【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 当默认路径


希望读者们多多三连支持

小编会继续更新

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

相关推荐
Yana.nice1 小时前
openssl将证书从p7b转换为crt格式
java·linux
AI逐月1 小时前
tmux 常用命令总结:从入门到稳定使用的一篇实战博客
linux·服务器·ssh·php
想逃离铁厂的老铁1 小时前
Day55 >> 并查集理论基础 + 107、寻找存在的路线
java·服务器
小白跃升坊2 小时前
基于1Panel的AI运维
linux·运维·人工智能·ai大模型·教学·ai agent
跃渊Yuey2 小时前
【Linux】线程同步与互斥
linux·笔记
杨江2 小时前
seafile docker安装说明
运维
舰长1152 小时前
linux 实现文件共享的实现方式比较
linux·服务器·网络
好好沉淀2 小时前
Docker开发笔记(详解)
运维·docker·容器
zmjjdank1ng2 小时前
Linux 输出重定向
linux·运维
路由侠内网穿透.2 小时前
本地部署智能家居集成解决方案 ESPHome 并实现外部访问( Linux 版本)
linux·运维·服务器·网络协议·智能家居