一,Linux文件系统
1,文件系统是什么
- 文件系统是操作系统用于明确存储设备(常见的有磁盘,固态硬盘等)或分区上的文件的方法和数据结构,即在存储设备上组织文件的方法。
- 操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。
2,文件系统可以做什么
从系统角度来看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。
3,Linux文件系统层次简介
如图1所示,文件系统从大的方面可以分为两层:
- 用户空间,如用户使用的各种应用程序
- 内核空间,其又可以分为
- 文件管理系统,包括虚拟文件系统(VFS)和具体文件系统。
- 设备驱动,包括缓冲区和设备驱动。
虚拟文件系统(Virtual Filesystem Switch,简称VFS)简介:
虚拟文件系统(VFS)是对真实文件系统的抽象和软件实现,定义了所有文件系统都支持的、基本的 、 抽象的接口和数据结构。
对内(具体文件系统),允许同时使用不同的文件系统,例如在不同文件系统之间复制、移动文件;
对外(如其他内核子系统及用户的各种应用程序),VFS提供统一的接口,使得程序开发者不需要关注Linux实际使用的文件系统。
因此,Linux系统支持同时使用多个不同类型的文件系统。具体文件系统(如ext4,xfs,btrfs等等)实现了VFS 定义的抽象接口和数据结构,将自身的诸如文件 、 目录等概念在形式上与 VFS 的定义保持一致,在统一的接口和数据结构下隐藏了具体的实现细节。
VFS的几个重要数据结构:
- 超级块(superblock):存放系统中已安装的文件系统的所有信息。对基于磁盘的文件系统,这类对象通常对应于存放在磁盘上的文件系统控制块。也就是说,每个文件系统都有一个超级块对象;
- 索引节点(index node,简称inode):存放对于具体文件的元数据信息(除了文件名)。每个文件都有一个索引节点对象,每个索引节点对象有一个索引节点号,这个号唯一地标识在某个文件系统中的指定文件;
- 目录项(dentry):存放目录项与对应文件进行链接的信息。VFS把每个目录看作一个有若干个子目录和文件组成的常规文件;
- 文件(file):存放打开文件与进程之间进行交互的有关信息。这类信息仅当进程访问文件存放于内存中。
二,ext4文件系统简介
1,ext4文件系统管理结构
ext4文件系统layout的官方说明可以到这个页面进行了解:https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inline_Data,下面说说个人理解:
上图简单介绍了ext4文件系统的布局:磁盘划分为分区,每个分区又划分为N个块组。在每个块组中,又根据块组的编号有不同的内部结构。
涉及到块组(Block Group)组成元素简介:
-
引导块
该磁盘分区的第一个块,如果该分区装了系统,则记录引导启动信息,用来引导启动该分区安装的系统,一般占用2KB。
BIOS+MBR启动的流程简述:
- BIOS:从固件中读取BIOS及其设置,识别第一个可开机设备的MBR位置
- MBR:MBR前446Byte是引导加载程序(Bootloader),
- 引导加载程序:Bootloader加载自己可识别的内核文件,或者将管理区交给其他加载程序,其他加载程序再加载自己可识别的内核
- 内核(Kernel):管理调度更多操作系统功能
MBR和启动块的关系
对上图的几点说明:
- 每个分区都拥有自己的启动块,但不是每个启动块都装载BootLoader。
- 实际可开机的内核文件是放置在各分区内的。
- Bootloader只会认识自己的系统分区内的可开机内核文件,或者其他Bootloader。
- Bootloader可直接指向或者是间接将管理权转交给另一个管理程序。
- 每个分区都有自己的启动块,Bootloader可以安装在MBR或启动块上,Bootloader只会识别自己分区内的可开机内核文件。
超级块(Superblock)
The superblock records various information about the enclosing filesystem, such as block counts, inode counts, supported features, maintenance information, and more.
超级块记录关于封闭文件系统的各种信息,比如块数量(已用和未用数量),inode数量(已用和未用数量),支持的功能特性,维护信息(比如分区上有多少个块组,块组的数据区和元数据区,)等等。
If the sparse_super feature flag is set, redundant copies of the superblock and group descriptors are kept only in the groups whose group number is either 0 or a power of 3, 5, or 7. If the flag is not set, redundant copies are kept in all groups.
如果设置按照稀疏方式存储,则超级块和块组描述符的备份信息将只记录在组号码是0或者3、5、7的n幂的块组中。如果不设置,则备份信息将记录在所有块组中(显然备份信息将占用很多磁盘空间)。
一般只使用块组0中的超级块,因此也叫主超级块。如果主超级块信息损坏,可以从超级块的备份信息中复制数据来修复。块组描述符(Group Descriptor,简称GDT)
主要包含块位图,inode位图和inode表位置,当前空闲块数,inode数以及使用的目录数(用于平衡各个块组目录数)。每个块组都对应这样一个描述符。
由于GDT对于定位文件系统的元数据非常重要,因此和超级块一样,也对其进行了备份。GDT及其备份的内容都是一样的,所占块数也相同。预留GDT块(Reserve GDT blocks)
在创建文件系统时,为了便于将来扩展文件系统,在块组描述符表后面分配预留的数据块。块位图(Block bitmap):
Block Bitmap是用来标识Block Group中的block使用情况,其中的每一位代表该Block Group中的一个block,如果是1则代表使用,如果是0则代表空闲。通过Block bitmap,可以快速知道该block group中每个block的使用情况。Inode位图(Inode bitmap):
Inode位图用于描述该块组所管理的inode的分配状态,如果inode位图中相应位置为1,那么代表该inode已经分配出去;否则可以使用。
inode:inode的全称是index-node,索引节点。
在Linux系统中,文件的数据分为元数据(metadata)和数据(data)两类,其中存放元数据信息的区域就叫做inode,存放文件实际内容的块(block)被叫做数据块(data block)。
inode中包含的信息主要有:
- inode编号(inode number)
- 文件的大小(size)
- 属主(User ID)、属组(Group ID)、权限(读、写、执行)
- 时间戳(共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间)、
- 链接数(硬链接次数)
- 指向存放文件实际数据的block的块指针(直接块指针,最高三级间接块指针)
注意:inode中文件的(原因下面会介绍)。
Inode表(Inode table):
Inode表用于存储inode信息。它占用一个或多个块(为了有效的利用空间,多个inode存储在一个块中),其大小取决于文件系统创建时的参数,由于inode位图的限制,决定了其最大所占用的空间。数据块(Data block):
元素1-7存放了ext4文件系统的元数据,其他磁盘空间则用来存放文件真正的内容,因此称为数据块。
三,ext4文件系统文件管理简介
ext4文件系统(或者说Linux系统)内部是使用inode number来识别文件的。任何文件都有其分区内唯一的inode number,并通过块指针与文件数据关联。文件名只是为了便于人类记忆和使用,给inode number起的“别称”或者“绰号”。
那么我们平时通过文件名来管理文件,是怎么实现的呢?文件名信息记录在哪里?
Linux系统的哲学思想之一是“一切皆文件”,磁盘设备是文件(在我的另一篇blog《CentOS磁盘和分区简介》中有简要介绍),目录也是文件。
目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项由两部分组成:目录直接子文件的文件名,及其对应的inode号码。而根目录/
由Linux内核直接管理并记录其inode number。
Note:目录只记录直接子文件的文件名,及其对应的inode号码。
- Linux系统的哲学思想之一是“一切皆文件”,子文件可以是Linux支持的任何类型文件,如普通文件、目录、设备、管道、链接等等。
- 子文件的子文件,不会记录在目录中,因为不是直接子文件。
因此,我们可以很容易的通过根目录及其子目录,找到文件名对应的inode number。再根据inode中指向数据块的块指针,找到文件内容数据。
下面通过几个例子来简单介绍管理文件的过程。
I,查找文件 /root/test/a.txt
- 通过内核找到根目录
/
的inode number - 根据根目录
/
的inode number,到inode table中找到根目录/
的块指针 - 通过块指针找到根目录
/
的数据块,根据其中的数据找到下级目录/root/
对应的inode number - 根据
/root/
对应的inode number,到inode table中找到目录/root/
的元数据及块指针 - 通过块指针找到存放目录
/root/
的数据块,根据其中数据找到/root/test/
目录对应的inode number - 根据
/root/test/
目录对应的inode number,到inode table中找到目录/root/test/
的元数据及块指针 - 通过块指针找到存放目录
/root/test/
的数据块,根据其中数据找到文件/root/test/a.txt
对应的inode number - 根据文件
/root/test/a.txt
对应的inode number,到inode table中找到文件/root/test/a.txt
的元数据及块指针 - 通过块指针找到存放文件
/root/test/a.txt
的数据块
至此完成寻找文件及其数据的过程。
根据执行命令的不同,上面的过程可能也不一定都会走到步骤9,例如:
ls /root/test/b.txt
,如果文件/root/test/b.txt
不存在,那么到步骤6,在/root/test/
目录的数据中,会发现目录项(dirent)中没有文件b.txt
的记录,提示ls: 无法访问/root/test/b.txt: 没有那个文件或目录
ls -i /root/test/a.txt
,列出文件/root/test/a.txt
的inode number,到步骤7就已经获取了需要的数据ls -l /root/test/a.txt
,列出文件/root/test/a.txt
的元数据,元数据是存放在inode table中的,到步骤8就已经获取了需要的数据cat /root/test/a.txt
,显示文件/root/test/a.txt
的数据,这才需要走到步骤9并获取数据块中的文件数据
II,创建文件 /root/test/b.txt
- 扫描inode bitmap,找到一个空闲的inode,将其标记为占用,获取其对应的inode number;
- 查找目录
/root/test/
的数据(过程参考I),并在其中添加一条目录项(dirent)记录,文件名为/root/test/b.txt
,inode number在步骤1中已经获得; - 根据inode number,在inode table中找到该inode,记录文件
/root/test/b.txt
的部分元数据,如inode number、权限等等; - 扫描block bitmap,找到可分配的空闲块,标记为占用,并在文件
/root/test/b.txt
的元数据中记录块指针; - 在数据块中写入文件数据。
III,删除文件 /root/test/b.txt
- 查找文件
/root/test/b.txt
使用的元数据信息(过程参考I); - 查找目录
/root/test/
的数据(过程参考I),并删除文件/root/test/b.txt
的目录项(dirent)记录; - 修改文件
/root/test/b.txt
的元数据,将其链接数-1; - 如果此时文件
/root/test/b.txt
的链接数≤0,扫描block bitmap,将分配给文件的块节点标记为空闲; - 如果此时文件
/root/test/b.txt
的链接数≤0,扫描inode bitmap,将其中对应的节点标记为空闲;
硬链接,软链接简介
- 硬链接:
1.1 多个文件指向同一个inode,这些文件的inode number相同
1.2 硬链接表明文件可以通过不同的文件名访问
1.3 不能对目录创建硬链接
1.4 硬链接不能跨分区
1.5 每多一个硬链接,inode的引用计数(链接数)+1
- 软链接(符号链接):
2.1 文件及其软链接文件,使用的不是同一个inode,inode number不一样;
2.2 软链接文件的实际数据是另一个文件的路径,是一个字符串,软链接文件的大小为该字符串的长度;
2.3 可以对目录创建软链接
2.4 软链接可以跨分区
2.5 增加文件软链接,不会增加inode的引用计数