** 🌈个人主页:**秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12625432.html
前言
💬 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
版权归原作者 秦jh_ 所有, 如有侵权,请联系我们删除。