• 冰冰学习笔记:linux的文件系统


    欢迎各位大佬光临本文章!!!

    还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正。

    本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬、帅哥、美女点点支持,您的每一分关心都是我坚持的动力。

    我的博客地址:bingbing~bang的博客_CSDN博客https://blog.csdn.net/bingbing_bang?type=blog

    我的gitee:冰冰棒 (bingbingsupercool) - Gitee.comhttps://gitee.com/bingbingsurercool


    系列文章推荐

    冰冰学习笔记:《基础IO》

    冰冰学习笔记:《进程程序替换》


    目录

    系列文章推荐

    前言

    1.磁盘的结构

    2.文件系统与inode

    3.软硬连接

    4.动静态库的制作

    4.1静态库(.a)

    4.2动态库(.so)


    前言

            Linux系统的设计理念就是一切皆文件。我们之前在基础IO阶段打开创建的文件在进行写入和读取时需要将文件加载到内存中打开,然后才能进行读写。那有没有没有被打开的文件呢?答案是有的,磁盘级文件就是没有被打开的文件。

    1.磁盘的结构

            我们知道,内存文件掉电易失,不能长时间存储。而磁盘文件是永久性存储介质,例如光盘,SSD,U盘,磁带等存储设备。

            磁盘结构由磁盘盘片,磁头,伺服系统,音圈马达等构成,每个盘面够可以存储数据。数据的存储实际上是通过磁头来改变盘面的存储介质的正负性来达到的。其中扇区是磁盘存储的基本单位,每个扇区512字节。磁头通过CHS寻址方式(哪个盘面,哪个磁道,哪个扇区)找到每个扇区进行读写。

            虽然磁盘的基本单位是扇区,但是操作系统和磁盘进行IO的基本单位是4KB,原因在于512字节太小了,读取时会照成多次的IO降低效率,而如果将其设计成相同的,在磁盘发生变化时,操作系统也需要发生变化,并不划算。

    2.文件系统与inode

            简单了解完磁盘的结构后,我们知道磁盘是存储文件的,操作系统想读取文件就会去访问磁盘,但是操作系统并不会一个扇区一个扇区的访问,而是直接访问一个block块,通常大小为4kb。多个数据块block就构成了一个文件的Data Block,这里面存储的就是文件的内容。

            但是文件不仅仅只包含内容,文件还包含属性。文件的属性存储在inode Table中inode Table是一个大小为128字节的空间,存储的是文件的属性,如inode编号,文件权限,拥有者,所属组等。并且一个文件一个inode编号,一个inode块。

    使用ls -i命令可以查看文件的inode编号。

    磁盘在被抽象后就如同下面的样子,方便我们进行管理。

            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是否空闲可用。

            而我们如果想找到一个文件,我们只需要找到文件对应的inode编号就能找到该文件的inode属性集合。如果想找到文件的存储内容,在文件的属性集合中存在一个block数组,里面存储的就是该文件对应的Data Block的编号。当文件特别大时,block数组中存不下所有占用的data block时,会将其他的data block块的编号存储到数组指向的data block块中。

            在磁盘进行格式化时,会将磁盘分为两个区域,一个是inode区域,一个是data block区域。这些数量都是固定的,当inode用完的时候,data block存在,创建文件会失败。当inode存在data block用完时,创建文件也会失败。

            这里还要注意一点,文件属性中并没有该文件的文件名,文件名是存储在目录结构中的data block中的。当我们进入目录并创建一个新文件时,实际上我们是拿到了目录的inode,并通过inode找到目录的属性,从而确定目录的data block,在data block中存储的并非只有新文件的文件名,还有文件的inode编号并将二者做出映射关系,其中文件名是用户输入的,inode编号是新文件创建时系统提供的。

            那么创建文件时系统做了什么呢?

            创建新文件时,系统通过inode位图找到第一个没有被占用的,将其置为1,同时拿到inode编号,并在inode表里填入新建文件的属性信息。最后会通过目录的inode编号找到对应目录的data block数据块,在数据块中将用户输入的文件名和inode编号进行关系映射。

            删除文件时操作系统做了什么呢?

            删除文件操作系统并没有将内容情况,而是将文件对应的inode Bitmap和block Bitmap置为空。因此文件删除后并非不能恢复,只要inode还能找到,并且对应的data block 没有被覆盖就可以恢复出来。

            查看文件时系统做了什么呢?

            操作系统找到目录对应的inode编号,从而目录的属性信息,并访问data block数据块,拿到文件名和inode编号的映射关系,从而找到文件的属性信息,然后读取文件的data block数据块。

    3.软硬连接

    (1)硬链接

            通常情况下操作系统分配的inode对应唯一的一个文件名,但是操作系统允许同一个inode有不同的文件名,这就是硬链接。硬链接没有独立的inode,与链接指向的文件共享inode,因此硬链接并不是单独的文件。换句话说,我们可以将硬链接理解为原文件的引用,硬链接的文件就是原文件自己,两个中的任意一个变化都会相互影响。

    创建硬链接:ln 需要连接的文件路径和文件名 连接后文件的名字

            在使用ln命令创建硬链接后,新文件filelink与file的inode相同,原本file文件中的计数为1,链接后变为2.这就意味着有两个文件指向此inode。在删除的时候并不是直接将原文件删除,而是计数先减1,等计数为0的时候才会完全删除该文件。

            当我们修改其中任意一个文件的时候,另一个文件也会发生变化。

            在我们删除硬链接的时候,原文件不会变化,引用计数会减少

    删除硬链接: unlink  文件名

            在我们创建一个目录的时候,文件的引用计数(硬链接数)默认就是2,这是为什么呢? 这是因为我们在进入目录后,会有一个.指向当前目录,..指向上级目录,此时会发现上级目录的硬连接数变为3。换句话说我们可以直接根据一个目录的硬链接数来推断里面由多少目录结构(硬链接数减2)。

    (2)软连接 

            与硬链接相对的就是软连接,软连接和硬链接最大的不同就是软连接具备独立的inode,软链接是一个独立的文件。

    创建软连接:ln -s 需要连接的文件  软连接文件名

            softlink文件在hello目录下,它软连接了上级目录中的file文件,在hello文件下直接访问sortlink文件可以得到file的文件内容,因此我们可以认为软连接的功能类似windows下的快捷方式,甚至可以理解为sortlink的文件内容就是file文件的路径。

            当我们对文件内容进行修改:

            两个文件的内容是一样的,因为sortlink只是指向file文件的路径,修改file文件后,通过softlink访问到的还是file文件本身。 即便是对sortlink进行修改,实际上也是对file文件进行的修改。

    4.动静态库的制作

            在实际开发过程中,我们难免会制作或者使用一些库来帮助我们进行开发,使用别人写完的库,简单高效。那么如何制作和使用库呢?

    4.1静态库(.a)

            静态库是程序在编译连接的时候将库中的代码拷贝到可执行文件中,程序运行的时候不在需要静态库。每个进程中都需要拷贝一份静态库的代码,这种方式容易造成代码冗余。

            当我们具备.o文件时,我们将.o文件和头文件拷贝到自己的可执行文件的目录中,并直接在自己的程序中调用.o文件中的方法是可以直接用的,但是这样频繁的拷贝容易造成文件丢失,并且每次运行我们都需要手动拷贝操作。

            如果我们能将这些.o文件统一进行管理,打包在一起就可以很方便的管理这些文件了。将.o文件打包的过程就是制作静态库。

    制作静态库:ar -rc libxxx.a xxx.o xxx.o

    静态库名称必须以lib开头.a结尾

            如果我们想对这个库发布,就需要对其打包,将.h文件放在一起,lib文件放在一起。我们使用makefile进行统一管理。

    Makefiel的命令如下: 

    1. libmethod.a:mymath.o myPrint.o
    2. ar rc libmethod.a myPrint.o mymath.o
    3. mymath.o:mymath.c
    4. gcc -c mymath.c -o mymath.o
    5. myPrint.o:myPrint.c
    6. gcc -c myPrint.c -o myPrint.o
    7. .PHONY:output
    8. output:
    9. mkdir -p output/lib
    10. mkdir -p output/include
    11. cp ./*.h output/include
    12. cp ./*.a output/lib
    13. .PHONY:clean
    14. clean:
    15. rm -rf *.o libmethod.a output

            那我们怎么使用这个静态库呢?我们知道,平常我们在编译C语言的文件时,使用的就是C语言的库我们并没有做什么其他的工作,直接使用就可以。但是我们使用自己的库是不能编译成功的。因为在编译连接阶段,编译器根本找不到我们的库以及头文件。

    因此我们如果想使用自己的库,就有如下两种做法。

    (1)将头文件,库文件拷贝到系统目录下,使用gcc main.c -l+静态库名称进行编译

            gcc头文件默认搜索路径是:/usr/include

            库文件的默认搜索路径是:/lib64 或者/usr/lib64

            现在我们将自己的库文件和头文件拷贝到这两个目录下

            现在我们执行运行命令是不能直接运行的,我们应当指定库名称。

            但是这种方式并不推荐,因为如果库中存在bug,这会污染系统的库文件。

    (2)显示告诉路径

    此种方式相对安全,不会影响内部的库。

    4.2动态库(.so)

            静态库毕竟是将代码拷贝进原程序中,因此在多次使用后会照成代码冗余的情况,这就需要使用动态库来解决这种情况。

            动态库的制作和静态库类似,只是需要添加-fPIC选项先生成与位置无关的.o文件,然后使用shared 选项生成动态库。

    生成与位置无关的二进制文件:gcc -fPIC -c mymath.c -o mymath.o

    生成动态库:gcc -shared myPrint.o mymath.o libmeth.so

            我们将生成的动态库放到自己的目录下进行使用时,也需要了解使用方式。

            首先gcc编译器是默认使用动态编译的,当同时具备同名动态库和静态库的时候,将使用动态库,如果我们想使用静态库就需要添加-static选项。动态库是在程序运行期间去寻找并连接的,如果我们使用静态库的方式指定文件路径进行访问是不能编译的,原因在于我们告诉路径只是告诉了gcc,gcc只负责编译,并不负责连接,这需要加载器或者操作系统,因此我们应该需要把库的位置和名称告知操作系统。

     因此我们想正确使用动态库需要一下几种方式:

    (1)拷贝到系统目录下,与静态库方式一致。

    (2)导入环境变量

    找到动态库的文件目录,然后使用下列命令导入到环境变量中

    查看环境变量配置:echo $LD_LIBRARY_PATH

    导入环境变量:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的路径

    但是这种方式并不长久,当我们重新登陆xshell后,设置的内容就不见了。

    (3)修改配置文件,一劳永逸

            在系统目录/etc/ld.so.conf.d/文件下创建文件xx.conf(一定要以conf结尾)将自己的路径写入到新建文件中,使用ldconfig命令进行更新。

    (4)使用软连接,在系统路径/lib64路径下创建软连接指定库的路径,然后就可以直接使用。

            每一个动态库被加载到内存,映射到进程的地址空间,映射的位置可能是不一样的,但是因为库里面是相对地址,每一个函数定位采用的是偏移量的方式进行寻找,因此,只要知道这个库的相对地址,那么库的起始地址+函数偏移量就可以在自己的地址空间中访问库中的所有函数了。堆栈相对而生,中间的区域就可以加载动态库。

  • 相关阅读:
    分享一个用python写的本地WIFI密码查看器
    A Survey on Fairness in Large Language Models
    亚马逊、速卖通、国际站测评需要什么条件?怎么养号?
    【实用技巧】Unity的Text组件实用技巧
    【JavaWeb】第一章 HTML标签
    <七>理解多态
    对批改网禁止复制粘贴问题的破解
    Shell脚本中常见的几种变量类型
    耗时半月,终于把牛客网上的Java面试八股文整理成了PDF合集
    我与梅西粉丝们的世界杯观球日常
  • 原文地址:https://blog.csdn.net/bingbing_bang/article/details/127716847