从青铜到王者系列:深入浅出理解DeepSeek 3FS (2)从内核到用户态文件系统的设计之路
大家好,我是小王同学,
本文希望帮你深入理解分布式存储系统3FS更进一步
参考答案
没有FUSE的IO流程示意图
带FUSE的IO流程示意图
从青铜到王者系列:深入浅出理解 DeepSeek 3FS(1)
我整理 大模型训练
目前
幻方为什么要搞3FS? AI训练与推理的业务需求,传统分布式文件系统已经难以满足:
因此重新实现一个新的文件系统
❝不考虑分布式,个人笔记或者购买云主机文件系统 如何支持大小文件读写,并且满足高效查询
划重点:重点是文件系统部分,扩展虚拟机(KVM)与内核部分。
电脑通电后,加载主板上的BIOS(basic input output system)程序
BIOS是电脑启动时加载的第一个软件。 它是一组固化到计算机内主板上一个ROM芯片上的程序
虚拟机(KVM)如何设置引导硬盘顺序
什么是 KVM? 基于内核的虚拟机(KVM)是 Linux® 操作系统的一种开源虚拟化技术。借助 KVM,Linux 可作为虚拟机监控程序运行多个独立的虚拟机(VM)
BIOS--》boot 第1引导顺序:hard drive 硬盘
第2引导顺序:cdrom 光驱 ----》安装系统
第3引导顺序:removable device 可移动设备--》u盘,移动硬盘 --》安装系统
第4引导顺序:Network --》从网络启动--》网络中安装服务器启动 --》安装
启动流程:
挂载文件系统想要操作文件系统,
第一件事情就是挂载文件系统
系统启动过程:
+----------------+ +---------------+ +------------------+
| 读取超级块 | --> | 定位inode 2 | --> | 挂载为根目录(/) |
+----------------+ +---------------+ +------------------+
在 Linux 启动过程中,加载 ext4 文件系统主要经历以下几个阶段:
这样,整个过程确保了 ext4 文件系统的元数据和数据块被正确加载,并最终成为系统的根文件系统,使用户和应用程序可以正常访问文件数据。
[root@watchpoints ~]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 1G 0 loop /mnt/icfs
sr0 11:0 1 19M 0 rom
vda 253:0 0 50G 0 disk
└─vda1 253:1 0 50G 0 part /
[root@watchpoints ~]# ls -id /
2 /
[root@watchpoints ~]# ls -id /mnt/icfs
2 /mnt/icfs
同理
我们有3个不同的文件系统分别挂载到了3个不同的挂载点(目录)。
EXT4格式的文件系统我们分了3个,
分别挂载了/,
/boot,
/home。
那么这里的了/,/boot,/home就是3个不同的挂载点。
https://github.com/torvalds/linux/blob/master/fs/ext4/ext4.h
/*
* Special inodes numbers
*/
#define EXT4_BAD_INO 1 /* Bad blocks inode */
#define EXT4_ROOT_INO 2 /* Root inode */
#define EXT4_USR_QUOTA_INO 3 /* User quota inode */
#define EXT4_GRP_QUOTA_INO 4 /* Group quota inode */
#define EXT4_BOOT_LOADER_INO 5 /* Boot loader inode */
#define EXT4_UNDEL_DIR_INO 6 /* Undelete directory inode */
#define EXT4_RESIZE_INO 7 /* Reserved group descriptors inode */
#define EXT4_JOURNAL_INO 8 /* Journal inode */
通过这张图,你可以看到,在 VFS 的下方,Linux 支持各种各样的文件系统,如 Ext4、XFS、NFS 等等。按照存储位置的不同,
这些文件系统可以分为三类。
也就是用来访问其他计算机数据的文件系统,比如 NFS、SMB、iSCSI 等。
网络文件系统 NFS:首次突破内核态
随着计算需求的增长,单台计算机的性能逐渐无法满足日益增长的计算和存储要求。
人们开始引入多台计算机,以分担负载并提高整体效率。
在这一场景下,一个应用程序往往需要访问分布在多台计算机上的数据。
为了解决这一问题,人们提出了在网络中引入虚拟存储层的概念,将远程计算机的文件系统(如某个目录)通过网络接口挂载到本地计算机的节点上。
这样做的目的是使本地计算机能够无缝地访问远程计算机的数据,就好像这些数据存储在本地一样。
linux分析利刃之sar命令详解
参考: https://www.cnblogs.com/zsql/p/11628766.html sar -n DEV 1 1#统计网络信息 这个有用带宽统计
查看进行哪些线程
来源: https://zh.wikipedia.org/wiki/Ext4
第四代扩展文件系统(英语:Fourth extended filesystem,缩写为ext4)是Linux系统下的日志文件系统
文件系统(六):一文看懂linux ext4文件系统工作原理
ext4它突出的特点有:数据分段管理、多块分配、延迟分配、持久预分配、日志校验、支持更大的文件系统和文件大小。
ext4文件系统的具体实现比较复杂,本文尝试用比较简单的方式用一篇文章的篇幅来简单地介绍一下它的工作原理。
命令:dumpe2fs /dev/loop0
从上面dumpe2fs的数据上我们可以看出,一个1GB大小的空间,ext4 文件系统将它分隔成了0~7的8个Group。
其中,每个Group中又有superblock、Group descriptors、bitmap、Inode table、usrer data、还有一些保留空间,细分之后的空间布局如下: ext4 的总体磁盘布局如下:
具体含义内: ext4文件系统信息表
从上面《1.1 ext4文件系统信息表》中可以知道Primary superblock在第0号block,每个block的大小为4096Byte
文件系统信息、块大小和块组信息、Inode 相关信息、文件系统大小和使用情况、日志相关信息、挂载信息、校验和和备份信息。
这里面我还需要重点说一下,超级块和块组描述符表都是全局信息,而且这些数据很重要。
如果这些数据丢失了,整个文件系统都打不开了,这比一个文件的一个块损坏更严重。所以,这两部分我们都需要备份,但是采取不同的策略。默认情况下,超级块和块组描述符表都有副本保存在每一个块组里面
其实使用dumpe2fs命令查看的ext4文件系统信息就是从superblock上的数据解析而来。
除了Primary superblock,还在不同的group中有备份superblock,其内容与Primary superblock原始数据相同,Primary superblock损坏的时候可以从备份区恢复回来
回顾:
在 Linux 内核挂载 ext4 文件系统时,挂载流程大致如下:
相关源码主要分布在内核源代码的 fs/ext4/ 目录下,具体可以查看 ext4_mount()、ext4_fill_super() 以及 ext4_iget() 等函数。
❝Linux的ext4文件系统,为什么inode里只有12个指向数据块的直接指针?
代码位置:
- https://github.com/torvalds/linux/blob/master/fs/ext4/ext4.h#L771
struct ext4_inode {
__le32 i_block[EXT4_N_BLOCKS];/* Pointers to blocks */ n=15
};
/*
* Constants relative to the data blocks
*/
#define EXT4_NDIR_BLOCKS 12
#define EXT4_IND_BLOCK EXT4_NDIR_BLOCKS
#define EXT4_DIND_BLOCK (EXT4_IND_BLOCK + 1)
#define EXT4_N_BLOCKS (EXT4_TIND_BLOCK + 1) //14
具体如何保存的呢?
也就是说,我们在 i_block[12]里面放间接块的位置,通过 i_block[12]找到间接块后,间接块里面放数据块的位置,通过间接块可以找到数据块。
EXT4 文件系统采用 索引节点(inode)结构 来管理文件,其中 inode 结构中包含 15 个指针,用于定位文件数据块。
指针类型 | 数量 | 说明 |
---|---|---|
直接块指针(Direct Block) | 12 | 直接指向数据块,每个指针可寻址 1 个数据块。 |
一次间接指针(Singly Indirect) | 1 | 指向一个块,该块内存储多个直接块指针。 |
二次间接指针(Doubly Indirect) | 1 | 指向一个块,该块内的指针指向多个一次间接块。 |
三次间接指针(Triply Indirect) | 1 | 指向一个块,该块内的指针指向多个二次间接块。 |
假设 块大小(block size)为 4KB(EXT4 常用设置),计算单个文件最大大小:
如果需要存储超过 4TB 的单个文件,可以:
对比:
❝带着问题去 阅读:ext4如何存储大文件的,遇得什么麻烦
❝对于大文件来讲,我们要多次读取硬盘才能找到相应的块,这样访问速度就会比较慢。
为了解决这个问题,ext4 做了一定的改变。 它引入了一个新的概念,叫做 Extents。
我们来解释一下 Extents。
比方说,一个文件大小为 128M,如果使用 4k 大小的块进行存储,需要 32k 个块。
如果按照 ext2 或者 ext3 那样散着放,数量太大了。
但是 Extents 可以用于存放连续的块,也就是说,我们可以把 128M 放在一个 Extents 里面。
这样的话,对大文件的读写性能提高了,文件碎片也减少了。
除了根节点,其他的节点都保存在一个块4k里面,4k扣除ext4_extent_header的12个byte,剩下的能够放340项,每个extent最大能表示128MB的数据,340个extent会使你的表示的文件达到42.5GB。
✅前置条件:
在 Linux 中,文件系统(如 ext4)通常只能在块设备(block device)上创建和操作,例如:
如果 没有额外的磁盘,但你仍然需要: 👉 你可以使用 Loop 设备(/dev/loopX)将普通文件当作块设备
📌 例子:用 Loop 设备模拟一个 ext4 文件系统
# 1. 创建一个 1GB 的普通文件文件
dd if=/dev/zero of=/root/temp/virtual_disk.img bs=1M count=1024
//bs 块大小为bytes个字节
# 2. 绑定到 Loop 设备(让 Linux 认为它是一个磁盘)
losetup /dev/loop0 /root/temp/virtual_disk.img
//loop 设备是一种伪设备(pseudo-device)
ls -l /dev/loop0
brw-rw---- 1 root disk 7, 0 3月 24 11:25 /dev/loop0
要从 ls -l 的结果的第一位标识位看出来。
- 表示普通文件;
d 表示文件夹;
c 表示字符设备文件,
b 表示块设备文件,
s 表示套接字 socket 文件,
l 表示符号链接
# 3. 在 /dev/loop0 上创建 ext4 文件系统
mkfs.ext4 /dev/loop0
//mkfs.ext4命令来自英文词组make filesystem Ext4的缩写,其功能是对磁盘设备进行EXT4格式化操作。
创建含有 262144 个块(每块 4k)和 65536 个inode的文件系统
# 4. 挂载到 /mnt 目录
mkdir /mnt/icfs
mount /dev/loop0 /mnt/icfs
//mount - mount a filesystem
# 5. 检查挂载情况
df -h | grep mydisk
ls /mnt/mydisk
准备:
strace 作为一种动态跟踪工具,strace 底层使用内核的 ptrace 特性来实现其功能
,ptrace可以让一个进程监视和控制另一个进程的执行,并且修改被监视进程的内存、寄器等,主要应用于断点调试和系统调用跟踪,strace和gdb工具就是基于ptrace编写的
Open 系统调用说起
这张图十分重要,一定要掌握。因为我们后面的字符设备、块设备、管道、进程间通信、网络等等,全部都要用到这里面的知识。 希望当你再次遇到它的时候,能够马上说出各个数据结构之间的关系
Linux内核官方文档对 FUSE 的解释如下:
What is FUSE? FUSE is a userspace filesystem framework. It consists of a kernel module (fuse.ko), a userspace library (libfuse.*) and a mount utility (fusermount).
思考问题:内核的 fuse.ko 模块,还有 libfuse 库。这两个角色是的作用?
这两个模块一个位于内核,一个位于用户态,是配套使用的,最核心的功能是协议封装和解析。
举给例子,内核 fuse.ko 用于承接 vfs 下来的 io 请求,然后封装成 FUSE 数据包,转发给用户态。
这个时候,用户态文件系统收到这个 FUSE 数据包,它如果想要看懂这个数据包,就必须实现一套 FUSE 协议的代码,这套代码是公开透明的,属于 FUSE 框架的公共的代码
,这种代码不能够让所有的用户文件系统都重复实现一遍,于是 libfuse 用户库就诞生了。
划重点:FUSE 是一个用来实现用户态文件系统的框架
用户态文件系统是区别于内核文件系统的,在用户态文件系统没有出现之前, 常见的文件系统如Ext2、Ext4等都是在内核中直接实现的。
通过这张图,你可以看到,在 VFS 的下方,Linux 支持各种各样的文件系统,如 Ext4、XFS、NFS 等等。按照存储位置的不同,
这些文件系统可以分为三类。
也就是用来访问其他计算机数据的文件系统,比如 NFS、SMB、iSCSI 等。
代码: https://github.com/libfuse/libfuse/blob/master/example/hello.c
这个图的意思是:
背景:一个用户态文件系统,挂载点为 /tmp/fuse ,用户二进制程序文件为 ./hello ;
当执行 ls -l /tmp/fuse 命令的时候,
流程如下: IO 请求先进内核,经 vfs 传递给内核 FUSE 文件系统模块;
内核 FUSE 模块把请求发给到用户态,由 ./hello 程序接收并且处理。
处理完成之后,响应原路返回;
划重点:
实现了 FUSE 的用户态文件系统有非常多的例子,比如,GlusterFS,SSHFS,CephFS,Lustre,GmailFS,EncFS,S3FS
收益:
FUSE 通过将用户空间与内核空间解耦,为开发者提供了在用户空间实现文件系统的巨大灵活性和便利性。
特别是在云计算和分布式存储等现代计算环境中,FUSE 使得构建和维护复杂的存储系统变得更加高效、可定制和易于扩展。
以用户挂载 JuiceFS 后,open 其中一个文件的流程为例。
请求首先通过内核 VFS,然后传递给内核的 FUSE 模块,经过 /dev/fuse 设备与 JuiceFS 的客户端进程通信。
具体步骤如下:
当 JuiceFS mount 后,JuiceFS 内部的 go-fuse 模块会 open /dev/fuse 获取 mount fd,并启动几个线程读取内核的 FUSE 请求;
具体步骤如下:
参考代码: src\ceph_fuse.cc
基本用法:
ceph-fuse处理IO流程
举个例子 lookup /fuse,从lookup /fuse到ceph-fuse的流程图如下
MDS返回结果给ceph-fuse进程。从ceph-fuse到lookup /fuse的流程图如下
const static struct fuse_lowlevel_ops fuse_ll_oper = {
init: do_init,
destroy: 0,
lookup: fuse_ll_lookup,
forget: fuse_ll_forget,
getattr: fuse_ll_getattr,
setattr: fuse_ll_setattr,
readlink: fuse_ll_readlink,
mknod: fuse_ll_mknod,
mkdir: fuse_ll_mkdir,
unlink: fuse_ll_unlink,
rmdir: fuse_ll_rmdir,
symlink: fuse_ll_symlink,
rename: fuse_ll_rename,
link: fuse_ll_link,
open: fuse_ll_open,
read: fuse_ll_read,
write: fuse_ll_write,
flush: fuse_ll_flush,
release: fuse_ll_release,
fsync: fuse_ll_fsync,
opendir: fuse_ll_opendir,
readdir: fuse_ll_readdir,
releasedir: fuse_ll_releasedir,
fsyncdir: fuse_ll_fsyncdir,
statfs: fuse_ll_statfs,
setxattr: fuse_ll_setxattr,
};
cephfs:用户态客户端write
从fuse到cephfs客户端的函数流程如下
mkdir就是创建目录,客户端并不直接创建目录,而是将mkdir的请求(op为CEPH_MDS_OP_MKDIR)发给MDS,然后MDS执行mkdir的操作,并返回创建的目录的元数据。
客户端无非就是发送请求和处理回复。
cephfs:用户态客户端getattr(1)
从libfuse到cephfs用户态客户端的过程
cephfs用户态客户端是从fuse_ll_getattr开始,fuse_ll_getattr的作用是通过cfuse->client->ll_getattr获取inode,填充struct stat信息,并返回该stat信息。
cephfs:用户态客户端getattr(2)
客户端获得的inode来自于MDS(元数据服务器),所以当客户端缓存的某个inode需要更新时,会向MDS发送op为CEPH_MDS_OP_GETATTR的请求。MDS回复的消息中带有最新的inode数据。这种情况下,就得了解发请求和处理请求的流程。
划重点:
s3fs-fuse 是一个基于 FUSE(Filesystem in Userspace)的文件系统,允许 Linux、macOS 和 FreeBSD 系统通过 FUSE 挂载 Amazon S3 存储桶。
通过 s3fs-fuse,用户可以像操作本地文件系统一样操作 S3 存储桶中的文件和目录。该项目的主要编程语言是 C++。
为什么使用fuse --小团队开发 简单为主
https://www.kernel.org/doc/html/latest/filesystems/fuse.html
https://www.cnblogs.com/liwen01/p/18237062
https://juicefs.com/zh-cn/blog/engineering/deepseek-3fs-vs-juicefs
-[7] ceph
https://zhuanlan.zhihu.com/p/27742193940
DeepSeek 3FS 源码解读——协程&RDMA篇
DeepSeek 3FS 源码解读——磁盘 IO 篇