Linux文件系统入门
前言
Linux经典语录:一切皆文件
文件系统的作用是用来管理磁盘上的文件(普通文件、目录、块设备、管道等),根据组织管理的方式不同,会有不同的文件系统。存在磁盘上的数据是持久化的,断电不丢失。
一、文件系统组成
Linux文件系统会给每个文件分配两个数据结构:索引节点和目录项。
1. 索引节点
索引节点(index node),即inode,用来记录文件metadata(元数据),是文件的唯一标识符,存在磁盘中。metadata主要为inode编号、文件大小、访问权限、stat时间、存储磁盘中的位置信息。
- stat时间
● struct timespec st_atim; /* 上次访问(access time)的时间 */
○ 对文件进行读取的操作,cat、more等命令会改变,stat、ls不会影响
● struct timespec st_mtim; /* 上次修改(modify time)的时间 */
○ 文件最后修改的时间,ls -al显示的时间,echo、vim操作会改变
● struct timespec st_ctim; /* 上次状态更改(change time)的时间 */
○ 改变文件的权限和属性的时候,chmod、chown
2. 目录项
目录项,即是dentry,用来记录文件名,索引节点指针、与其他目录项之间的层级关联关系。可以理解成tree显示目录层级关系。
区别于目录,目录项是内核管理的一个数据结构,占用了内存进行缓存;而目录则是一个文件,存储在磁盘中。
由于dentry是内核进行维护,则在系统启动的时候,就会对目录项进行init,读取各文件目录之间的层级关系。而当目录更新的时候,也会实时更新缓存。这样的好处是,查询目录不需要从磁盘读取,之间从缓存查询效率更高,特别有利于频繁查询。
3. 两者关系
磁盘挂载到某个路径下,才可访问;Linux系统启动的时候,文件系统会挂载在根目录/下,所以启动之后才可以对系统盘进行访问
磁盘读取的最小单位是扇区,一般为512B的大小
而文件系统读取的最小单位是一个逻辑块,一般为8个扇区,也即是4KB,这样的好处是提高磁盘的读写效率,一次性可以多读点数据出来。同时你要从磁盘读取10B的数据,也的最少读取4KB的数据出来,再去找那10B的数据。这里可以看出文件系统其实屏蔽了用户读写数据操作。
磁盘格式化,一般会分成三部分:超级块、索引节点、数据块;
- 超级块区:存储文件系统的信息,块个数、块大小、空闲块等;磁盘挂载后,该块信息将加载进入内存
- 索引节点区:存储索引节点;文件被访问时,加载进入内存,可以加速对文件的访问
- 数据块区:存储文件
4. 文件链接
- 硬链接
多个文件名指向同一个inode号码;删除一个文件名,不影响另一个文件名;目录不允许硬连接;不同分区、不同硬盘不允许硬链接(inode编号可能重复)
root@CentOS7 lnDemo]# ln source source_ln
[root@CentOS7 lnDemo]# ls -ali
总用量 8
33575032 drwxr-xr-x. 2 root root 37 9月 30 17:35 .
33574977 dr-xr-x---. 4 root root 161 9月 30 17:34 ..
33575033 -rw-r--r--. 2 root root 7 9月 30 17:31 source
33575033 -rw-r--r--. 2 root root 7 9月 30 17:31 source_ln
[root@CentOS7 lnDemo]# stat source
文件:"source"
大小:7 块:8 IO 块:4096 普通文件
设备:fd00h/64768d Inode:33575033 硬链接:2
权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root)
环境:unconfined_u:object_r:admin_home_t:s0
最近访问:2020-09-30 17:34:54.975711266 +0800
最近更改:2020-09-30 17:31:49.124074625 +0800
最近改动:2020-09-30 17:35:05.665345071 +0800
创建时间:-
[root@CentOS7 lnDemo]# stat source_ln
文件:"source_ln"
大小:7 块:8 IO 块:4096 普通文件
设备:fd00h/64768d Inode:33575033 硬链接:2
权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root)
环境:unconfined_u:object_r:admin_home_t:s0
最近访问:2020-09-30 17:34:54.975711266 +0800
最近更改:2020-09-30 17:31:49.124074625 +0800
最近改动:2020-09-30 17:35:05.665345071 +0800
创建时间:-
- 软连接
当创建软连接的时候,linux确实已经创建了一个inode 和起对应来的data block,只不过,在data block存放的是字符串,字符串的内容则是 链接文件的地址。
[root@CentOS7 lnDemo]# ln -s source source_sln
[root@CentOS7 lnDemo]# ls
source source_sln
[root@CentOS7 lnDemo]# ls -alt
总用量 4
drwxr-xr-x. 2 root root 38 9月 30 18:08 .
lrwxrwxrwx. 1 root root 6 9月 30 18:08 source_sln -> source
dr-xr-x---. 5 root root 172 9月 30 17:55 ..
-rw-r--r--. 1 root root 7 9月 30 17:31 source
[root@CentOS7 lnDemo]# ls -ailt
总用量 4
33575032 drwxr-xr-x. 2 root root 38 9月 30 18:08 .
33575034 lrwxrwxrwx. 1 root root 6 9月 30 18:08 source_sln -> source
33574977 dr-xr-x---. 5 root root 172 9月 30 17:55 ..
33575033 -rw-r--r--. 1 root root 7 9月 30 17:31 source
二、虚拟文件系统
1. VFS作用
虚拟文件系统(Virtual File System,VFS),是操作系统为了给用户提供的一个统一的接口,而引入的一个中间层,介于用户层和文件系统层之间。这样的好处是,用户不需要关心底层的文件系统是什么(EXT2、3、4,NFS),调用统一的接口对文件进行操作就行,而底层的逻辑则交给VFS去实现,对接不同的文件系统。
2. VFS实现
对文件的访问流程:用户空间调用库函数,内核空间进行系统调用,通过VFS的接口决定调用什么文件系统,然后将磁盘或者网络存储上的文件读入缓存buffer中,再依次返回到用户空间中
3. 文件描述符
用户要对文件进行write,首先需要open一个文件。在open一个文件的时候,操作系统其实为每个进程维护了一个打开文件表,而这个打开文件表就记录着对应打开文件的文件描述符,可以理解成对应打开文件标识或者编号。
三、用户空间文件系统(FUSE)
1. FUSE作用
用户空间文件系统(Filesystem in user space),也即是在用户空间实现的文件系统。常用的EXT4文件系统是内核空间的,我们通过系统调用write文件时,其底层通过VFS调用了EXT4文件系统的write实现,对磁盘中的数据进行写入。但是如果用户需要定制自己需求的一些文件系统功能,是不会对内核进行修改的,难度比较高。如果能在用户空间实现自己的文件系统,则可以定制自己的需求,而且不涉及内核,非常方便我们调试开发。
2. FUSE实现
首先,实现这个用户空间的文件系统,至少需要三个组件:
- 内核模块的fuse:接收VFS的IO请求,并通过管道发送给用户态
- 用户态的libfuse库:通过管道接收内核态的IO请求,并回调用户空间的文件系统
- 用户空间的文件系统:用户空间的文件系统的具体实现
整体流程为:
- FUSE将磁盘挂载到/mnt/fuse目录下
- 用户使用FUSE文件系统,创建my.logwenj
- 调用系统open调用
- 经过VFS接口调用到FSUE处理
- 这里FUSE不是和内核文件系统一样,直接就调用内核create了,而是通过管道发送create的消息给后台的libfuse。
- libfuse创建的后台进程读取到消息,会调用对应的create函数,也即是libfuse中的low level层的接口
- 根据libfuse接口调用,回调用户空间文件系统FUSE的create函数,由用户空间的文件系统进行创建对应的文件
- 创建完成之后,再通过管道将消息传回给fuse.
3. FUSE 回调
static struct fuse_operations spdk_fuse_oper = {
.mkdir = fuse_mkdir, // make directories
.opendir = fuse_opendir, // The opendir() function opens a directory stream corresponding to the directory name, and returns a pointer to the directory stream. The stream is positioned at the first entry in the directory.
.getattr = fuse_getattr, // 获取文件、目录属性
.readdir = fuse_readdir, // 读取目录,返回指向dirent结构体指针
.mknod = fuse_mknod, // 建立一个目录项和一个特殊文件的对应索引节点
.unlink = fuse_unlink, // 删除文件
.truncate = fuse_truncate, // 重置文件大小
.utimens = fuse_utimens, // 以纳秒级分辨率更改文件的访问和修改时间
.open = fuse_open, // 打开文件
.release = fuse_release, // 释放打开的文件,关闭文件描述符和内存解映射
.read = fuse_read, // 读取打开文件的内容
.write = fuse_write, // 往打开的文件中写数据
.flush = fuse_flush, // 刷新缓存数据,close前调用
.fsync = fuse_fsync, // 同步文件内容
.rename = fuse_rename, // 重命名文件
.rmdir = fuse_rmdir, // remove empty directories
};
四、参考文档
深入理解基于Linux文件系统原理与实现
一口气搞懂「文件系统」,就靠这 25 张图了
5分钟搞懂用户空间文件系统FUSE工作原理
自制文件系统 — 02 FUSE 框架,开发者的福音
虚拟文件系统