zl程序教程

您现在的位置是:首页 >  系统

当前栏目

Linux文件系统

Linux 文件系统
2023-06-13 09:17:43 时间

文件系统

磁盘

磁盘是一个机械机构,不同于硬盘。 现在普通人很少能见到磁盘了。 磁盘是一个机械结构+外设=访问很慢(只是相对于硬盘很慢)

磁盘的物理结构

磁盘是这种结构,像光碟一样的是盘面,主要是用于储存内容的,两面都能用,盘面是光滑的,盘面中间的是马达,控制盘面旋转。像一个指针一样的头叫做磁头,每个面都有一个磁头,是用于读写磁盘内容的,磁头和盘面没有接触,后面的是马达,用来控制磁头进行上下移动。电路板那里是电路硬件+伺服系统,用于控制磁头何时进行读写的,也就是正负电二进制控制。 其次,磁盘是有多片的,要防止抖动,不然摩擦或者是碰撞可能会导致数据丢失或者是磁盘无法使用。 还有不能拆开,拆开基本等于报废,因为磁盘不能落灰,整个磁盘在制作过程中是载真空室内,并且密封性也是非常的好。

磁盘的储存结构

每个盘面上其实是有磁道的,上面是一圈一圈的,我们看不到,磁头摆动也是为了找磁道。 磁盘寻址的时候,是512个byte访问一次,这也是一个扇区。 那么在一个扇形区域内,每个表面看扇区大小不一样,实际上储存的都是512byte。 那么磁头是如何定位一个扇区的呢?就是盘载旋转的时候就是在定位扇区,磁头摆动是在定位磁道。

这些磁头是连在一起的,一起摆动,共进退的,这就是为什么有柱面这个概念了。 柱面就是磁道每个盘面相同位置的磁道上下连在一起。 这样的话就是先定位磁道,在定位是哪一个盘面,最后在定位扇区。这种定位的方式是CHS定位法。

磁盘的逻辑结构

虽然磁盘的盘面是圆的,但是在操作系统看来他就像数组一样。 就好像录音机里面的磁带一样,虽然是卷起来的,但是也可以拉直。 那么假设一个磁盘是500GB:

对磁盘的管理==对数组的管理 那么我们如何定位扇区呢?把磁盘想象成线性结构,只要知道了数组的下标就可以,这就是等于线性空间的地址。 这个下标叫做LBA地址,逻辑块地址。 举个例子: 盘面有4个,磁道/面:10,扇区/磁道:100,扇区512字节大小。 总共大小是:4*10*100*512. 下标范围:4*10*100. 一面是:10*100

假如说下标为123: 123/100在一号磁道 C 123/1000在第0面 H 123%100在23号扇区 S

这就是CHS方法。 操作系统进行逻辑抽象是因为便于管理,还有一个原因是不想让操作系统的代码和硬件强耦合,不然明天换个配置,你要换一种代码,后面又换怎么办。

文件系统

分治

虽然对应的磁盘访问基本单位是512字节,但是依旧很小。 所以操作系统定制的进行多个扇区的读取->1KB,2KB,4KB(现在基本都是4KB)为基本单位。 哪怕是要读取或者修改1bit的内容,都必须将4KB的内容放进内存中进行读取或修改,如果必要,再写回盘。(局部性原理:如果我们访问某个部分,那么在他周围的数据是非常大概率会被访问的,这也相当于以空间换时间)

内存中被划分成4KB大小的空间——页框。 磁盘的文件尤其是可执行文件——按照4KB大小划分好的块——页帧。 文件放在内存就是页帧放进页框,这就是操作系统和文件的耦合。

假设一个磁盘有500GB:

这种思想叫做分治。 那么也就是说,我们如何去管理这个5G的区域,其他区域也可以这样管理,这种管理的方法复制过去就好了。 所以讨论文件系统,只要讨论这5G就可以了。

Boot Block:操作系统加电开机启动的时候,所有的信息都在这个区域。 Block group 0:超级块,块组0。 Super Block:超级块对象,保存的是整个文件系统的信息。 如果想清空哪个盘里面的所有数据内容,其实就相当于重写文件系统。一个磁盘,第一步是分区,第二部就是格式化,也就是写入文件系统。 文件系统的信息有分区的使用状态,分区的每个组的状态和信息。 既然这么重要,为何放在这里呢?其实在大部分文件系统中,块组前几个开头就是Super Block,这就相当于备份。 假如说某个Super Block出问题了,发现的原因其实就是Super Block对比,然后询问是否进行恢复。

inode与数据块

文件 = 内容 + 属性 Linux中,文件的属性和内容是分批存储的。 保存文件属性的叫inode块:具体大小跟文件系统的版本有关,我的是ext3,128字节。 inode是固定大小,一个文件对应一个inode(硬链接除外,下面会说),里面包含了一个文件几乎所有的属性。注意,文件名并不在里面! inode为了区分彼此,每一个都有自己的ID。

inode Table:这个就是用来保存分组内部已使用+未使用的inode。 文件内容是存储在data block(数据块)中的:这个大小是随着应用类型的变化而发生大小的变化。 Data block:保存的是分组内部所有文件的数据块。 那么,上面的inode与数据块,他不可能随便去使用,肯定要先找到未被使用的才可以根据需求进行使用。 inode Bitmap:inode的结构位图,按照二进制,一个比特位就是一个inode,如果是0就是未被占用,1就是已被占用。 第几个比特位就代表是第几个位置,位图中比特位的位置和当前文件对应的inode的位置是一 一对应的。 也就是说,删除一个文件速度会很快,只要讲相对应的比特位置零就可以了! Block Bitmap:这个是数据块对应的位图结构,和上面inode的位图结构同理,位图中的比特位和当前的data block对应的数据块位置是一 一对应关系! Group Descriptor Table:块组描述表,对应分组的宏观属性信息。 在查找一个文件的时候统一用的是inode编号,它可以跨组的,不同的组里面是不同的,但是不能跨分区。

data block blocks[15]是储存数据块的,假设某个inode编号对应的数据块是这样存储的:

那么,如果inode拿到了对应的这个编号,我们要查找内容就找data block blocks[15],发现只有4个链接的,那么把数据块中的1,2,7,8,13给连接在一起就是我们要找的文件内容了。 可是,只有15个储存数据块地址内容也太小了,一个才4KB,其实后三个才是重头戏,12储存的不是某个数据块的id,而是指向了某个数据块,这个数据块里面储存的是其他数据块的id,一共可以存1024个,这个叫做一级索引。

13储存的是1024个储存1024个数据块的地址,这个叫二级索引。 14就是三级索引。 文件名在哪里? 我们平时也不会去用inode,用的都是文件名,那么文件名放在了哪里呢? 首先,任何文件都在某个目录下,那么目录是文件,也有自己对应的inode,只要有自己的inode,就有对应的数据块,那么目录的数据块放什么呢?放的就是当前目录下的文件名和inode的映射关系。 这就是为什么在一个目录下创建一个文件为什么要有写权限和读权限了,因为创建文件代表需要向这个目录中写入inode与文件名,读取目录下的文件需要去找当前目录的文件名才可以找到对应的inode。

软硬链接

什么是软硬连接

在一个目录下创建一个文件,然后创建一个硬链接与软连接。

这里,inode相同的hard_log.txt是log.txt的硬链接,soft_log.txt是log.txt的软链接。 软连接有独立的inode,可以被当成独立文件看待。 那么硬链接呢?

这次我们更加的肯定,log.txt与hard是同一个文件了。 但是2是什么呢?为什么软链接是1呢? 既然没有给硬链接单独分配inode,那么久没有自己的属性和内容,用的就是别人的inode和内容。

引用计数是硬链接数! 那么我们删除log.txt试一下。

原来log.txt对应的inode对应的文件没有被彻底删除,也就是说只有在inode中的引用计数归零的时候,这个文件才是真正被删除。 那么软链接这里却显示了文件已经消失,这说明软连接不是和inode有关系,而是和文件名有关。

这里又创建了一个同名文件,现在又显示链接成功了,但是inode已经不是原来的了。 软链接就相当于windows下的快捷方式一样。 删除链接文件用unlink 文件名也可以。

文件与引用计数

我们创建一个普通文件和目录,普通文件是一有一个硬链接的,因为文件名和它的inoded是映射关系。

那么为什么目录又两个呢?

一个点代表是当前目录的意思,这时隐藏文件,两个点是上级目录的意思,所以一个点与当前目录就是硬链接,一个点也是文件名。 但后我们在当前目录下创建一个目录。

然后回到上级目录发现硬链接数变成3个了,这是因为创建的lol目录的两个点是链接在empty目录的:

注意:操作系统不让用户给目录创建硬链接!

文件的三大时间

Access 最后访问时间(因为特殊原因,这个数值只有达到一定条件才会刷新) Modify 文件内容最后修改时间 Change 属性最后修改时间(属性包括大小的,所以有大部分时候改内容属性也会修改)

动静态库

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

什么是库

首先创建三个文件:add.h add.c main.c

这一步将add.c和main.c进行了汇编,就差链接了,add.o和main.o里面都是二进制。 既然是二进制,那么我们肯定是看不懂的,这个时候就可以让别人也来使用add这个方法了,只需要将add.o和add.h给他就好,头文件是有哪些方法,源文件是方法具体怎么实现的。 但是如果.o的文件太多了怎么办?那么我们可以将.o的文件尝试打包,给对方提供一个库文件即可。 所以,库就是多个.o文件打包成一个文件,然后分为静态库和动态库。库的本质就是多个.o文件的集合

库的使用

静态库

那么我们如何来打包呢?

add.c是加法实现方法,sub.c是减法实现方法。

用ar -rc选项去进行打包。

这时就形成了一个库文件。 这里要注意:交付一个库,就等于将头文件和库文件一起交付,不然会报错。 这里进行给库打包然后放在别的目录看看效果:

然后将test这个目录打包,然后放在其他目录:

然后将main.c放在同目录下。

我们知道,现在还不能编译,因为现在头文件和库没有在同一目录下: 编译器默认搜索头文件: 1.在同一路经下。2.在/usr/lib中。 编译器默认搜索库: 1.在/usr/lib中。2.指定路径下。(注意如果要链接第三方库,就算是在/usr/lib中,必须要有库的名称,库的名称是去掉前缀去掉后缀) 那么为什么我们之前从来没写过库的名字呢?因为,gcc和g++认识他们自带的库,所以不用带库名称。 格式:选项 -I 是指定头文件目录,选项 -L 指定库文件路径,选项 -l 库文件名。

刚才我们使用的是静态库,但是我们查看以后发现:

一没显示我们自己写的库,二显示用的是动态库,这是怎么回事呢? 首先gcc默认是动态链接,然后程序不仅仅只链接一个库,静态库和动态库都有。 gcc只是建议使用动态库,但是具体你想用静态还是动态取决于提供的是动态库和静态库。并且,只要有一个动态库,那么就要用动态链接! 显示动态库是因为默认用的是C的库,所以gcc只能把静态库拷贝到代码里面。 指令这么长,写的也确实很难受,如果有几百个第三方库,就更难受了,所以这里提供一个减少指令行的方法。 放在/usr/lib中。(第三方库的名称一定要带) 这里就不演示了,也不推荐放在里面,因为我们是用来测试一次或者是两次,删除的时候容易删除掉重要文件!

动态库

和动态库的步骤没啥区别,就是gcc多加了选项。 -fPIC: 产生位置无关码 shared: 表示生成共享库格式

然后我们来执行:

这里发现报错了,原因是没有发现这个目录或文件。

为什么找不到呢? 首先要考虑清除一件事:用户告诉了库文件的路径和库名,我们是告诉了谁? 我们是告诉了gcc,但是gcc编译完之后就不管了,形成可执行文件执行是系统的事情!库没有在系统的路径下! 所以系统和shell也要知道在哪里。 除了会在系统路径下,还会再一个环境变量去找。

LD_LIBRARY_PATH

这时运行成功了。

但如果我们想永久设置动态库搜索路径,这种方法是行不通的。 在这个路径有配置文件:

/etc/ld.so.conf.d/

这些配置文件是动态库在进行搜索时,自己定义配置conf文件方式,让操作系统找到动态库。 自己创建一个配置文件然后再里面写入动态库的路径: 记得提权输入

然后wq!退出,之后要刷新一下配置文件才行。

ldconfig

这个也要提权才行。 然后回到原来路径执行命令可执行程序就能通过了。

除此之外,还有一种方式就是软链接的方式:

这说明搜索库的时候会在当前路径下搜索。 如果不想再当前路径下搞软链接,也可以再/lib64路径下进行软链接,也可以将这个库拷贝到/usr/lib路径下。 那么如何使用别人的第三方库呢? 只要下载好了,用gcc只需要告诉库名字即可。

动态库加载

静态库是怎么加入代码中的 静态库的代码是拷贝在代码区。未来这部分代码通过相对确定的地址位置来进行访问。

静态库在程序进入内存之前就被拷贝进了我们的程序内部,进入内存,在虚拟地址空间中也是在代码区里面,这样是很浪费空间的。 静态库也是采用绝对编址的方式,假如说要使用某个函数,那么这个函数在整个程序中的位置必须确定。 动态库如何加载到内存

动态库是指定函数的地址写入可执行程序中。这个地址是什么地址呢? 之前gcc选项编译动态库的时候有一个产生位置无关码:fPIC。这种采用的是相对编址。 这个就相当于是一个坐标,以这个坐标为基准,某个人或物距离这个坐标多远,是这么用的。 相当于一个start+偏移量。

start相当于动态库的起始地址,偏移量就是用户要用的函数在这个库当中的位置。

那么写入的地址就是那个偏移量! 然后代码在运行的时候发现到了一个库函数,然后操作系统回去看内存中库函数定义的代码发现这个并不存在,地址也是一个外部地址,所以就回到了磁盘当中找动态库,然后加载到内存中,然后通过页表映射到共享区:

这就是为什么我们用ldd的时候他知道我们链接的哪个库。 一旦映射到共享区,立刻就能够决定这个库的起始地址。 上面步骤执行完之后,操作系统才会继续读取代码。 回到调用库函数的地方,然后再虚地址空间发现库函数的偏移量,然后去共享区的库找到该函数代码的位置。 如果有很多个进程,那么内存当中也只需要加载这一个库就够了,因为每个进程操作系统都会帮助我们去映射对应库再虚拟地址空间的位置。

博客同步到腾讯云社区

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1zogtjilt9us0