Linux 文件系统学习之 EROFS 源码阅读笔记

前言

学 C 语言已有两个多月,还没尝试过调试一个完整的项目。故借 erofs-utils 项目实战一下,记录一些调试笔记,并对 erofs 文件系统根据源码进行更近一步的梳理

erofs-utils 使用

笔者使用的主机环境为 Ubuntu 18.04,可正常运行

环境配置

可以先查阅 linux 官方文档 https://www.kernel.org/doc/html/latest/filesystems/erofs.html

其中提供了 erofs-utils 的地址

git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git

clone 到本地后,根据 README 可以进行使用。由于 erofs 默认支持 lz4 压缩算法,因此需要安装相应的库,不然 .configure 时会关闭 lz4

sudo apt-get install liblz4-dev

工具编译

	$ ./autogen.sh
	$ ./configure
	$ make

进行压缩

先生成一个 img 为 erofs_disk,并创建测试所用待压缩路径 /home/srcd

# dd if=/dev/zero of=/home/erofs_disk bs=512 count=23000

# mkdir /home/srcd
# cp README /home/srcd
# cp COPYING /home/srcd
# cp ChangeLog /home/srcd
# cp Makefile /home/srcd

使用 lz4 进行压缩

# ./mkfs/mkfs.erofs -zlz4 /home/erofs_disk /home/srcd/

将 img 挂载到某个路径下进行查看

mount -t erofs /home/erofs_disk /mnt/scratch -oloop

vscode 配置

我们需要对 vscode 进行配置,使其能够对 mkfs.erofs 进行调试

安装 C/C++ 插件(这部分不赘述)

在 {workspace} 中创建 .vscode 目录,并在其下创建 launch.json。笔者没有编写 task.json。而是每次手动进行 make

{
   
    "version": "0.2.0",
    "configurations": [
 
        {
   
            "name": "(gdb) Launch",// 配置名称,将会在启动配置的下拉菜单中显示
            "type": "cppdbg",// 配置类型,这里只能为cppdbg
            "request": "launch",// 请求配置类型,可以为launch(启动)或attach(附加)
            "program": "${workspaceRoot}/linux_learn/new_erofs-utils/erofs-utils/mkfs/mkfs.erofs",// 将要进行调试的程序的路径
            "stopAtEntry": true, // 设为true时程序将暂停在程序入口处,我一般设置为true
            "cwd": "${workspaceRoot}",// 调试程序时的工作目录
            "environment": [],// (环境变量?)
            "externalConsole": false,// 调试时是否显示控制台窗口,vscode自带控制台
            "MIMode": "gdb",// 指定连接的调试器,可以为gdb或lldb。
            //"preLaunchTask": "shell" // 调试会话开始前执行的任务,一般为编译程序。
            //与tasks.json的taskName相对应,可根据需求选择是否使用,本文不需要。
            "args": ["-zlz4","/home/erofs_disk","/home/srcd/"]
        }
    ]
}

至此,便能够开始调试了,运行-》启动调试。vscode 便会进入 main 函数,如下所示

在这里插入图片描述

跟踪执行流程

上一篇文章只是根据源码对 erofs 格式化流程进行了大致梳理,在此,笔者通过跟踪的方式,进一步细化对 erofs 的学习,整个过程以下述命令为实际例子。

# ./mkfs/mkfs.erofs -zlz4 /home/erofs_disk /home/srcd/

main

mkfs/main.c ---> main
    --- erofs_init_configure
    --- erofs_mkfs_default_options
    --- mkfs_parse_options_cfg
    --- dev_open
    --- erofs_set_fs_root
    --- erofs_buffer_init
    --- erofs_load_compress_hints
    --- z_erofs_compress_init ()
    	--- erofs_compressor_init
    		--- compressors[i]->init(c)
    --- erofs_generate_devtable
    --- erofs_inode_manager_init
    --- erofs_build_shared_xattrs_from_path
    --- erofs_mkfs_build_tree_from_path ()
    	--- erofs_iget_from_path : 初始化文件系统结构
    	--- erofs_mkfs_build_tree : 压缩文件
    --- erofs_lookupnid()
    --- erofs_mkfs_update_super_block()

由于初始化部分比较简单,笔者主要跟踪后几个方法的执行流程

z_erofs_compress_init

该方法主要调用 erofs_compressor_init 用于初始化 compressors。后续压缩过程需要调用 compressors 的 compress 方法 。 由于命令行传入了 lz4 参数,因此对应 compressors 中只有一个元素,即 erofs_compressor_lz4

static struct erofs_compressor *compressors[] = {
   
#if LZ4_ENABLED
#if LZ4HC_ENABLED
		&erofs_compressor_lz4hc,
#endif
		&erofs_compressor_lz4,
#endif
#if HAVE_LIBLZMA
		&erofs_compressor_lzma,
#endif
};

erofs_mkfs_build_tree_from_path

该函数是最核心的函数,我们一步一步跟踪,对于部分参数,笔者直接替换为真实值

  1. 通过 erofs_iget_from_path 为 “/home/srcd” 创建目录文件 inode。该目录文件对应的是 erofs 文件系统的根目录 / ,其对应了源文件系统的 “/home/srcd” 目录。
  2. 将该 inode 的 parent 指向自己,说明自己是根目录
  3. 调用 erofs_mkfs_build_tree 递归地为根目录创建子目录及文件,并一一对应 “/home/srcd” 下的子目录和文件
struct erofs_inode *erofs_mkfs_build_tree_from_path(struct erofs_inode *parent,
						    const char *path)
{
   
    // *parent = NULL,path = "/home/srcd"
	struct erofs_inode *const inode = erofs_iget_from_path(path, true);

	if (IS_ERR(inode))
		return inode;

	/* a hardlink to the existed inode */
	if (inode->i_parent) {
   
		++inode->i_nlink;
		return inode;
	}

	/* a completely new inode is found */
	if (parent)
		inode->i_parent = parent;
	else
		inode->i_parent = inode;	/* rootdir mark */

	return erofs_mkfs_build_tree(inode);
}

具体地,在执行 erofs_iget_from_path 的过程中,有如下流程

  1. 通过 lstat64 解析 path,可以快速获知当前 path 是目录还是文件
  2. “/home/srcd” 是目录,因此不会执行 erofs_iget 而直接调用 erofs_new_inode 创建一个新的 inode
  3. 通过 erofs_fill_inode 对新 inode 进行初始化
static struct erofs_inode *erofs_iget_from_path(const char *path, bool is_src)
{
   
	struct stat64 st;
	struct erofs_inode *inode;
	int ret;

	/* currently, only source path is supported */
	if (!is_src)
		return ERR_PTR(-EINVAL);

	ret = lstat64(path, &st);
	if (ret)
		return ERR_PTR(-errno);

	/*
	 * lookup in hash table first, if it already exists we have a
	 * hard-link, just return it. Also don't lookup for directories
	 * since hard-link directory isn't allowed.
	 */
	if (!S_ISDIR(st.st_mode)) {
   
		inode = erofs_iget(st.st_dev, st.st_ino);
		if (inode)
			return inode;
	}

	/* cannot find in the inode cache */
	inode = erofs_new_inode();
	if (IS_ERR(inode))
		return inode;

	ret = erofs_fill_inode(inode, &st, path);
	if (ret) {
   
		free(inode);
		return ERR_PTR(ret);
	}

	return inode;
}

在 erofs_fill_inode 中,主要就是装填 inode 的属性。此时,也将 path 设入 inode 的 srcpath 中,建立了源文件系统与目标文件系统的映射关系。

最后,由于是新的 inode 。需要将其插入 inode_hashtable 中。inode_hashtable 可以理解为 inode 的缓存,便于加速 inode 的分配与释放。

static int erofs_fill_inode(struct erofs_inode *inode,
			    struct stat64 *st,
			    const char *path)
{
   
	// 省略部分代码
	inode->i_mode = st->st_mode;
	inode->i_uid = cfg.c_uid == -1 ? st->st_uid : cfg.c_uid;
	inode->i_gid = cfg.c_gid == -1 ? st->st_gid : cfg.c_gid;
	inode->i_ctime = st->st_ctime;
	inode->i_ctime_nsec = ST_CTIM_NSEC(st);
	// 省略部分代码
    inode->i_ctime = sbi.build_time;
    inode->i_ctime_nsec = sbi.build_time_nsec;
  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值