1. 文件系统的硬盘结构
之前学到,文件=属性+内容。而inode就存储这属性集合。而内容存储在block中,可能是多个block因为不确定文件内容的大小。硬盘上有很多文件,当inode和block多起来,就要被先描述在组织。同时也要对文件进行操作,所以就引出了文件系统。
filesystem
{
//基本情况
//空间一共多大
//已经使用和没有被使用
//inode
//block
//group
方法:
//创建
//删除
//等等
}
当我们ll时,文件呈现出如下形式:
每行包含7列,依次是:
- 模式
- 硬连接数
- 文件所有者
- 组
- 大小
- 最后修改时间
- 文件名
当在命令行敲下ls-l这条命令时就发生了如下操作:
其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息
磁盘的结构如下:
硬盘上最小单位为扇区,扇区大小为512个字节,一个block大小为4kb,也就是说一个块拥有8个扇区。磁盘中的光片,两面可以写入。
操作系统读取扇区的时候是靠逻辑地址,所以在底层其实将他们换分成一个大数组,通过下标划分不同的区域。
而这分区还需要再划分成一个个块组(BLOCK GROUP)。每个块组的结构相同。
- 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。描述整个文件系统的信息
- GDT,Group Descriptor Table:块组描述符,描述块组属性信息。具体某个分组的详细信息
- 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
- inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。1字节8比特位,1kb=1024个字节,1mb=100万个字节=800万个比特位,可以保存10万个位图,
- i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等.
- 数据区:存放文件内容(数据)。
找一个文件,需要先找到inode,在找到block(可能是多个)。
-
怎么找到Inode?
每一个inode对应一个inodeid。 -
怎么找到BLOCK
Inode里面存放着一个map数组,数组里面的指针指向一个个BLOCK,就可以找到Block.
INODE里存放着属性信息,DataBlocks存放着数据信息。 -
怎么创建文件?
创建文件,找到分区,块组,分配INODE,在inode位图里,找一个为0的位,然后将他置为1。通过这一个位找到inode表里的inode,将属性信息填入,然后在块位图里找一个未被使用的block的位(编号),通过这个位找到block,填入内容,然后将block的位(编号)写入到inode的map数组里。最后将文件名,inode的放到目录文件的block中,文件名和inode之间有着映射关系。 -
怎么找到文件?
在struct file中存在path,path中有一个dentry,里面存着文件目录的inode,inode里面有和目录block的映射,目录的block存着文件名和inodeid,根据inodeid,在找到文件的inode。 -
怎么删除数据?
将块位图的位,置成0。将inode位图的位,置成0。所以删除数据非常快 -
怎么恢复数据?
通过某些手段或工具获取inode,根据map数组映射找到block将0恢复成1. -
看上去我们每次都在遍历,实际上Group Descriptor Table里就整体描述了inode和block的使用情况。
-
目录也是一种文件,所以目录肯定也有inode存放着属性,那么目录里的block放什么呢?
那么目录里的block实际存放着文件名和inode之间的映射关系。他inode都找到了,那么block也能找到。
2. 软硬链接
2.1 硬链接
ln new.txt h_link
创建硬链接,可以看到h_link和new.txt的inode是一样的
当我们修改new.txt,h_link也会跟着修改
硬链接:和一个文件共享inode。将文件的inode和链接文件建立一个映射关系。
- 就像我们重命名的时候,本质就是创建一个硬链接文件,删除掉之前的文件,而且属性和内容是一模一样。
当h_link和new_txt指向同一个inode的时候,硬链接数是2。
当2021_4_7这个目录里都是文件,没有目录时,硬链接数是2。
他自己指向自己的inode算一个。
在目录里还有一个 .
,.代表当前目录所以这个文件名也指向了那个inode,所以一共是两个。
当我们在2021_4_7里在创建一个目录时,发现硬链接数变成了3。
其实是在我们刚创建的目录里,又有一个…指向了上级目录,也就是指向了那个inode。所以他自身文件名,本目录的.
,
目录里还有一个目录,这个目录还有个..
,都指向了inode,所以硬链接数就是3.
2.2 软链接
ln -s log.txt link
创建软链接,link是一个独立的文件,因为他的inode不一样。
软链接:具有独立的inode,是一个独立的文件。本质就是在创造文件的基础上,在block中存储log.txt的路径。
比如windows的快捷方式。
假如不想用环境变量的情况下,而他又在比较底层的目录中,想快捷的启动一个可执行文件。就可以用软链接。
3.动静态库
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
当编译完一个程序后,使用ldd命令,查看他的链接关系。
发现此程序默认使用的是动态库。
一般的命名方式是 lib+name+.so+version
所以这个动态库是c库。
当使用静态链接生成的话(gcc后面加static),将近大了100倍,仅仅是编了一个hello world。
动态链接和静态链接运行时的区别?
- 动态链接:运行程序的时候,遇到printf函数的时候,才去链接动态库中的代码去执行。所以当他编译链接完程序没有库代码那一部分,是比较小的。
- 静态链接:当你gcc编译链接的时候,就把库的代码和源文件链接在一起在代码区中也就是说,一旦编译链接完,运行的时候就不依赖外面的那个静态库了,因为都在代码区所以编译链接完,程序比较大。
动态链接和静态链接的优劣?
- 动态链接:程序生成的体积小,程序运行起来成为进程,运行到进程中使用到库函数代码那一步时,动态库的代码才会加载到内存中。所以比较依赖库,物理内存中只有一份。哪个进程用,哪个进程的虚拟地址空间(共享区)通过页表映射到物理内存。
- 静态链接:程序生成的体积大,编译链接的时候就把对应的代码链接到程序中,一旦编译链接完,不需要依赖库。内存中有多份拷贝(多个进程,多个代码区,多个拷贝)
和之前的内容关联一下。
一个标准的第三方库是要包含两个东西。include头文件以及.a或.so
例如c语言提供的:
/usr/include/
/lib64/lib.so
而一批头文件提供的是,有什么方法可以使用,接口参数的含义。
一个或多个库文件:就是方法的具体实现(二进制),供我们进行动静态链接
3.1 模拟静态链接
先编写好.c和.h文件,因为静态链接是在源文件链接的时候,从静态库直接拷贝代码放在代码区,意味着当你的代码被转换成二进制的时候才会拷贝静态库的代码,所以我们要把.c文件写好,并编译链接成.o文件,才能模拟出静态链接的效果。
将所有的.o文件打包,变成了静态库。别人用的时候只需要包含头文件,然后找到链接写好打包的静态库。
ar -rc libmymath.a MyAdd.o MySub.o
其实这就是我们平常使用人家的库函数,先包头文件(声明)
,然后链接的时候就从静态库里拷贝他.o
形式的实现代码
mytest.c代表测试静态库的例子。(等会把makefile,mylib一换还可以测试动态库的例子)
模拟c库中的分布,在include下包含.h文件,在lib目录下包含库文件
由于人家c库的环境变量里面包含了头文件和库文件的路径,所以直接包含就可以找到。
(编译的时候找声明文件,链接的时候找静态库的二进制实现文件。)
我们要模拟只好在makefile中用自带路径。
I代表include头文件所在位置
L代表lib库文件所在位置
l代表库文件的名字
-I ./mylib/include -L ./mylib/lib -l mymath
这样别人最终只需要,头文件和打包的库文件,就可以使用我们写好的方法。和我们使用c语言库的原理是一样的。
不带也可以,把头文件,库文件放在系统默认搜索路径,就不用带I,L
但是库文件名字要带(配置一下也可以不用带)但是尽量不要污染环境。
3.2 模拟动态链接
在现在文件夹,创建一个dynamic文件夹,里面先编写好mylib。(到时候拷贝到外面就好,在改一下外面的mkfile,就能在演示动态链接的例子。)
.c和.h文件同样模拟C语言库的放置路径。只不过我们这次不再外面一个个敲命令建文件夹,直接在makefile文件写好
1 libmymath.so:myadd.o mysub.o
2 gcc -shared -o $@ $^
3 myadd.o:myadd.c
4 gcc -fPIC -c $<
5 mysub.o:mysub.c
6 gcc -fPIC -c $<
7 .PHONY:clean
8 clean:
9 rm -rf output *.o libmymath.so
10 .PHONY:output
11 output:
12 mkdir -p mylib/include
13 mkdir -p mylib/lib
14 cp *.h mylib/include
15 cp *.so mylib/lib
因为makefile是依赖关系,所以从第6行向上看。
- -shared代表共享库,
- gcc用到的参数fpic代表-fPIC与-fpic都是在编译时加入的选项,用于生成位置无关的代码(Position-Independent-Code)。这两个选项都是可以使代码在加载到内存时使用相对地址,所有对固定地址的访问都通过全局偏移表(GOT)来实现。-fPIC和-fpic最大的区别在于是否对GOT的大小有限制。-fPIC对GOT表大小无限制,所以如果在不确定的情况下,使用-fPIC是更好的选择。
主要看伪目标output代表我们创建的路径,库文件和头文件所在位置。
删除静态库例子(mylib),把生成的mylib(这个是动态库例子),拷贝到上级目录。
上级目录的makefile也要改一下,刚才加了-static,演示动态链接时需要去掉,路径那些不变。
接着make可以生成可执行文件,但是运行却出错,没有找到,为啥呢?
由于知道声明文件的位置,使用gcc编译是可以生成可执行文件的,编译器知道库在哪但没用到,因为动态链接是程序启动之后变成进程,需要的时候动态库对应代码加载到内存,这是由操作系统加载的,但是我们没有将路径告诉操作系统,操作系统不知道。
默认的在系统环境变量下,编译器操作系统都能找到,所以我们用这个命令,把路径加入到找动态链接的环境变量中
export LD_LIBRARY_PATH=/home/zjn/lessones/14_lesson/mylib/lib
再次运行文件就可以运行了