索引
- 磁盘文件的理解
- inode
- 软硬链接
- 动静态库
- 理解动态链接与地址无关
- 生成动静态库
- 使用静态库
- 使用动态库
- 动态链接再次理解!
磁盘文件的理解
之前讲述的都是内存级的文件,但是系统中最多的还是磁盘级的文件,大量的文件自然也需要被管理,那么如何管理呢?
我们先看几张磁盘的实物图才能更好的了解
如图所示,读写磁盘的时候,磁头找的是某一个面(哪一个磁头)的某一个磁道(距离圆心的半径)的某一个扇区(磁道上的一段,盘面旋转决定的)。
只要我们能找到磁盘上的盘面,柱面(磁道),然后找到扇区也就是找到一个存储单元就能读写磁盘。
注:每个扇区的大小都是512字节,无论距离圆形的半径是多少
用同样的方法,我们也就能找到所有的基本单元了。
由于他们的英文名称,所以在物理上找某个扇区的地址叫做CHS地址。
但毕竟磁盘还是物理结构,我们需要将其逻辑抽象一下。
如图所示,如果我们将这个磁带全部拉出来的话,就变成了一条直线,同理我们也可以把磁盘的一块块扇区拼接起来,变成一条长方形。
因此,定位一个扇区,只需要找到对应数组的下标就可以了,在内存中这个地址叫做LBA地址(Logic Block Address )逻辑地址
理解:内存中有个数据想向磁盘中写入的话,在内存中他只知道LBA地址,所以首先将LBA地址映射转化成CHS地址,转化成CHS地址之后,然后将内存数据配合CHS地址写入到磁盘中,至此写入就完成了。
但是磁盘的结构是硬件层面的,我们要学习的是如何管理这些磁盘上的文件。
虽然磁盘存储的基本单位扇区的大小是512字节,但是文件系统访问读写磁盘的基本单位是4kb,为什么要这样呢?
- 提高IO的效率
- 不要软件操作系统的设计和硬件磁盘具有强相关性,使得他们之间接耦合。
万一以后硬件的一个扇区的大小设置成1024个字节,此时操作系统难道也要跟着变吗?所以操作系统不管硬件的扇区大小是多少,他只需要保持4kb的吞吐量就可以了,接耦合有利于软硬件的发展。
inode
如上所示,最左边的就是文件的inode 编号
问题:创建一个文件时,OS做了什么?
当文件在目录被创建时,Linux文件系统会根据文件名为其分配一个唯一的inode编号,然后修改inode Bitmap,在inode Table找到对应的节点然后向他里面写入对应的属性号。找到还未使用的数据块,修改Block Bitmap,将文件名和inode编号的映射关系写入到目录的数据快中!!
删除了一个文件,OS做了什么?
先找到文件名与inode对应的条目,然后找到inode编号,再根据inode编号找到对应的inode Bitmap和blockBitmap由1置0,此时就完成了文件的删除,再在文件的所在目录中将文件名与inode对应的映射关系清除,所以在linux当中文件的数据并没有被真正的删除
我知道了自己所处的目录,就能知道目录的inode吗?
不能,目录的inode在其根目录的内容中,最终得找到根目录
软硬链接
什么是软链接
软连接时第一个独立文件,拥有自己的独立inode和inode编号,主要作为Linux下的快捷方式,因为有的可执行文件或头文件或者是动静态库所在路径十分的深,如果直接运行或者是使用的话路径过深会非常麻烦,所以使用快捷方式非常容易
什么是硬链接?
硬链接不是一个独立的文件,他和目标文件使用的是同一个inode,就是单纯的在linux指定的目录下,给指定的文件新增文件名和inode编号的映射关系。
可以理解为一个“指针”概念,表示有几个文件名和我的inode建立的关系,有什么用呢?路径间切换
为什么新建目录的硬链接数是2?但是文件确实1呢?
可以看到当我们进入test2目录之后,其表示当前路径的.
与test2的inode编号是相同的,所以说硬链接数可以用于路径间切换,一般还可以用来估算一下目录中有多少层目录。
动静态库
什么是静态库?
程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库。
优点:
- 性能高,程序运行时无需加载额外的库文件
缺点
- 占用空间大, 每个程序内部都要保留一份
printf(), scanf(), strlen()
等函数还有一些辅助数据结构,所占据的内存十分大。 - 代码的耦合度太高,如果其中的一个地方错了改动之后需要用户重新下载一遍
- 代码重复,代码的复用率不高
所以有了动态库
程序在运行的时候才去链接动态库的代码,将文件根据功能还分成若干模块,降低了代码的耦合度,并且所有的程序共享使用库的代码。
理解动态链接与地址无关
理解动态链接产生与地址无关码
当进程加载到动态链接时,链接器会将库中所有的符号地址都替换成相对地址,静态链接是将库中所有的符号地址替换成绝对地址,相对地址是相对于库中某个特定基地址的偏移量,然后在运行时,进程会将这个基地址设置为虚拟地址空间中(共享区)的某个地址,从而使得动态链接库的相对地址能够正常的映射到实际的虚拟地址中。动态链接的符号地址都是相对于基地址的偏移量,所以说动态链接与地址无关,基地址是指动态链接在内存中被加载的起始地址然后再将这个基地址映射到进程的虚拟地址空间中。假设动态库的基地址为B,某个符号在动态链接库的地址为A,该符号在进程的虚拟地址空间中的地址是V,那么在运行时,进程会将动态链接库中符号地址A替换成相对地址A-B,然后将基地址B设置为V-A,从而使得动态链接库中的相对地址能够正确映射到实际的虚拟地址空间中。
生成动静态库
.o文件是一种目标文件,它是编译器将源代码编译后生成的中间文件,其中包含了已编译的机器代码,符号表和调试信息
如果我们把多个.o文件给他人,其他人链接能够使用吗?
先记录下下面的源码
mymath.h
#pragma once
#include<stdio.h>
#include<assert.h>
//[from, to] -->累加 -> result ->return
extern int addToVal(int from, int to);
myprintf.h
#pragma once
#include<stdio.h>
#include<time.h>
extern void Print(const char *msg);
mymath.c
#include"mymath.h"
int addToVal(int from, int to)
{
assert(from <= to);
int sum = 0;
for(int i = from; i <= to; i++)
{
sum += from;
}
return sum;
}
myprintf.c
#include"myprintf.h"
void Print(const char *msg)
{
printf("%s : %lld\n", msg, (long long)time(NULL));
}
实验验证,多个.o
文件与一个源文件链接在一起,会形成可执行文件,因为链接多个.o
文件时,链接器会解析符号表,将不同的.o
文件中定义和引用的符号进行匹配,从而形成可执行文件或共享库文件。
但是.o
文件太多了,用起来特别不方便,所以我们将.o文件打包形成一个库。
生成静态库:
10 libmymath.a:mymath_s.o myprintf_s.o
11 ar -rc libmymath.a mymath_s.o myprintf_s.o
12 mymath_s.o:mymath.c
13 gcc -c mymath.c -o mymath_s.o
14 myprintf_s.o:myprintf.c
15 gcc -c myprintf.c -o myprintf_s.o
ar是gnu归档工具,rc表示(replace and create)
生成动态库:
3 libmymath.so:mymath.o myprintf.o
4 gcc -shared -o libmymath.so mymath.o myprintf.o
5 mymath.o:mymath.c
6 gcc -fPIC -c mymath.c -o mymath.o
7 myprintf.o:myprintf.c
8 gcc -fPIC -c myprintf.c -o myprintf.o
shared:表示生成共享库格式
FPIC:产生位置无关码
库名规则:libXXX.so
同时实现动静态库的代码
1 .PHONY:all
2 all:libmymath.so libmymath.a
3 libmymath.so:mymath.o myprintf.o
4 gcc -shared -o libmymath.so mymath.o myprintf.o
5 mymath.o:mymath.c
6 gcc -fPIC -c mymath.c -o mymath.o
7 myprintf.o:myprintf.c
8 gcc -fPIC -c myprintf.c -o myprintf.o
9
10 libmymath.a:mymath_s.o myprintf_s.o
11 ar -rc libmymath.a mymath_s.o myprintf_s.o
12 mymath_s.o:mymath.c
13 gcc -c mymath.c -o mymath_s.o
14 myprintf_s.o:myprintf.c
15 gcc -c myprintf.c -o myprintf_s.o
16
17
18
19 .PHONY:lib
20 lib:
21 mkdir -p lib-static/lib
22 mkdir -p lib-dyl/lib
23 mkdir -p lib-dyl/include
24 cp *.so lib-dyl/lib
25 cp *.h lib-dyl/include
26 mkdir -p lib-static/include
27 cp *.a lib-static/lib
28 cp *.h lib-static/include
29 .PHONY:clean
30 clean:
31 rm -rf *.o *.a lib *.so lib*
使用静态库
此时如果包含头文件然后编译运行mytest一定是错误的
因为库文件的搜索路径
- 在当前路径下查找文件,此时
mytest.c
包含的头文件是在lib-static
中,所以不行 - 由环境变量指定的目录(LIBRARY_PATH)
- 系统默认目录
/usr/lib
/usr/local/lib
系统的头文件如下所示
系统库安装位置
由于我们的头文件和库文件都不在系统路径下,所以此时编译器找不到,所以一定是会报错的。
如上所示:如果我们此时把头文件和库文件都拷贝进系统目录的话再次编译,此时还是会报错,因为我们从来没有使用过第三方库,一直使用的都是C/C++库,所以如果我们要使用第三方库的话我们必须链接,链接名 需要去掉前缀和后缀
,然后就可以了。
但是我们并不推荐安装库,会污染系统的标准库
我们还可以指定路径寻找头文件
使用动态库
我们一开始直接使用和上述最后一种的方法
为什么静态库运行的时候没有出现上述找不到库的问题?因为静态链接形成可执行程序之后,已经把需要的代码拷贝进我的代码中!运行时,不需要依赖系统中的库——不需要运行时查找
为什么动态库会有这个问题呢?
因为程序和动态库是分开加载的!
如何解决?
想办法让进程找到动态库即可!
- 动态库拷贝到系统路劲下 /lib64 --安装(不推荐!)
- 通过环境变量的方式,—程序运行的时候,会在环境变量中查找自己需要的动态库路劲 – LD_LIBRARY_PATH
- 系统配置文件来做
通过导入环境变量的方式
这样导入环境变量只在当前shell有效,重启之后就无效了,因为shell本身启动的时候就是在配置文件里导入,说明这个指令本身是内存级别的。
通过配置文件
就是在/etc/ld.so.conf.d/
这个路径下写一个文件然后将对应的库路径写入就可以了。因为系统寻找库路径的时候不仅会在默认路径下还会扫描这个路径(试验完记得删除!)
在系统中建立软链接查找动态库
此时不用指明库的路径和名称直接就可以编译运行