【Linux】基础IO(文件系统)

秦jh_ 2024-10-17 10:37:02 阅读 99

 🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343

🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12625432.html

9efbcbc3d25747719da38c01b3fa9b4f.gif

目录

理解文件系统

磁盘

磁盘的存储结构

软硬链接

软链接 

 硬链接​编辑

动态库和静态库 

静态库 

 动态库

 动态库加载--可执行程序和地址空间


前言

💬 hello! 各位铁子们大家好哇。

             今日更新了Linux基础IO的内容

🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

理解文件系统

磁盘

图1,2是磁盘,图3是服务器,磁盘插入到服务器的凹槽中。以前的电脑里都是磁盘,但现在大部分的笔记本里都是SSD。 

 

表面光滑的部分叫盘片。盘片:可读可写可擦除。一片两面都可以写。

光碟只可读。

盘片一般都是一摞的,有很多片。

旁边像时针的东西叫磁头,盘片的每一面都有对应的磁头。

磁盘的存储结构

磁盘读写的基本单位是512字节。 

如何找到一个指定位置的扇区?

找到指定的磁头。 Header找到指定的磁道(柱面) Cylinder找到指定的扇区。  Sector

这种在硬件上定位某一个扇区的寻址方案叫CHS定址法,即需要三个参数。

文件其实就是在磁盘中占有几个扇区的问题。 

我们把磁盘抽象成巨大的线性结构

 假设是两片四面。尽管扇区大小有差异,但是每个扇区的内存大小都是一样的。每一面上都有很多扇区,最后就把磁盘抽象成数组 。

有了数组,就可以通过下标找到某个扇区,但是磁盘只认CHS,所以要通过算法将下标转换成CHS的地址。我们找到下标后交给磁盘,磁盘内部会把线性地址转换成CHS的地址。进而定位到某一个扇区里。

一般而言,OS未来和磁盘交互的时候,基本单位为;4KB,而不是512字节(一个扇区512字节),因为一次读512字节太少了,要提高效率。 

所以4KB=8个连续的扇区

系统把这8个连续的扇区称为块大小(数据块)。

8个扇区为一个块,块号*8就等于每一个块的第一个扇区的下标,连续往后读就可以知道整个块的下标了。

对于OS而言。未来我们读取数据就可以以块为单位了。

这里的每一个块号的起始地址称作LBA 即逻辑区块地址。 

磁盘的空间很大,需要分区管理,只要能管理好其中一个区,剩下的其他区就可以照搬它的管理方法,全部管理好。 

  假设这里管理其中一个区,这个区还是太大,就要在这个区里面继续分组。只要把一个组管理好了,这个区的每一个组就都能管理好了。

 上图下半部分是磁盘文件系统图。

Linux文件系统特点:文件内容和文件属性 分开存储。

Block Group:文件系统会根据分区的大小划分为数个Block Group。而每个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是否空闲可用。inode表:存放文件属性 如 文件大小,所有者,最近修改时间等数据区:存放文件内容

 Linux中文件的属性是一个大小固定的集合体,也就是inode结构体。

inode Table里面存的是文件的属性,Data blocks里面存的是文件的内容。

inode结构体里面没有文件名。内核层面,每一个inode都有inode number,通过inode号标识一个文件。通过inode号找到inode后,inode里面还有一个数组,可以映射到对应数据块。

GDT描述的是一个块组的使用情况。

Super Block(超级块)描述的是整个分区整体的文件系统的情况。

超级块不止一个,可能有多个,在不同的块组里,这样可以让文件系统更稳定。

通过-i选项,我们可以看到目录或者文件前面都有一个数字,这就是inode编号。 

inode编号是以分区为单位的。一个分区内inode号不能重复,两个分区间可以,所以inode不能跨分区访问。

我们平时都是用文件名找文件,inode就是通过文件名找到的。 

目录=文件属性+文件内容          目录的内容就是文件名和inode编号的映射关系。

所以同一目录下不能有同名文件

找文件的顺序:文件名->inode编号

目录的r权限,本质是是否允许我们读取目录的内容,即文件名和inode的映射关系。

目录的w权限,新建文件时,本质是建立文件名和inode编号的映射关系。

要找到一个文件,就要先找到文件所在的目录,然后打开,通过文件名与inode号的映射关系找到目标文件的inode。可目录也是文件, 就要继续往上找,直到来到根目录下。根目录是系统给我们的,是已知的,就可以找到目标文件了。这种逆向的路径解析由OS完成。这也是为什么Linux定位一个文件时,都要有路径的原因。

 要找到一个文件,需要inode,但更前提的是需要知道在哪个分区。

我们用的云服务器一般只有一个盘,盘里面有分区。要访问一个分区,就需要把该分区挂载。挂载实际是把磁盘分区和文件系统的目录进行关联,未来进入该分区本质就是进入该目录。

一个文件起始在访问之前,都是先有目录的。

文件都是带有路径的,目录也是。所以根据目录的路径,就可以找到对应哪个分区。

所以目录本身除了可以定位文件,还能确定是在哪个分区下的。

软硬链接

下面直接见识一个软连接

如上图,建立了一个软链接,ln就是link的意思,-s就是soft的意思。 后者链接前者。

file1和软链接都是独立的文件,因为他们都有独立的inode。

 下面见识一个硬链接

硬链接与软链接的建立相比,就少了一个-i选项。硬链接和file2的inode都是一样的。

上方红框中的数字,在建立硬链接前是1,建立硬链接后就变成了2。

 综上,可以知道,软链接是一个独立的文件,因为有独立的inode。

硬链接不是一个独立的文件,因为没有独立的inode。

属性中有一列数字表示硬链接数。

软链接 

软链接的内容:目标文件所对应的路径字符串。

如上图,我们可以直接通过软链接,就能打印出目标文件的内容。

软链接类似于windows中的快捷方式,所以删掉软链接对目标文件不会有影响。 

如果把目标文件删了,软链接就没用了。

我们建立6个目录,和一个可执行程序myls。

假设这是个项目,给舍友用,每次运行可执行程序都要带大串路径,很麻烦。这时就可以建立一个软链接来解决,类似于快捷方式。如下图:

 硬链接

如上图,通过硬链接,也可以直接打印出目标文件的内容。删除目标文件后,硬链接能照常打印。他们的inode编号都相同。没删除前,硬链接数是2,删除后变成1。这个数字叫引用计数,inode编号相当于指针,两个指针指向同一对文件属性,引用计数就是2,删除一个inode和文件名的映射关系,引用计数就变成1。

上面这个建立硬链接,然后删除原文件的操作,就相当于重命名。

硬链接本质就是一个文件名和inode的映射关系,建立硬链接,就是在指定目录下,添加一个新的文件名和inode号的映射关系。 

硬链接数就是文件的磁盘级引用计数:有多少个文件名字符串通过inode编号指向我。

如上图,新建立的文件和目录,一个引用计数是1,另一个是2。file是1,因为只有它这一对映射关系。dir也是刚创建的,它的引用计数却是2,为什么是2呢?

如上图,我们知道,任何一个目录下,都有 . 和 .. 两个隐藏文件。dir目录里的. 的inode跟dir的inode一样,即有两个对应的映射关系,所以引用计数是2。

我们再在dir里面新建一个otherdir目录。可以看到dir的引用计数变成了3。他的三对映射关系如上图红框。

因此可得出结论:

任何一个目录,刚开始新建的时候,引用计数一定是2。

目录A内部,新建一个目录,会让目录A的引用计数自动+1。

一个目录内部有几个目录=该目录引用计数-2

Linux系统中,不允许给目录建立硬链接。主要是为了避免路径环绕。

硬链接的意义:

构建Linux的路径结构,让我们可以使用. ..来进行路径定位。 一般用硬链接来做文件备份。

动态库和静态库 

如上图,我们只是调用了接口,并没有实现该函数。可执行程序执行必须要有对应的实现,所以编译时,由gcc默认帮我们链接了对应的库。

 ldd的作用是帮我们找可执行程序所依赖的库。上面红框就是C语言的标准库。

Linux中:.so(动态库) .a(静态库)

windows中:.dll(动态库)  .lib(静态库)

 

上图.c文件变成.o文件是翻译的过程。.o文件的全称是可重定位目标文件,也就是目标文件。

将所有的.o文件链接起来,就形成了可执行文件。

静态库 

假设有一个作业是做库,舍友不会,你又不能直接发原文件给他。因为程序最后都是由.o链接的,所以你就把所有的源文件编成.o文件。

如上图,你把所有的.o文件都发给了舍友。

这些.o文件都是函数的实现,可舍友又不知道函数名是什么,所以你就把头文件也发给了它。

有了.o文件和头文件,舍友只需要自己写个main函数。main函数里面只需要根据头文件提供的函数名和参数就能使用了。

 

我们直接编译main.c,发现编译不通过,因为找不到别的文件。所以可以把所有需要的文件揉在一起编译,这样就能形成可执行程序了。

通过上面例子可知,我们不需要给舍友源代码,只需要给他方法的实现,还有头文件就可以。

头文件是一个手册,提供函数的声明,告诉用户怎么用。

.o文件提供实现,我们只需要补上一个main,调用头文件的方法,然后和.o进行链接,就可以形成可执行。

 假设又有一个作业,也是一样的任务,只不过需要更多的库。因为.o文件太多了,你就准备打包给你的舍友,可是它不会解包。于是你就不打包了,而是使用ar 命令。

 选项-r就是replace的意思,-c就是create的意思。上面把所有的.o文件都打包进了libmyc.a文件里面。你告诉舍友libmyc.a直接用就行了,不需要解包。

如上图,舍友可以直接像刚才的方法一样使用。

由此可知,所谓的库文件,本质就是把.o打包。

ar就是打包工具,也是建立静态库的命令。

-c就是.o文件不存在就新建,-r选项是文件存在就替换。 

库的名字一般以lib开头,中间的myc才是真正的名字,.a就是静态亏的后缀。

我们可以把头文件和库文件都放到mylib目录里面,然后再把mylib打包给舍友,如下图:

 舍友不管选项怎么用,想直接用这个库。这就需要把库安装到系统里。

 如上图,这时候就已经把库安装到系统里了。

安装到系统后,这里的mylib相当于安装包,也就没用了,可以删除。

gcc/g++默认认识c/c++库,因为这里的库是我们自己写的,gcc/g++不认识,所以我们要带一个-l选项,后面再带上库的名字就可以了。注意库的名字是中间部分,要去掉前缀和后缀。

我们不建议把非官方的代码放到系统里。 所以要马上删除。

如果舍友不想安装到系统,想在当前目录下直接使用,即动态链接。 就需要用到-I,-L,-l选项,如下图:

-I:表示指定用户自定义头文件路径  (大写的i)

-L:表示指定用户自定义库文件路径

-l:表示指定确定的第三方库的名称  (小写的L)

我们ldd查看依赖的库,发现这里是动态库,这是为什么呢?我们明明是静态库,为什么没有用我们的库。

编译器在形成可执行程序的时候,能动态链接的就动态链接了,只能静态链接的,就把代码里的函数实现拷贝到可执行程序里面。

因为gcc/g++编译器默认是动态链接的,如果带了-static就必须强制全部静态链接。如果不带-static,就优先动态链接,需要对应的动态库就提供,没有就把静态库拷贝到可执行程序里。

 动态库

 形成动态库的目标文件需要带-fPIC选项。动态库的形成可以直接使用gcc,不需要ar,后面要带上-shared选项。

shared: 表示生成共享库格式

fPIC:产生位置无关码

库名规则:libxxx.so

把动态库放到mylib里,编译main.c后形成可执行程序。我们发现该可执行程序不能运行。

为什么这里会显示找不到?我们不是已经在上面指明了路径和库了吗?

这是因为上面的指明只是告诉了gcc/g++编译器,并没有告诉操作系统。

形成了可执行程序后,后面的工作就跟gcc/g++编译器没关系了。

动态库要在程序运行的时候,去找到动态库加载并运行。

静态库没有这个问题,因为在编译期间,就已经将库中的代码拷贝到我们的可执行程序内部了,加载和库就没有关系了。

 系统在运行期间,会自动找对应的动态库。动态库的查找路径默认是/lib64目录下。

第一种找动态库的方法就是,将需要的库拷贝到/libn64目录里。

这种方法不推荐。

第二种方法就是建立软链接。 

第三种方法就是通过环境变量。系统中存在一个环境变量叫LD_LIBRARY_PATH。添加库的路径到该环境变量中,这样就可以找到动态库了。

但是这样做,在我们关闭xshell后,就又找不到了。这是因为环境变量是内存级的,以前的文章讲过。

 上面是第四种方法。如果想在今后的使用中,都有该环境变量,就要在用户的家目录下修改.bashrc配置文件。如上图。

除非是非常重要的库,不然这种方法也不推荐。 

第五种方法。在系统的配置路径下,有一个目录ld.so.conf.d。里面有很多系统级别的配置文件。可以在这里面添加一个配置文件。把要用的动态库所对应的路径添加到配置文件里,最后再把配置文件加载一下即ldconfig,这个配置就永久生效了。ldconfig就是把目录里的所有配置文件生效。注意配置文件的后缀必须是.conf

总结5种找动态库的方法:

 安装到系统建立软链接命令行导入环境变量修改.bashrc配置文件,让环境变量永久生效/etc/ld.so.conf.d  新增动态库搜索的配置文件,ldconfig

 当动态库和静态库同时使用时,默认使用动态库。当我们带上-static选项后,链接的时候就是静态链接。动态链接和静态链接的文件大小差别很大。

总结:

默认链接的是动态库。

如果没有使用-static,并且只提供.a库,只能静态链接当前的.a库,其他库正常动态链接。

 -static就是强制要求程序进行静态链接,如果没有静态库版本,就会报错。

也就是说,没有带-static时,优先使用动态库,没有动态库就使用静态库。

如果带了-static,一定不要动态库,只要静态库。

 动态库加载--可执行程序和地址空间

静态库不需要考虑加载,因为代码编译后,静态库就已经被拷贝到可执行程序内部了。

可执行程序函数实现在库文件中,所以库文件也要被加载到内存中。

动态库要映射到当前进程的堆栈之间的共享区。

未来如果有新的进程需要用到已经加载到内存的库,可以直接把已加载的库映射到新进程的地址空间里。所以未来不同的进程可以使用同一个库,这个库叫动态库,也叫共享库。

动态库可以被加载到进程的地址空间中的任何位置,所以形成动态库是要带选项-fPIC



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。