- 作者: 陈孝松
- 主页: chenxiaosong.com
- 哔哩哔哩教学视频: 陈孝松
- 课程: chenxiaosong.com/courses
- 博客: chenxiaosong.com/blog
- 贡献: chenxiaosong.com/contributions
- 邮箱: chenxiaosong@chenxiaosong.com
- QQ交流群: 544216206, 点击查看群介绍
一般的Linux书籍都是先讲解进程和内存相关的知识,但我想先讲解文件系统。
第一,因为我就是做文件系统的,更擅长这一块,其他模块的内容我还要再去好好看看书,毕竟不能误人子弟嘛;第二,是
因为文件系统模块更接近于用户态,是相对比较好理解的内容(当然想深入还是要下大功夫的),由文件系统入手比较适合初学者。
使用
虚拟机启动时,不能使用4k盘,qemu启动命令logical_block_size
和physical_block_size
参数要使用512:
-drive file=1,if=none,format=raw,cache=writeback,file.locking=off,id=dd_1 \
-device scsi-hd,drive=dd_1,id=disk_1,logical_block_size=512,physical_block_size=512 \
格式化磁盘,具体的选项使用man mkfs.minix
查看:
mkfs.minix image # 默认版本1
mkfs.minix -3 /dev/sda # 指定版本3
mkfs.minix image
的输出如下:
21856 inodes
65535 blocks
Firstdatazone=696 (696)
Zonesize=1024 # v1的zone大小
Maxsize=268966912
挂载文件系统:
mount -t minix /dev/sda /mnt
或者格式化文件,通过loop设备挂载,注意这时需要打开CONFIG_BLK_DEV_LOOP
配置。
独立模块编译
如果我们要在minix文件系统的基础上再开发,为了方便开发测试,可以fs/minix
复制出来,然后打上补丁0001-myminix.patch
,这里我把文件系统类型名改为了myminix
,挂载时要指定挂载选项,如通过loop设备挂载:
mount -t myminix -o loop image /mnt
util-linux
用户态工具源码包含在util-linux
中,github仓库。
编译参考Documentation/howto-compilation.txt
。
apt install -y autopoint gettext flex bison sqlite3 libsqlite3-dev
./autogen.sh && ./configure && make -j`nproc`
# make install # 默认安装到/usr/sbin/mkfs.minix
数据结构
- 超级块结构。
- 磁盘超级块结构
struct minix_super_block
和struct minix3_super_block
- 内存超级块结构
struct minix_sb_info
,赋值给struct super_block
的s_fs_info
成员
- 超级块操作方法
minix_sops
。 - 索引节点结构。
- 磁盘索引节点结构
struct minix_inode
和struct minix2_inode
- 内存索引节点结构
struct minix_inode_info
- 各种类型文件的索引节点操作方法:
- 常规文件
minix_file_inode_operations
。 - 目录
minix_dir_inode_operations
。 - 符号链接(路径名小于60字节)
minix_symlink_inode_operations
。
dentry
操作方法,minix没有定义- 各种类型文件的
file
操作方法:
- 常规文件
minix_file_operations
。 - 目录
minix_dir_operations
。 - 其他类型查看
init_special_inode()
函数。
- 各种类型文件的
address_space
操作方法,常规文件、目录、符号链接都是minix_aops
- 文件系统类型
minix_fs_type
。 - 模块加载卸载方法,
init_minix_fs
和exit_minix_fs
。
其他重要的数据结构:
typedef struct {
block_t *p; // key在内存中的地址
block_t key; // 块号
struct buffer_head *bh; // 缓冲头,内存中保存块的数据
} Indirect;
函数流程
写文件流程:
write
ksys_write
vfs_write
new_sync_write
generic_file_write_iter
__generic_file_write_iter
generic_perform_write
minix_write_begin
block_write_begin
__block_write_begin_int
minix_get_block
V1_minix_get_block
get_block // 这里的bh已经分配内存了
block_to_path
offsets[n++] = block // if (block < 7) 直接块
// depth=1时直接指向数据,depth=2时一次间接地址
// Zonesize=1024,v1版本DIRECT = 7,所以当写的文件大小超过7168字节时,depth=2
get_branch
i_data(inode)
return u.i1_data
add_chain(i1_data + *offsets)
Indirect->p = block_t *
Indirect->key = block_t
Indirect->bh = buffer_head *
sb_bread // 根据块号和块大小获取数据,返回buffer_head
alloc_branch // 如果块没找到
parent = minix_new_block // 获得新块,只是设置bitmap
// 间接块才往下走
nr = minix_new_block(inode)
bh = sb_getblk // 获取间接块对应的buffer_head
map_bh // 将buffer_head映射到块
支持长文件名
我们来看一个有趣的问题: 让minix文件系统(v3)支持最大长度4095字节的文件名。
当我们使用touch
命令创建一个4095字节长度的文件时,会执行到minix_lookup
函数。而当创建一个4096字节长度的文件时,不会执行到minix_lookup
函数,说明在vfs
已经拦截了。
相关代码流程如下:
openat
do_sys_open
do_sys_openat2
getname
getname_flags
len = strncpy_from_user(kname, filename, EMBEDDED_NAME_MAX) = 4064 // EMBEDDED_NAME_MAX 为 4096-32
// touch <4095字节文件名> 时 len = 4095, 会调用到 minix_lookup
// touch <4096字节文件名> 时 len = 4096, 不会调用到 minix_lookup
len = strncpy_from_user(kname, filename, PATH_MAX)
if (unlikely(len == PATH_MAX))
return ERR_PTR(-ENAMETOOLONG) // touch <4096字节文件名> 时
do_filp_open
path_openat
open_last_lookups
lookup_open
minix_lookup
// s_namelen 的值在 minix_fill_super 中设置,minix v3 为 60字节
return ERR_PTR(-ENAMETOOLONG) // touch <4095字节文件名> 时
如果当路径中前面有其他路径时(如/mnt/<4095字节文件名>
就有4100个字节),会被vfs拦截,所以当要支持4095字节长度时,要在vfs
做修改。而大部分文件系统支持的最大文件名长度为255字节,所以我们可以这样设计: 当文件名(普通文件和文件夹)大于255字节时,在vfs
对文件名做hash映射,当文件名(普通文件和文件夹)大于minix v3文件系统最大支持的60字节时,在minix文件系统对文件名做hash映射。
暂时只对最后一个路径名作hash映射,后续再补充支持对中间路径名进行hash映射,补丁为0001-minix-support-long-file-name.patch
。