38、UNIX文件系统uxfs的设计、实现与实践探索

UNIX文件系统uxfs的设计、实现与实践探索

1. 文件系统状态接口

在Linux系统中, df 命令是一个常用的工具,用于显示文件系统的使用信息,如空闲块和已使用块的数量等。 uxfs 文件系统通过 super_block 操作向量导出了 ux_statfs() 函数,该函数会在 df 调用 statfs 系统调用(针对每个文件系统调用一次)时被触发。

ux_statfs() 函数的原型如下:

int 
ux_statfs(struct super_block *sb, struct statfs *buf);

df 命令会为每个挂载的文件系统调用 statfs() 系统调用,其原型如下:

int statfs(const char *path, struct statfs *buf);

这里使用的 statfs 结构体定义如下:

struct statfs {
    long    f_type;     /* type of filesystem (see below) */
    long    f_bsize;    /* optimal transfer block size */
    long    f_blocks;   /* total data blocks in file system */
    long    f_bfree;    /* free blocks in fs */
    long    f_bavail;   /* free blocks avail to non - superuser */
    long    f_files;    /* total file nodes in file system */
    long    f_ffree;    /* free file nodes in fs */
    fsid_t  f_fsid;     /* file system id */
    long    f_namelen;  /* maximum length of filenames */
};

这个结构体传递的信息对应着文件系统的限制,如文件系统中文件和块的总数,以及现有的空闲资源,如可用文件和数据块的数量。

下面是在 kdb 中设置断点,当内核进入 ux_statfs() 时停止的示例:

Entering kdb (current=0xc03b0000, pid 0) on processor 0 due to Keyboard Entry
[0]kdb> bp ux_statfs
Instruction(i) BP #0 at 0xd08bb400 ([uxfs]ux_statfs)
    is enabled globally adjust 1
[0]kdb> bl
Instruction(i) BP #0 at 0xd08bb400 ([uxfs]ux_statfs)
    is enabled globally adjust 1
[0]kdb> go

当运行 df 命令并到达 ux_statfs() 时,断点被触发,内核进入 kdb 。此时可以使用 bt 命令显示堆栈回溯,展示内核是如何通过系统调用,经过 sys_statfs() vfs_statfs() 后进入 ux_statfs() 的。

2. 文件系统操作示例

以下是一系列操作 uxfs 文件系统的示例:
- 创建文件系统:

# ./mkfs /dev/fd0
# insmod ./uxfs
# mount -t uxfs /dev/fd0 /mnt
  • 查看文件系统使用情况:
# df -k
Filesystem           1k - blocks      Used Available Use% Mounted on
/dev/hda2             15120648   2524836  11827716  18% /
/dev/hda1               102454     11147     86017  12% /boot
/dev/hda5               497829      8240    463887   2% /home
none                    127076         0    127076   0% /dev/shm
/dev/fd0                  1000         1       999   1% /mnt
  • 创建目录后再次查看:
# mkdir /mnt/dir
# df -k
Filesystem           1k - blocks      Used Available Use% Mounted on
/dev/hda2             15120648   2524836  11827716  18% /
/dev/hda1               102454     11147     86017  12% /boot
/dev/hda5               497829      8240    463887   2% /home
none                    127076         0    127076   0% /dev/shm
/dev/fd0                  1000         2       998   1% /mnt
  • 查看inode分配信息:
# df -i /mnt
Filesystem            Inodes   IUsed   IFree IUse% Mounted on
/dev/fd0                  32       4      28   13% /mnt
# mkdir /mnt/mydir
# df -i /mnt
Filesystem            Inodes   IUsed   IFree IUse% Mounted on
/dev/fd0                  32       5      27   16% /mnt

从上述示例可以看出,创建目录时会分配一个inode和一个数据块,这会反映在 df 命令显示的信息中。

3. 文件系统源代码

uxfs 文件系统的源代码包含多个文件,以下是这些文件及其功能概述:

文件名称 功能描述 代码行数
ux_fs.h 包含文件系统使用的结构定义,如超级块、inode、目录项等,以及全局参数 1 - 103
mkfs.c uxfs mkfs 命令的源代码 104 - 263
fsdb.c uxfs fsdb 命令的源代码 264 - 394
ux_alloc.c 包含分配inode和数据块的例程 395 - 470
ux_dir.c 包含通过 inode_operations 向量导出的函数,如文件和目录创建、名称解析等 471 - 912
ux_file.c 包含读写文件所需的例程,主要是用于检索文件数据块的分配bmap接口 913 - 1008
ux_inode.c 包含对整个文件系统进行操作的例程,如模块初始化和反初始化 1009 - 1317

以下是 ux_fs.h 文件中部分关键代码:

#define UX_NAMELEN              28        
#define UX_DIRS_PER_BLOCK       16
#define UX_DIRECT_BLOCKS        16
#define UX_MAXFILES             32
#define UX_MAXBLOCKS            470
#define UX_FIRST_DATA_BLOCK     50
#define UX_BSIZE                512
#define UX_BSIZE_BITS           9
#define UX_MAGIC                0x58494e55
#define UX_INODE_BLOCK          8
#define UX_ROOT_INO             2

struct ux_superblock {
    __u32        s_magic;
    __u32        s_mod;
    __u32        s_nifree;
    __u32        s_inode[UX_MAXFILES];
    __u32        s_nbfree;
    __u32        s_block[UX_MAXBLOCKS];
};

struct ux_inode {
    __u32        i_mode;
    __u32        i_nlink;
    __u32        i_atime;
    __u32        i_mtime;
    __u32        i_ctime;
    __s32        i_uid;
    __s32        i_gid;
    __u32        i_size;
    __u32        i_blocks;
    __u32        i_addr[UX_DIRECT_BLOCKS];
};

struct ux_dirent {
    __u32       d_ino;
    char        d_name[UX_NAMELEN];
};

struct ux_fs {
    struct ux_superblock      *u_sb;
    struct buffer_head        *u_sbh;
};

#ifdef __KERNEL__
extern ino_t ux_ialloc(struct super_block *);
extern int ux_find_entry(struct inode *, char *);
__u32 ux_block_alloc(struct super_block *);
extern __u32 ux_block_alloc(struct super_block *);
extern int ux_unlink(struct inode *, struct dentry *);
extern int ux_link(struct dentry *, struct inode *, 
                   struct dentry *);
#endif
4. 代码流程分析

下面是一个简单的mermaid流程图,展示了文件系统操作的大致流程:

graph TD;
    A[启动系统] --> B[挂载uxfs文件系统];
    B --> C[执行df命令];
    C --> D[调用statfs系统调用];
    D --> E[触发ux_statfs函数];
    E --> F[返回文件系统使用信息];
    B --> G[创建目录或文件];
    G --> H[分配inode和数据块];
    H --> I[更新文件系统信息];
    I --> J[再次执行df命令查看变化];

通过这个流程图,可以清晰地看到在系统启动、文件系统挂载、文件或目录操作以及信息查看等过程中各个环节之间的关系。

5. 从基础到进阶:uxfs文件系统的实践探索

uxfs 文件系统虽然提供了基本的操作功能,但仍有许多提升空间和待修复的问题。以下为大家提供一系列不同难度的练习,帮助大家深入理解和改进 uxfs 文件系统。

5.1 初级到中级练习

这些练习可以在不改变底层磁盘布局的情况下对现有文件系统进行改进,部分练习需要仔细分析和一定程度的测试。

  • uxfs魔数的意义 :探究 uxfs 文件系统魔数的重要性,魔数通常用于标识文件系统的类型,在文件系统的识别和验证中起着关键作用。
  • 启用调试功能 :利用 ux_read_super() 函数的 silent 参数来启用调试功能。可以在文件系统中添加一些 printk() 调用,这些调用仅在指定 silent 选项时激活。首先需要确定 silent 标志设置的条件, ux_read_super() 函数提供了该参数的使用示例。
  • 未实现功能分析 uxfs 存在一些未实现的功能,如符号链接。通过查看各种操作向量,确定哪些文件操作无法正常工作,并找出这些函数在内核中的调用位置。
  • 时间戳更新 :在 uxfs 文件系统的大多数操作中,各种时间戳没有得到更新。可以通过与其他 Linux 文件系统(如 ext2 )进行比较,找出时间戳更新缺失的区域,并对文件系统进行相应修改以提供这些更新。
  • 超级块状态更新 :当文件系统挂载时,超级块字段 s_mod 应设置为 UX_FSDIRTY ,并将超级块写回磁盘。 ux_read_super() 中已有处理和拒绝脏文件系统的代码,但存在一个需要修复的 bug 才能使此功能正常工作。可以在 fsdb 中添加一个选项来标记超级块为脏,以帮助测试此功能。
  • 构建测试设备 :在互联网上查找 Loopback Filesystem HOWTO ,并使用它来构建一个可以创建 uxfs 文件系统的设备。
  • 资源释放问题修复 :文件系统中存在一些 inode 和缓冲区未正确释放的地方。在执行某些操作并卸载文件系统时,内核会显示警告信息,需要找出这些问题并进行修复。
5.2 高级练习

这些练习需要对文件系统进行更多修改,可能需要对命令和/或内核源代码进行重大调整。

  • 实现fsck命令 :如果系统崩溃,文件系统可能会处于不稳定状态。实现一个 fsck 命令,该命令可以检测并修复任何此类不一致性。一种测试 fsck 版本的方法是修改 fsdb 以故意破坏文件系统。研究目录创建等操作,了解创建目录需要多少 I/O 操作,通过模拟部分 I/O 操作,使文件系统处于结构不完整的状态。
  • 引入间接块概念 :引入间接、双重间接和三重间接块的概念。允许从 inode 直接引用 6 个直接块、2 个间接块和 1 个三重间接块。计算这种设置允许的文件大小。
  • 模块卸载问题解决 :当模块出现故障时,内核通常能够检测到 uxfs 模块的问题并继续运行。但如果 uxfs 文件系统已经挂载,由于文件系统繁忙,模块无法卸载。需要寻找方法卸载文件系统,以便卸载模块。
  • SMP环境优化 uxfs 文件系统在 SMP(对称多处理)环境中表现不佳。通过分析其他 Linux 文件系统,提出改进建议,使 uxfs 能够在 SMP 系统中运行。可以考虑采用粗粒度和细粒度锁机制。
  • 目录结构优化 :删除目录项会在目录结构中留下空隙。编写一个用户级程序,进入文件系统并重新组织目录,以消除未使用的空间。思考可以使用哪些机制进入文件系统。
  • 使用位图管理资源 :修改文件系统,使用位图来管理 inode 和数据块。确保位图和块图与实际的超级块分开。这将涉及对现有磁盘布局和用于管理文件系统资源的内核结构进行重大修改。
  • 用户自定义参数 :允许用户指定文件系统块大小和文件系统大小。这将涉及更改磁盘上的布局。

通过完成这些练习,你可以更深入地理解 uxfs 文件系统的工作原理,并提升自己在文件系统开发和调试方面的技能。希望大家在实践中不断探索,挖掘 uxfs 文件系统的更多潜力。

UNIX文件系统uxfs的设计、实现与实践探索

6. 代码详细解析
6.1 ux_alloc.c文件

ux_alloc.c 文件主要负责inode和数据块的分配。以下是该文件中关键函数的详细解析:

#include <linux/module.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/locks.h>
#include <linux/smp_lock.h>
#include <asm/uaccess.h>
#include "ux_fs.h"

/*
 * Allocate a new inode. We update the superblock and return
 * the inode number.
 */
ino_t
ux_ialloc(struct super_block *sb)
{
    struct ux_fs          *fs = (struct ux_fs *)sb->s_private;
    struct ux_superblock  *usb = fs->u_sb;
    int                   i;

    if (usb->s_nifree == 0) {
        printk("uxfs: Out of inodes\n");
        return 0;
    }
    for (i = 3 ; i < UX_MAXFILES ; i++) {
        if (usb->s_inode[i] == UX_INODE_FREE) {
            usb->s_inode[i] = UX_INODE_INUSE;
            usb->s_nifree--;
            sb->s_dirt = 1;
            return i;
        }
    }
    printk("uxfs: ux_ialloc - We should never reach here\n");
    return 0;
}

/*
 * Allocate a new data block. We update the superblock and return
 * the new block  number.
 */
__u32
ux_block_alloc(struct super_block *sb)
{
    struct ux_fs          *fs = (struct ux_fs *)sb->s_private;
    struct ux_superblock  *usb = fs->u_sb;
    int                   i;

    if (usb->s_nbfree == 0) {
        printk("uxfs: Out of space\n");
        return 0;
    }

    /*
     * Start looking at block 1. Block 0 is 
     * for the root directory.
     */
    for (i = 1 ; i < UX_MAXBLOCKS ; i++) {
        if (usb->s_block[i] == UX_BLOCK_FREE) {
            usb->s_block[i] = UX_BLOCK_INUSE;
            usb->s_nbfree--;
            sb->s_dirt = 1;
            return UX_FIRST_DATA_BLOCK + i;
        }
    }
    printk("uxfs: ux_block_alloc - "
           "We should never reach here\n");
    return 0;
}
  • ux_ialloc 函数:用于分配一个新的inode。首先检查是否还有空闲的inode,如果没有则打印错误信息并返回0。然后遍历inode数组,找到第一个空闲的inode,将其标记为已使用,更新空闲inode数量,标记超级块为脏,并返回该inode的编号。
  • ux_block_alloc 函数:用于分配一个新的数据块。同样先检查是否还有空闲的数据块,若没有则打印错误信息并返回0。从第1个块开始遍历数据块数组,找到第一个空闲的数据块,将其标记为已使用,更新空闲数据块数量,标记超级块为脏,并返回该数据块的编号。
6.2 ux_dir.c文件

ux_dir.c 文件包含了与目录操作相关的函数,如目录项的添加、删除、读取等。以下是部分关键函数的解析:

#include <linux/sched.h>
#include <linux/string.h>
#include <linux/locks.h>
#include "ux_fs.h"

/*
 * Add "name" to the directory "dip"
 */
int
ux_diradd(struct inode *dip, const char *name, int inum)
{
    struct ux_inode       *uip = (struct ux_inode *)
                                  &dip->i_private;
    struct buffer_head    *bh;
    struct super_block    *sb = dip->i_sb;
    struct ux_dirent      *dirent;
    __u32                 blk = 0;
    int                   i, pos;

    for (blk=0 ; blk < uip->i_blocks ; blk++) {
        bh = sb_bread(sb, uip->i_addr[blk]);
        dirent = (struct ux_dirent *)bh->b_data;
        for (i=0 ; i < UX_DIRS_PER_BLOCK ; i++) {
            if (dirent->d_ino != 0) {
                dirent++;
                continue;
            } else {
                dirent->d_ino = inum;
                strcpy(dirent->d_name, name);
                mark_buffer_dirty(bh);
                mark_inode_dirty(dip);
                brelse(bh);
                return 0;
            }
        }
        brelse(bh);
    }

    /*
     * We didn't find an empty slot so need to allocate 
     * a new block if there’s space in the inode.
     */
    if (uip->i_blocks < UX_DIRECT_BLOCKS) {
        pos = uip->i_blocks;
        blk = ux_block_alloc(sb);
        uip->i_blocks++;
        uip->i_size += UX_BSIZE;
        dip->i_size += UX_BSIZE;
        dip->i_blocks++;
        uip->i_addr[pos] = blk;
        bh = sb_bread(sb, blk);
        memset(bh->b_data, 0, UX_BSIZE);
        mark_inode_dirty(dip);
        dirent = (struct ux_dirent *)bh->b_data;
        dirent->d_ino = inum;
        strcpy(dirent->d_name, name);
        mark_buffer_dirty(bh);
        brelse(bh);
    }

    return 0;
}

/*
 * Remove "name" from the specified directory.
 */
int
ux_dirdel(struct inode *dip, char *name)
{
    struct ux_inode         *uip = (struct ux_inode *)
                                    &dip->i_private;
    struct buffer_head      *bh;
    struct super_block      *sb = dip->i_sb;
    struct ux_dirent        *dirent;
    __u32                   blk = 0;
    int                     i;

    while (blk < uip->i_blocks) {
        bh = sb_bread(sb, uip->i_addr[blk]);
        blk++;
        dirent = (struct ux_dirent *)bh->b_data;
        for (i=0 ; i < UX_DIRS_PER_BLOCK ; i++) {
            if (strcmp(dirent->d_name, name) != 0) {
                dirent++;
                continue;
            } else {
                dirent->d_ino = 0;
                dirent->d_name[0] = '\0';
                mark_buffer_dirty(bh);
                dip->i_nlink--;
                mark_inode_dirty(dip);
                break;
            }
        }
        brelse(bh);
    }
    return 0;
}
  • ux_diradd 函数:用于向指定目录添加一个新的目录项。首先遍历目录的所有数据块,查找空闲的目录项槽位。如果找到,则将新的目录项信息写入该槽位,并标记缓冲区和inode为脏。如果没有找到空闲槽位,且inode还有空间,则分配一个新的数据块,将新的目录项写入该块,并更新相关信息。
  • ux_dirdel 函数:用于从指定目录中删除一个目录项。遍历目录的所有数据块,查找与指定名称匹配的目录项。如果找到,则将该目录项的inode编号置为0,名称清空,标记缓冲区和inode为脏,并减少目录的链接计数。
7. 练习操作步骤示例
7.1 启用调试功能

以下是启用 uxfs 文件系统调试功能的具体操作步骤:
1. 确定 silent 标志设置条件 :查看 ux_read_super() 函数,找到 silent 参数的使用逻辑。通常, silent 标志用于控制是否输出调试信息。
2. 添加 printk() 调用 :在需要调试的代码位置添加 printk() 调用,并使用条件判断确保这些调用仅在 silent 选项指定时激活。例如:

if (!silent) {
    printk("Debug message: Entering ux_read_super()\n");
}
  1. 编译和测试 :重新编译 uxfs 模块,并加载到内核中。在挂载文件系统时,根据需要设置 silent 参数,观察是否输出调试信息。
7.2 实现fsck命令

实现 fsck 命令以检测和修复文件系统不一致性的步骤如下:
1. 分析文件系统结构 :深入了解 uxfs 文件系统的磁盘布局,包括超级块、inode、数据块等的组织方式。
2. 设计检测逻辑 :编写代码来检测文件系统中的各种不一致性,如inode和数据块的使用情况、目录项的完整性等。
3. 实现修复逻辑 :针对检测到的不一致性,设计相应的修复算法。例如,对于丢失的inode或数据块,可以尝试重新分配或标记为空闲。
4. 修改 fsdb 进行测试 :修改 fsdb 命令,使其能够故意破坏文件系统,以便测试 fsck 命令的修复能力。
5. 测试和优化 :使用修改后的 fsdb 破坏文件系统,然后运行 fsck 命令进行修复,检查修复结果并进行必要的优化。

8. 总结与展望

通过对 uxfs 文件系统的深入研究,我们了解了其基本原理、源代码实现以及如何进行调试和改进。从文件系统状态接口的理解,到各种操作示例的实践,再到源代码的详细解析,我们逐步揭开了 uxfs 的神秘面纱。

然而, uxfs 文件系统仍有许多可以改进的地方。在未来的工作中,我们可以继续完成前面提到的练习,进一步完善 uxfs 文件系统的功能。例如,实现更复杂的文件操作,如符号链接;优化文件系统在多处理器环境下的性能;提高文件系统的可靠性和稳定性等。

同时,我们也可以将 uxfs 文件系统的设计思想和实现方法应用到其他项目中,为开发更高效、更可靠的文件系统提供参考。希望大家在文件系统的学习和实践中不断探索,取得更多的成果。

以下是一个mermaid流程图,展示了从文件系统问题检测到修复的大致流程:

graph TD;
    A[启动fsck命令] --> B[检测文件系统];
    B --> C{是否存在不一致性};
    C -- 是 --> D[分析不一致性类型];
    D --> E[选择修复策略];
    E --> F[执行修复操作];
    F --> G[再次检测文件系统];
    G --> C;
    C -- 否 --> H[结束fsck命令];

这个流程图展示了 fsck 命令在检测和修复文件系统不一致性时的循环过程,直到文件系统恢复正常。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值