文章目录
- 1、磁盘结构
- 1.1 磁盘的物理结构
- 1.2 磁盘的存储结构
- 1.3 磁盘的逻辑结构
- 2、文件系统
- 2.1 4KB加载到内存
- 2.2 文件系统结构
- 3、软硬链接
- 3.1 软链接
- 3.2 硬链接
- 4、动静态库
- 4.1 什么是库?
- 4.2 静态库和静态库链接
- 4.3 动态库和动态库链接
- 4.4 动静态库的加载
下面了解到,操作系统是如何管理放在磁盘中的未打开文件的。
1、磁盘结构
磁盘不仅是一种外设,还是唯一的一个机械结构的外设,所以相对其它硬件来说很慢,但是它可以存储大量的数据,下面看它的具体结构。
1.1 磁盘的物理结构
磁盘主要由盘面、磁头等构成。
值得注意的是:
磁盘的盘面有两面,两面都可以读写数据,一个磁盘有多个盘面。
磁盘的磁头,每个盘面的一面都对应着一个磁头,并且磁头和盘面是没有挨着的。(这点不像老式DVD读光盘是挨着的)
磁盘的存储原理在大概上:
盘片中有很多微小的磁块,这些磁块拥有着南北极,存储对应的二进制01。磁头对磁块进行放磁,使得磁块进行转向,通过南北极转换来进行01二进制标识。
1.2 磁盘的存储结构
在盘面上,每一圈对应着一个磁道,磁道又分为多个扇区。
磁头是共进退的!磁头在定位对应盘面的时候,整体是共进退的,所以磁头共同在同一个磁道上找,整体就成了柱面。
对于扇区来说,靠圆心的扇区面积小,离圆心远的扇区面积大,但是每个扇区都是512byte,密度不一样。(当然也有一样的,这里是一般的。)
磁盘的寻址方式的基本字节是512byte,一个扇区大小。
那么磁盘中如何定位一个扇区呢?
先在整个柱面(Track)定位哪一个磁道,再定位磁头(head)看在哪个盘面上,最后定位在哪个扇区(sector)上。综上这个方法也叫CHS。
磁头来回摆动确定在哪个磁道,盘面高速旋转,让磁头定位扇区。
所以磁盘效率就看在高速旋转中,在能读写数据的前提,多少转每秒,即每秒多少读写次数。
1.3 磁盘的逻辑结构
磁盘在物理上的存储结构是圆形的,如果将其抽象拉直,就变成了对数组的管理。
在数组上,分为多个大的磁道,磁道中又分了多个扇区,所以只要通过简单的除与除余就能得到一个扇区的位置。这种定位也被称为LBA(logical block address)
这种方式相较于CHS法有什么好处吗?
1、首先这样肯定是更加方便管理的。
2、其次,OS不想和硬件进行强耦合,这个方式在OS中可以不仅限于管理磁盘,也能用在其它硬件上。
2、文件系统
2.1 4KB加载到内存
首先,前面说过磁盘在读取时基本单位是512byte,但是对于OS来说一次读这么点还是太少了,OS就有自己的读取策略,一次读取多个扇区,比如1KB、2KB、3KB、4KB(现在主流都是一次4KB)。
在一次读多个扇区下,哪怕是访问一个字节的量,也要将4KB空间load到内存。
这种方式,也有部分因为局部性原理:
当计算机访问数据,附近的数据也大概率被访问到。加载更多的数据能增加数据IO的效率,局部性原理的存在也能增加数据的命中率,一定上减缓了更多的IO过程,本质是一种数据预加载,空间换时间!
内存也因此,被划分成了多个4KB大小的空间,这个空间称为页框,一个4KB大小的块被称为页帧。
2.2 文件系统结构
磁盘是很大的,为了更好的管理,需要分区,分组 (与国家划分省、市是一样的),但磁盘有一点不太的是,各个分区分组的管理方式是一样的。
- 超级块(Super Block):存放文件系统本身的结构信息,有属性信息、磁盘布局和资源使用情况等信息。超级块属于整个分区,分区有一定比率分组都有对应的超级块,多存在意味着备份,保存在不同分组,若某一个分组的文件系统坏了,可以用其它分组的超级块恢复。
- 块组描述符(GDT):存放分组的宏观属性信息,如一共有多少个inode,有多少数据块,以及用了多少,多少没被用。
首先,一个文件 = 内容 + 属性,并且文件的内容和属性是分开存储的!
其中文件内容保存在数据块中(Data Blocks),文件属性保存在Inode中。
Inode(ext3-128byte ext4-256byte)包括一个文件的几乎所有属性,文件名不在Inode中,并且每个文件都有属于自己的Inode。
通过ls -li 带上-i 可以查看每个文件对应的Inode。
- Inode Table 保存了分组内部所有可用(使用和未使用)的inode。
- Data blocks 保存的是分组内部所有文件的数据块(4KB为单位)
为了区分Inode或者Data blocks 中哪些被使用哪些没被使用。
- Inode Bitmap 中inode通过位图结构,一个Inode对应一个bit位,比如第一个bit位代表1号inode,1代表被用,0代表没被占用。
- Block bitmap 数据块对应的位图结构也一样,比如10000个块,对应10000个bit位,被使用的块由0置1,清除就由1置0。
当想查找一个文件时,统一使用的是: inode编号
(并且,Inode在不同组,有多套inode,比如0组是1000开始的,1组是10000开始的,所以拿到一个inode也很好定位)
值得注意的是:我们使用的是文件名,不是inode,这是因为目录也是个文件,目录的数据块存的是目录里文件的inode和文件名的映射,所以通过文件名再到映射的inode,就可以找到了。
这也说明,一个目录下不能有同名文件。
也说明,如果要在目录创建一个文件,必须有目录的写入权限,需要在目录代码块中建立inode和文件名的映射。
那如何找到文件内容呢?数据块这么多,怎么知道是你的?
在inode属性中有一个blocks数组,其中0-11一般指向一个数据块,如果文件只有几个数据块,直接通过下标定位对应数据块就行了。
一般从12、13、14开始,比如12指向的数据块,里面保存着其它数据块的地址。13、14指向的数据块里面指向有多层都是地址,这样就可以指向很多给数据块。
那么如何删除文件呢?
直接通过inode找到对应代码块,将代码块对应的block bitmap中1置0,再将inode bitmap中对应的inode置0就行。
(这就说明,删除文件只是把bitmap中至0,对应数据块的数据还是在的)
如果误删文件,只是清bit位,恢复文件Inode bitmap至1,inode table中找数据块映射,将对应block bitmap中至1,就能找回文件,所以如果误删文件,最好就是什么也别做,怕将原来数据块覆盖。
3、软硬链接
3.1 软链接
首先通过
ln -s 文件名 生成软链接文件名
,比如下面创建一个soft_file.link。
软链接的原理:
软链接是一个独立的文件,只不过这个文件保存着被链接文件的地址。
当被链接文件删除时,这个地址也就无效了,简单来说软链接生成的文件就是一个快捷方式。
(可以看到,软链接生成的文件有自己的inode)
通过unlink 链接文件名 或者 rm 就可以删除链接文件取消软链接。
3.2 硬链接
硬链接通过
ln 被链接文件名 生成链接文件名
,比如:
(可以看到,生成的链接文件和被链接文件用的是同一个inode)
因为目录中存在两个不同的文件名映射同一个inode,inode里也会有一个计数器,计数有多少个文件名和inode映射关系(引用计数:硬链接数)
如果删除了mytest.txt,仅仅减少一个引用计数和一个对应关系。
只有当引用计数为0时,位图才清0,文件才删除。
(下面两个文件名链接到同一个inode,所以各自硬链接数为2)
硬链接的作用:
首先,对于一个普通文件,硬链接数为1,因为只有自身文件名和inode具有映射关系。
一个目录默认硬链接为2,因为本身和inode有映射关系,而在目录中 .
作为一个文件代表当前目录和目录有一样的inode,是目录的硬链接。
下面又在empty目录里再创一个目录empty2,此时empty的硬链接数为3,这个empty2中的…文件是empty目录的硬链接。
综上,硬链接让文件系统能够方便的访问上下文。
下面看到的一个问题:
Linux 为什么不允许用户(包括root)给目录建立硬链接呢?
.和… 不就是给目录建立的硬链接吗?
其实,这时操作系统给的保护措施,操作系统只运行自己建立,不允许使用者建立。
怕的就是这样:
4、动静态库
4.1 什么是库?
首先得了解一下编译的过程及命令
-o 指明形成的临时文件名 .o文件结尾的叫做可重定义目标二进制文件。
gcc -E test.c -o test.i 预处理
gcc -S test.i -o test.s 编译 转换成汇编语言
gcc -c test.s -o test.o 汇编转换成不可执行的二进制文件
gcc test.o -o mytest 链接
分别记忆成ESc和iso
在实际生成一个文件可以不用从预处理写起,比如生成一个不可执行的二进制文件可以直接gcc -c 指定文件名
如果不想给对方源代码(.c)文件,只需要给对方编译好的.o可重定义目标二进制文件,再包括对应头文件就行。 (.o文件包括方法的实现,.h文件包括有什么方法)
将所有".o文件",打一个包,给对方提供一个库文件即可!
综上库其实就是多个.o文件的集合。
4.2 静态库和静态库链接
生成静态库:
比如:ar -rc libmymath.a add.o sub.o
(rc表示replace and create)
将add.o和sub.o打包成libmymath.a静态库文件。
查看静态库中的目录列表:
比如:ar -tv libmymath.a
(-t 列出静态库中的文件,v:verbose 详细信息)
除了要静态库文件还要对应匹配的头文件,将库文件和.h打包起来,并且可以通过压缩包让别人使用,而安装本质就是将头文件和.a文件拷贝到系统特定路径。
(特定的头文件就是放在user/include下,特定动静态库就是放在/lib64下)
这里的测试文件就不放在系统特定文件中,这里可以自己创建一个mylib。
再将静态库文件和头文件打包,让使用者只能获取函数使用。
接下来就是链接库,生成可执行文件的过程
值得注意的是,平常程序生成可执行文件的命令简略是因为默认指明了库文件的路径以及头文件的路径。
这里的链接就需要自己指明各自的库路径。
gcc -o mymath main.c -I ./mylib/include -L ./mylib/lib -l mymath
(其中-o指明生成的可执行文件名,-l 指明include文件路径,-L 指明lib文件路径,-l 指明库文件名) 最重要的是库文件名其实是libmymath.a,但是在链接时需要去掉lib和后缀.a才是真正文件名。
测试目标文件生成后,静态库删掉,程序照样可以运行
4.3 动态库和动态库链接
生成动态库:
比如:gcc -fPIC my_add.c
生成与位置无关码的.o文件。
生成多个.o文件后。
通过gcc -shared -o libmymath.so my_add.o my_sub.o
生成动态库文件。
生成动态库文件后,将库文件和头文件进行打包。
打包后,如果正常链接也是会出错的,意料之中,因为没有指明库文件路径和头文件路径
指明路径后,成功生成可执行文件,但是运行却发生了错误。
通过ldd(列出动态库依赖关系)看到libmymath.so 动态库的地址找不到。
为什么会找不到动态库?
因为上述操作只告诉了gcc库文件路径和名称,但是程序在gcc编译完后,就和gcc没关系了,变成进程和系统的关系了,所以也要告诉OS和shell库文件的路径和名称。
(一般动态库也是在lib64下的,我的库没有在系统路径下,所以OS无法找到)
OS搜索库,除了放在特定目录下,还可以在环境变量中搜索
比如:echo $LD_LIBRARY_PATH
将动态库绝对路径放入:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/yzh/dslib/test/mylib/lib/
这种方法如果用户退出就失效了。
如果要永久有效的话。
只要访问cd /etc/ld.so.conf.d/
这个路径,在这个路径下创建一个配置文件,然后将我们的动态库的绝对路径放入任意一个配置文件当中就可以了。
sudo创建一个配置文件,再sudo vim打开放入路径。
放入后sudo ldconfig 更新配置文件
最后一种方式就是通过软链接,在自定义路径下创建一个动态库的快捷方式。
4.4 动静态库的加载
静态库的加载
静态库的加载很简单,静态库的代码在编译期间直接拷贝到程序的代码区,未来这部分代码,必须通过相对确定的地址位置来进行访问,这也使得这部分代码在编译期间就有了一套虚拟地址,可以和虚拟地址空间进行适配。(绝对编址方案)
动态库的加载
动态库是将指定函数的地址,写入到可执行程序中。
1、动态库中的函数,在编译期间,是以一种start:偏移量方式定位的,函数只需要填一个偏移量地址。start就是ldd指令显示的动态库起始地址,偏移量是相对start的,所以在gcc -fPIC 形成与位置无关码,就是想用偏移地址,不是绝对编地址。
2、当程序加载到内存,并且走到函数时,这个时候就会停下来,访问动态库(所以也是为什么要让OS看到动态库),然后将动态库需要的部分加载到内存中,映射到共享区,这个时候就获得了库的起始地址!这样函数就可以通过偏移量访问对应函数。
综上:
1、如果有很多个程序并且有很多用着同一份函数,如果链接的是静态库,就有着相同份的函数加载到内存,如果链接动态库就只需要加载一份函数到内存,能很好的节省资源。
2、其实gcc是默认建议是动态链接的,一个可执行文件如果链接了很多个库,有静态和动态的,多个库是一个一个链接的,如果是动态库就动态链接,静态就静态链接,但是如果有一个动态库,那么整个链接就是动态链接。(这也是为什么静态链接后,查看文件链接类型还是动态链接,因为还动态链接了C库。)
本章完~