文件系统—磁盘、软硬链接、动静态库

image-20230205003410658

文件系统

文件被打开,就会被加载到进程被操作系统管理,文件没有被打开,就待在磁盘上。磁盘上有着大量的文件,这些文件也需要被管理,这部分工作就称为文件系统!

那磁盘长啥样?

磁盘的物理结构

现在的笔记本大多数都是用的ssd(固态硬盘)。而在早时期用的是磁盘,磁盘是计算机中的唯一一个机械结构,硬盘也是外设,这两个特性决定了硬盘相对于其他结构来说访问很慢。 但是在互联网企业中磁盘还是主流。

磁盘拆开,可以看到一个磁盘,磁盘是两面光—两面可用,每面都有一个磁头,磁盘中间有个马达控制磁盘旋转,磁头也有一个马达控制摆动,搭配有硬件电路和伺服系统负责给磁盘发送二进制指令,来寻址等等来对某个区域的数据读取或写入。

image-20230131150331179

磁盘可能是一个,也可能是一摞

image-20230131122624005

磁盘密封性很好,一旦打开几乎不能使用。 磁头和盘面看似挨着其实距离等同于歼二十在离地面一米的地方高速飞行,一旦进入灰尘落到盘面上,磁头和盘面因灰尘发生碰撞,盘面被刮花,数据丢失,磁盘就报废了。另外磁盘必须防止抖动,抖动的话磁头就会上下摆动就可能会刮花磁盘。

磁盘的存储结构

以一个磁面来说,上面有多个同心圆,同心圆之间有间隙,每个同心圆都是磁道。磁道的表面由一些磁性物质组成,可以用这些磁性物质来记录二进制数据。磁道上分出的一段就是一个扇区,一个扇区上存储512个byte(字节),每个扇区都是。外圈的扇区大,字节密度小,内圈扇区小,字节密度大。

在一个磁面上寻址

先找到在哪个磁道,再找到再哪个扇区。磁头来回摆动的时候就是在确认在哪一个磁道,盘片旋转的时候就是让磁头定位扇区。

一摞磁盘来说,所有相同位置的磁道就是相同大小的同心圆,摞起来抽象正视图就是一个柱体,**磁盘中所有盘面的同一个磁道被称为一个柱面,可以说,柱面和磁道是等价的。**磁头的个数等于磁面个数,且磁头是共进退的,一个磁头找到有一个磁道就相当于一摞磁头找到了一摞磁道,相当于磁道找到了相应的柱面。

image-20230131153848711

在磁盘上寻址

先定位在一摞磁头先定位在哪一个磁道(柱面—cylinder),然后定位在哪个磁头(head)即定位盘面,最后定位在哪个扇区(sector)。磁盘中定位任何一个扇区,采用的是CHS定位法。同样的也可能定位任意多个扇区。

磁盘的逻辑结构

小学的时候用的随身听,磁带坏了就拆开,发现里面的两卷磁带拉开就是一条带状结构体。

image-20230131160606303

同样的,可以把磁盘抽象成线性结构。如果一个此面能存储500GB的数据,那么抽象成的显性结构也能存储500GB的数据。

image-20230131160829052

在线性结构上,可划分为可数数量的盘面,在盘面内可以划分为可数数量的扇区,那么只要知道了扇区的下标就定位到了在对应的扇区。这种方法在操作系统内部称为LBA地址。【LBA全称是Logic Block Address(逻辑块地址)】

image-20230131161943204

假设磁盘有2块磁盘,一共四个盘面,每个盘面有10个磁道,每个磁道有100个扇区;那么磁盘的总容量就是4 * 10 * 100 * 512 字节。下标范围就是 4 * 10 * 100 。 一面个盘面就有1000个扇区。 那么在软件层知道某块数据的lba地址就能在磁盘上相应的找到。

image-20230131165203017

操作系统进行逻辑抽象的意义

那为什么不直接用CHS定位法,而是要抽象出LBA法呢?

磁盘内部使用的是CHS定位法,而别的外设可能不是,那么抽象出一套方法所有的外设都共用同一套方法就便于管理外设,也避免了OS的代码和硬件强耦合。

磁盘进行IO的文件大小

虽然对应的磁盘的访问的基本单位是512字节,但依旧很小,所以OS的文件系统定制的进行多个扇区的读取->1kb、2kb、4kb(主流)为基本单位。当读取的数据小于4kb时,也得把4kb的数据加载到内存中。这样的操作可以减少IO次数、提高数据命中率,提高了IO的效率。计算机中的局部性原理很好的证实了这一点。

内存被划分成了4kb大小的空间—该空间称为页框 磁盘中的文件尤其是可执行文件按照4kb大小划分成块—这样的空间叫页帧

磁盘的分治

假设这里有500GB大小的磁盘,对500GB的数据进行管理;500GB分为5个区,第一个区为100GB,那么对500GB数据进行管理分为对一个区100GB的数据进行管理;一个区100GB分为若干个组,一个组5GB,那么对100GB的管理分为对5GB的一个组进行管理。

image-20230131203100766

实际上,每个组的的第一部分是Boot Block (启动快),这与计算机的开机有关。后面才是各个组

image-20230131201429266

Super Block(超级块)

bolck和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了,所以Super Block存放在分组里,但并不是每个组都有Super Block,但是肯定每个区都有几个,以便当最常用的Super Block被破坏后,把别的Super Block的数据拷贝过去还原。

文件等于文件的属性+文件内容;文件属性放在inode中,文件内容放在Data Block中。

inode

inode是固定大小的,一般为128字节或者256字节。一个文件对应的一个inode,文件的几乎所有属性都存放在inode中,但文件名不在inode中存储。inode为了区分彼此,每一个inode都有一个id

如图三个文件各有各的inode的id

image-20230131204755516

inode里面存储着文件属性,还存储着一个数组(映射表)。数组一般有16个元素,从第0个元素开始存放着对应块数据结构的指针来找到对应的块数据结构。但是到第12个元素存放的指针指向的块数据结构,该块数据结构也存放着指向其他块数据结构的指针,这叫一级索引;第13个元素指向的块数据结构也能指向其他块数据结构,这些块数据结构也能指向其他块数据结构也能指向其他块数据结构。这叫二级索引。后面以此类推,这样inode里面就能存放大量的快数据结构的位置。

image-20230131215658714

查看文件内容时,就拿着文件的inode去到inode Bitmap找到对应的inode看是否存在,然后找到对应的inode的指向的块数据结构就能拿到数据。删除文件时只需要把文件对应的inode在inode Bitmap里把1至0即可,再去把Data blocks中的块数据结构的比特位删掉即惰性删除。所有我们把文件删除了也是可以把文件恢复的。

但是我们有查看文件一般用的是文件名而不是inode,目录也是文件,也有对应的inode和数据块,**目录的数据块存放当前目录底下的文件名和inode的映射关系!**所以在同一个目录底下不能存在同样文件名的文件! 所以目录天然需要写权限,文件在创建时需要在目录底下写文件名和对应的映射关系;所以目录天然需要读权限,在目录下罗列文件需要在目录下找到对应的inode并读取才能读到文件属性。

inode Table

inode Table保存了分组内部所有可用的(已使用+未使用)inode

Data Blocks

保存的是分组内部所有文件的数据块

那么在创建文件时,先进入inode Table里找到未使用的inode中填充文件属性,然后再去Data Blocks里找到未被使用的数据块,把数据填充进去。那么寻找inode和数据快的操作由谁来做呢?

inode Bitmap

存放的是inode对应的位图结构。比特位的位置与当前文件对应的inode的位置一一对应。0表示inode未使用,1表示inode已使用。

Block bitmap

存放的是块数据对应的位图结构。比特位的位置与当前文件对应的块数据的位置一一对应。0表示块数据未使用,1表示块数据已使用。

但是寻找未被使用的inode和未被使用的块数据结构本身也是需要计算的,且该计算效率较低,也就又存在了Group Descriptor Table块组描述表这个结构。

Group Descriptor Table

块组描述表(GDT) 存放的是对应分组的宏观的属性信息,比如被使用的和未被使用的inode有多少,被使用的和未被使用的块数据结构有多少。

软硬链接

在磁盘上找文件用的不是文件名,而是inode,Linux还可以让多个文件名对应同一个inode

硬链接

指令: ln 文件名 硬链接名

1、硬链接的文件属性和原文件相同,inode、权限、日期、文件内容大小等等相同。

2、硬链接没有创建新的文件—>没有给硬链接分配独立的inode,既然没有创建新文件,就没有自己的属性集合和内容集合。用的内容是别人的inode和内容。

3、硬链接的本质就是在指定的路径下,新增文件名和inode映射关系

image-20230202110659486

image-20230202111445699

4、向硬链接写入数据,原文件

删除原文件后,引用计数减减,所以引用计数又称硬链接数。当一个文件的硬链接数为0时,文件才算真正被删除。

image-20230202111642172

软链接

指令:ln -s 文件名 软链接名

Linux的软链接等同于windows下的快捷方式!

1、软链接是独立的文件,有独立的文件属性和文件内容。 和硬链接的区别就是软链接有独立的inode

2、软链接的文件属性开头是’l’,代表链接文件

image-20230202114406847

2、向软链接写入数据,原文件会因此改变,软链接文件本身不会改变

image-20230202114045042

3、软链接的本质是在把原文件的文件路径存入软链接的data block中,软链接 链接的是文件名,而不是文件的inode(类似windows下的快捷方式)

image-20230202115101747

4、指向的文件如果被删除,那么软链接报错:可以看到原文件inode 926589还存在一个指向,但原文件myfile.txt已经被删除,软链接也会报错(印证了第3点)

image-20230202114350643

取消链接: unlink 链接名

取消链接可以用unlink也可以用rm删掉链接(文件),且删除链接对原文件没有影响

image-20230202115636735

链接的应用

比如软链接,如果要运行某个目录下的程序,就需要输入程序的路径,这样比较麻烦

image-20230202122409921

那么链接一下,输入链接名即可运行程序

image-20230202122514035

理解.和…

理解.

我在当前目录创建一个新目录mydir,查看到inode是921783,硬链接数是2;进入到mydir目录,查看到.的inode也是921783,可以知道,.是硬链接,意思是当前目录,相当于运行程序时会用./程序名即运行当前目录的程序;即这个inode和mydir目录及这个inode和.分别有映射关系即一共两组映射关系

image-20230202131451887

理解…

在目录mydir里再创建一个一个目录dir,可以看到mydir的inode是921783,硬链接数是3;进到mydir目录可以看到当前目录.的inode也是921783;再进到dir目录可以看到…的inode也是921783

…是硬链接,表示上级目录;即和上级目录的inode同样有映射关系

image-20230202132205427

image-20230202132703530

动态库和静态库

在linux系统下,编译c语言通过【gcc -o 可执行文件 编译的文件】可形成可执行文件。形成可执行文件依次通过预处理、编译、汇编、链接。而给编译器传递-c则可形成编译文件对应的.o 文件—可重定向二进制文件,.o文件已经完成了预处理、编译、汇编,最后把全部的.o文件加上库链接起来就可以形成可执行文件了。

恰恰是最后一步,可重定向二进制文件和库是怎么链接起来的呢?

通过库的使用者看待库

现在我写了一个main.c,并且也写了一个两个.h文件和两个.c文件,.h文件有函数实现的方法,.c文件有函数的实现;当函数的拥有者不想给使用者源代码(.c文件)时,就把.c形成的.o(可重定位二进制文件),和.h(函数的方法)给使用者,使用者照样可以链接形成可执行文件

image-20230204153431546

把.o文件(方法的实现),.h文件(有什么方法)给使用者,使用者可以利用这些文件形成可执行文件,这就是库的最初思想

当要使用的.o文件越来越多,就有了一个思想:把多个.o文件打一个包,即库文件,然后把库文件和对应的.h文件给对方即可! 库是.o文件的集合

静态库

静态库特征:程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库

ar打包文件

指令【ar -rc 要形成的包文件的名称 要打包的文件】rc(replace and create:打包形成的文件原来存在则替换,不存在则形成)

image-20230204155542980

打包后通过file查看到该文件是当前的归档文件(档案文件)

image-20230204155747594

把.o文件打包了还有.h文件也要打包

output 发布

output 发布文件,创建一个mylib路径,把.a文件放到路径底下的lib路径,把.h文件放到路径底下的include路径。

image-20230204160831083

然后就能看到一个路径底下有我们发布的文件

image-20230204161326674

单单是发布了还不行,还得打包起来好发送给别人,tar 压缩打包

image-20230204161443402

这个压缩包就可以放到yum原上给别人下载使用

现在路径底下只有main.c和压缩包,等于这个压缩包就yum上的库,我刚下载下来,

image-20230204161714806

编译代码时,展开头文件只会在两个地方搜索头文件,一是当前路径(源文件路径)下搜索,二是在系统指定的路径下搜索;


生成静态库

链接静态库有两种方法,一是指定库和头文件,二是把库和头文件放在系统指定的路径

指定库和头文件生成静态库

把压缩包解压后,需要的头文件和库文件在mylib路径底下,所以头文件并不是在源文件路径底下

image-20230204163153877

编译时使用第三方库,需要指明第三方库的头文件路径(-I【大写i】),需要指明第三方库文件路径(-L),需要指明库文件名称(-l【小写L】)

image-20230204163847082

然后就能成功编译

image-20230204164216858

然而查看该文件时,可以看到这个库显示的是动态链接。事实上,对于单个库而言,gcc编译默认是动态链接(建议性),如果是静态库,就选择以静态库的内容拷贝到源文件里的形式实现。而一次编译链接形成可执行程序,链接的不仅仅一个库,则对于多个库时,对于只有静态库则拷贝静态库对应内容到源文件,而对于动态库则动态链接,只要有一个动态库就都是动态链接的形式!

image-20230204165216746

拷贝到系统指定的目录底下生成静态库

把第三方库的头文件拷贝到/usr/include/目录底下,把库文件拷贝到/lib64/目录底下;这就是在yum源下下载资源的过程:把压缩包下载下来,系统自动解压,然后安装即把对应的文件拷贝到对应的地方去所谓的安装的本质就是拷贝

image-20230204175656765

然而直接编译也会报错,刚刚咱们写的是第三方库,就算拷贝到了系统指定的目录底下,还是得说明库文件是哪个

image-20230204180231995

image-20230204180435619

然而最好还是不要把自己写的文件拷贝到系统底下,这样会污染系统指令池


动态库

  1. 程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
  2. 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
  3. 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
  4. 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

生成动态库

fPIC:产生位置无关码(position independent code)

要生成动态库时,.c文件编译出来的.o 文件要携带fPIC(形成.o文件在动态库中的偏移量【相对地址】)

image-20230204195156434

shared: 表示生成共享库格式

要生成动态库时,打包.o 文件的库文件不需要ar打包,直接用gcc就可以行,但是要携带shared

image-20230204195547343

然后按照之前对待第三方静态库的方法对待第三方动态库,然而报错,且说明.so文件找不到

image-20230204195752715

在编译的时候告诉了gcc头文件路径、库文件路径和库名称,但是当把程序编译完,程序运行起来的时候,OS和xshell也需要知道这些信息,然而我们写的第三方库不再系统路径底下,OS和xshell无法找到!

那这么让操作系统找到第三方库呢?这里有四种方法:一是在可执行文件的路径底下或者系统默认路径下给库文件建立软链接;二是配置环境变量 LD_LIBRARY_PATH;三把库文件和.h文件拷贝到系统指定的目录底下生成动态库,方法和生成静态库的第二种方法一样后面就不讲了,四是配置文件

在可执行文件的路径底下或者系统默认路径下给库文件建立软链接

在可执行文件的路径底下给库文件建立软链接

image-20230204222357660

在系统默认路径下给库文件建立软链接

image-20230204223251200

image-20230204223301603

配置环境变量 LD_LIBRARY_PATH

这里的配置环境变量只对本次登录有效,下次登录就会失效

操作系统除了在系统默认路径下搜索,也会在LD_LIBRARY_PATH这个环境变量下搜索

image-20230204214122134

把库文件的路径配置到该环境变量里

image-20230204214739868

然后查看该可执行文件时系统就能找到库文件了,而且运行也不会报错

image-20230204214834365

配置文件

在/etc/ld.so.conf.d/路径下有很多配置文件(.conf结尾的),通过

image-20230204220831557

把库文件的路径写道到/etc/ld.so.conf.d/路径的任意一个文件即可

这里我创建一个.conf文件,把库函数路径写进去,注意这里要sudo提权

image-20230204221303798

image-20230204221323327

ldconfig:更新文件配置

配置完文件后,要通过ldconfig更新文件配置

image-20230204221540839

后面可执行文件就可以运行了

image-20230204221728303

动静态库的加载

静态库不需要加载:在编译可执行程序时,就把静态库里的代码拷贝到程序的代码区,需要一份就拷贝一份,要是有多份需要就拷贝多份;可执行程序文件本身也有逻辑地址,静态库在该文件的代码区从0x00000000 到0xffffffff编址,当可执行程序加载到内存中时,这重复的代码就会造成空间浪费

image-20230204232925545

理解动态库的加载

在编译动态库中某对应的.o文件地址时:由于组成动态库的可重定向二进制文件是通过位置无关码fPIC生成的,所以这个地址不是.o的地址,而是**.o文件在动态库中的偏移量**;

然后可执行程序运行时:操作系统会把磁盘中的可执行程序加载到内存,再通过页表映射到进程地址空间的代码段,然后开始执行代码,当执行到动态库时,操作系统发现这个函数链接的地址是一个动态库的地址,且该地址是一个外部地址,操作系统会暂停程序运行,开始把动态库加载到内存中;

加载动态库:把磁盘上的动态库加载到内存中,然后通过页表映射到进程地址空间的共享区,立即就在共享区确认了动态库在进程地址空间的起始地址,然后操作系统跳回到代码段继续执行代码

执行代码:操作系统拿着.o文件在动态库的偏移量去到共享区从起始地址开始找到.o文件然后执行动态库的代码,继续执行后面的代码。这就是完整的动态库的加载过程。

image-20230205001325612

因此程序中多个函数需要链接相同动态库中的相同代码时,就只需要把一份对应代码加载到内存中,不会出现重复代码。

猜你喜欢

转载自blog.csdn.net/m0_71841506/article/details/128887541