35、开发 Linux 内核的 uxfs 文件系统

开发 Linux 内核的 uxfs 文件系统

1. 内核级调试与 gdb 的使用

在开发过程中,需要进入调试器来设置断点等操作。本文将全程展示如何使用 gdb 进行内核级调试。

2. 构建 uxfs 文件系统

2.1 下载与解压源码

www.wiley.com/compbooks/spate 下载的源码树是一个经过 gzip 压缩的 tar 归档文件。可以将其下载到任意目录,然后执行以下命令进行解压:

# gunzip uxfs.tar.gz
# tar xvf uxfs.tar
# ls
uxfs.tar
uxfs
# ls uxfs
cmds
kern

2.2 构建命令

构建命令非常简单,只需将 uxfs.h 头文件放在 "../kern" 目录中即可。要构建每个命令,进入 cmds 目录并执行以下命令:

# make fsdb
cc fsdb.c -o fsdb
# make fsdb
cc fsdb.c -o fsdb

执行完上述命令后,这些命令就可以使用了。

2.3 内核 Makefile

内核 Makefile 相对简单,内容如下:

KERNELDIR = /usr/src/linux
include $(KERNELDIR)/.config
FLAGS = -D__KERNEL__ -DMODULE $(VERCFLAGS)
GLOBAL_CFLAGS = -g -I$(KERNELDIR)/include $(FLAGS)
M_OBJS = ux_dir.o ux_alloc.o ux_file.o ux_inode.o
M_TARGET = uxfs
SRCS = $(M_OBJS:.o=.c)
CFLAGS = $(GLOBAL_CFLAGS) $(EXTRA_CFLAGS)
$(M_TARGET) : $(M_OBJS)
    ld -r -o $@ $(M_OBJS)
$(M_OBJS) : %.o : %.c
    $(CC) -c $(CFLAGS) -o $@ $<
all: uxfs
clean:
    rm -f $(M_OBJS) $(M_TARGET)

要构建内核源码,必须修改 Makefile 顶部的 KERNELDIR 变量,使其指向内核源码目录。设置好该变量后,可以按以下方式构建内核:

# make uxfs
cc -c -g -I/usr/src/linux/include -D__KERNEL__ -DMODULE -o ux_dir.o ux_dir.c
cc -c -g -I/usr/src/linux/include -D__KERNEL__ -DMODULE -o ux_alloc.o ux_alloc.c
cc -c -g -I/usr/src/linux/include -D__KERNEL__ -DMODULE -o ux_file.o ux_file.c
cc -c -g -I/usr/src/linux/include -D__KERNEL__ -DMODULE -o ux_inode.o ux_inode.c
ld -r -o uxfs ux_dir.o ux_alloc.o ux_file.o ux_inode.o

上述命令会生成 uxfs 模块,后续可以将其加载到内核中。

3. 创建 uxfs 文件系统

3.1 编写 mkfs 命令

开发新文件系统的第一步是编写 mkfs 命令,将初始文件系统布局放置到磁盘上。这包括以下任务:
- 创建并初始化文件系统超级块,然后将其写入磁盘。
- 创建根目录索引节点和 lost+found 目录索引节点。对于每个索引节点,确保 . .. 条目存在,并在根目录中添加 lost+found 条目。
- 在索引节点映射中记录这两个目录的分配情况。
- 记录根目录和 lost+found 目录使用的两个块的分配情况。

mkfs 的代码位于第 104 行到第 262 行。对于 uxfs 而言,这是一个相当简单的程序。与内核一样,它使用 ux_fs.h 中的各种结构定义和信息,包括超级块结构信息、索引节点格式、目录条目以及各种文件系统边界,如最大块数和索引节点数。

3.2 编写 fsdb 命令

在实现文件系统之前,验证 mkfs 写入磁盘的信息非常重要。因此,接下来要编写的程序是 fsdb ,它可以读取并显示各种超级块和索引节点信息。

fsdb 命令(第 264 行到第 393 行)非常简单,它接受两个命令,允许显示超级块或指定的索引节点。其工作流程如下:

graph TD
    A[开始] --> B[读取超级块到内存]
    B --> C[验证超级块]
    C --> D[将超级块保留在内存中]
    D --> E[进入主循环]
    E --> F{读取命令}
    F -- 超级块命令 --> G[显示超级块信息]
    F -- 索引节点命令 --> H[显示指定索引节点信息]
    F -- 'q' 命令 --> I[退出程序]
    G --> E
    H --> E

以下是在新创建的文件系统上运行 fsdb 的示例输出:

# ./mkfs /dev/fd0
# ./fsdb /dev/fd0
uxfsdb > s
Superblock contents:
  s_magic   = 0x58494e55
  s_mod     = UX_FSCLEAN
  s_nifree  = 28
  s_nbfree  = 468
uxfsdb > i2
inode number 2
  i_mode     = 41ed
  i_nlink    = 3
  i_atime    = Wed Aug 21 09:55:16 2002
  i_mtime    = Wed Aug 21 09:55:16 2002
  i_ctime    = Wed Aug 21 09:55:16 2002
  i_uid      = 0
  i_gid      = 0
  i_size     = 512
  i_blocks   = 1
  i_addr[ 0] =  50   i_addr[ 1] =   0   i_addr[ 2] =   0   i_addr[ 3] = 0 
  i_addr[ 4] =   0   i_addr[ 5] =   0   i_addr[ 6] =   0   i_addr[ 7] = 0 
  i_addr[ 8] =   0   i_addr[ 9] =   0   i_addr[10] =   0   i_addr[11] = 0 
  i_addr[12] =   0   i_addr[13] =   0   i_addr[14] =   0   i_addr[15] = 0
  Directory entries:
    inum[ 2],name[.]
    inum[ 2],name[..]
    inum[ 3],name[lost+found]
uxfsdb > q

4. 模块初始化与反初始化

编写可加载内核模块时,需要定义以下三件事:
- 一个声明,提供有关模块类型的信息。
- 一个在模块加载时调用的函数,该函数可以执行任何初始化功能,包括向内核注册文件系统类型。
- 一个在模块卸载时调用的函数,该函数可以清理任何剩余的文件系统结构并注销文件系统。

适用于 uxfs 的各种组件在 ux_inode.c 的第 1304 行到第 1317 行中显示。 module_init() 调用指定了模块加载时要运行的函数,而 module_exit() 函数指定了模块卸载时要调用的函数。这两个函数除了分别注册和注销文件系统驱动程序外,几乎不执行其他工作。 DECLARE_FSTYPE_DEV() 宏如下所示:

#define DECLARE_FSTYPE(var,type,read,flags) \
struct file_system_type var = { \
    name: type, \
    read_super: read, \
    fs_flags: flags, \
    owner: THIS_MODULE, \
}
#define DECLARE_FSTYPE_DEV(var,type,read) \
DECLARE_FSTYPE(var,type,read,FS_REQUIRES_DEV)

内核维护一个包含所有此类结构的列表,每个文件系统对应一个条目。调用 register_filesystem() 时会添加 uxfs 的条目。当 mount 系统调用进入内核时,会将传递给 mount 的文件系统名称与每个 file_system_type 结构的 name 字段进行比较。如果找到匹配项,则调用 read_super 函数来挂载文件系统。

使用 rmmod 命令来移除内核模块。如果仍有文件系统挂载,则移除操作将失败;否则,内核将调用模块退出函数,对于 uxfs 来说,就是 exit_uxfs_fs() 函数,该函数只需调用 unregister_filesystem() 即可。

5. 测试新文件系统

以下示例展示了如何创建 uxfs 文件系统、加载内核模块、卸载文件系统以及卸载模块:

# ./mkfs /dev/fd0
# insmod ./uxfs
# lsmod
Module                  Size  Used by    Not tainted
uxfs                    8608   0  (unused)
ext3                   71968   2  (autoclean)
jbd                    66208   2  (autoclean) [ext3]
# mount -t uxfs /dev/fd0 /mnt
# mount
/dev/hda2 on / type ext3 (rw)
none on /proc type proc (rw)
/dev/hda1 on /boot type ext3 (rw)
none on /dev/pts type devpts (rw,gid=5,mode=620)
/dev/hda5 on /home type ext3 (rw)
none on /dev/shm type tmpfs (rw)
/dev/fd0 on /mnt type uxfs (rw)
# rmmod uxfs
uxfs: Device or resource busy
# umount /mnt
# rmmod uxfs
# lsmod
Module                  Size  Used by    Not tainted
ext3                   71968   2  (autoclean)
jbd                    66208   2  (autoclean) [ext3]

上述命令序列仅用于说明如何挂载 uxfs 文件系统的基本步骤。 lsmod 显示的模块是实际二进制文件的名称,与源代码没有任何相似之处。

6. 挂载和卸载文件系统

6.1 挂载文件系统

ux_read_super() 函数用于挂载 uxfs 文件系统。该函数通过 DECLARE_FSTYPE_DEV() 宏声明,并在文件系统注册时被 Linux 内核识别。该函数的代码位于 ux_inode.c 的第 1240 行到第 1302 行。

ux_read_super() 函数接受三个参数,如下所示:

ux_read_super(struct super_block *s, void *data, int silent)

每个挂载的文件系统都有一个 super_block 结构。 ux_read_super() 的任务之一是通过填充以下字段来初始化该结构:
- s_magic :该字段保存文件系统的魔数,对于 uxfs 来说是 0x58494e55 ,该字段实际价值不大。
- s_blocksize :该字段保存文件系统的块大小,对于 uxfs 来说是 512 字节( UX_BSIZE )。
- s_op :该字段保存超级操作向量,这是一组函数,用于处理整个文件系统或允许读取、写入和删除索引节点。
- s_root :该字段设置为引用根索引节点的目录项。

data 参数由内核用于传递传递给 mount 的任何参数。目前,uxfs 不接受 mount 的任何命令行参数,因此该参数被忽略。 silent 参数如果设置,则允许文件系统开发者在运行时显示更详细的信息,这有助于显示调试信息。

ux_read_super() 函数还必须执行以下任务:
- 调用 set_blocksize() 来向底层驱动层指定通过缓冲区缓存访问数据时将传递的 I/O 单位。请注意,所有后续 I/O 必须是固定大小的块。
- 为文件系统分配并初始化根索引节点。

以下是在 gdb 中设置断点、显示堆栈回溯以及显示各种结构的示例:

(gdb) b ux_read_super
Breakpoint 1 at 0xd08557ca: file ux_inode.c, line 237.
(gdb) c
Continuing.
# mount -f uxfs /dev/fd0 /mnt
Breakpoint 1, ux_read_super (s=0xcf15a400, data=0x0, silent=0)
    at ux_inode.c:237
237        dev = s->s_dev;
(gdb) list
232        struct ux_fs              *fs;
233        struct buffer_head        *bh;
234        struct inode              *inode;
235        kdev_t                    dev;
236
237        dev = s->s_dev;
238        set_blocksize(dev, UX_BSIZE);
239        s->s_blocksize = UX_BSIZE;
240        s->s_blocksize_bits = UX_BSIZE_BITS;
241
(gdb) bt
#0  ux_read_super (s=0xcf15a400, data=0x0, silent=0) at ux_inode.c:237
#1  0xc0143868 in get_sb_bdev (fs_type=0xd0856a44, 
    dev_name=0xccfe8000 "/dev/fd0", flags=0, data=0x0) at super.c:697
#2  0xc0143d2d in do_kern_mount (type=0xccfe9000 "uxfs", flags=0, 
    name=0xccfe8000 "/dev/fd0", data=0x0) at super.c:879
#3  0xc0156ff1 in do_add_mount (nd=0xcd011f5c, type=0xccfe9000 "uxfs", 
    flags=0, mnt_flags=0, name=0xccfe8000 "/dev/fd0", data=0x0)
    at namespace.c:630
#4  0xc01572b7 in do_mount (dev_name=0xccfe8000 "/dev/fd0", 
    dir_name=0xcf80f000 "/mnt", type_page=0xccfe9000 "uxfs",
flags=3236757504, data_page=0x0) at namespace.c:746
#5  0xc015737f in sys_mount (dev_name=0x805b418 "/dev/fd0", 
    dir_name=0x805b428 "/mnt", type=0x805b438 "uxfs", flags=3236757504, 
    data=0x0) at namespace.c:779
#6  0xc010730b in system_call ()
(gdb) print *(struct super_block *)0xcf15a400
$1 = {s_list = {next = 0xc0293840, prev = 0xcf6df400}, s_dev = 512, 
  s_blocksize = 0, s_blocksize_bits = 0 ’\0’, s_dirt = 0 ’\0’, 
  s_maxbytes = 2147483647, s_type = 0xd0856a44, s_op = 0x0, dq_op = 0x0, 
  s_flags = 0, s_magic = 0, s_root = 0x0, s_umount = {count = -65535, 
    wait_lock = {lock = 1}, wait_list = {next = 0xcf15a43c, 
prev = 0xcf15a43c}}, s_lock = {count = {counter = 0}, sleepers = 0, 
    wait = {lock = {lock = 1}, task_list = {next = 0xcf15a450, 
prev = 0xcf15a450}}}, s_count = 1073741824, s_active = {counter = 1}, 
  s_dirty = 0,
  ...

6.2 扫描 uxfs 文件系统

挂载文件系统时的首要任务是从磁盘读取超级块。这需要调用 sb_bread() 来读取超级块所在设备的第 0 块。 sb_read() 函数只是 bread() 的包装器,它从 super_block 结构的 s_dev 字段中提取设备信息。因此,以下调用是等效的:

bh = sb_bread(sb, block);
bh = bread(sb->s_dev, block, sb->s_blocksize);

sb_bread() 返回时, buffer_head 结构将引用从设备读取的数据。请注意,每次调用 sb_read() 后,必须在某个阶段调用 brelse() 来释放缓冲区。在调用 brelse() 之前尝试重新读取磁盘上的同一块会导致文件系统阻塞。可以通过访问 b_data 字段来引用从磁盘读取的数据。

由于超级块位于第 0 块的偏移量 0 处,可以按第 1253 行所示引用 ux_superblock 结构:

usb = (struct ux_superblock *)bh->b_data;

首先要进行的检查是验证这是否是一个 uxfs 文件系统,通过检查 uxfs 魔数的存在来实现验证。假设检测到魔数并且超级块未标记为 UX_FSDIRTY ,则可以挂载文件系统。由于所有索引节点和数据块信息都存储在 uxfs 超级块中,因此必须始终将超级块保留在内存中。会分配一个 ux_fs 结构来保存用于读取超级块的 buffer_head ,这使得可以从 Linux super_block 结构或 Linux 索引节点轻松访问 ux_superblock 结构。

访问 ux_fs 结构可以通过 Linux super_block 结构或间接从 Linux 索引节点结构实现,如下所示:

struct super_block      *sb = inode->i_sb;
struct ux_fs            *fs = (struct ux_fs *)sb->s_private;
struct ux_superblock    *usb = fs->u_sb;

由于所有导出的 uxfs 函数都通过 super_block 或索引节点结构作为参数传递,因此始终可以访问 uxfs 超级块。

6.3 读取根索引节点

挂载文件系统的最后一步是读取根索引节点并将其实例化到目录项缓存(dcache)中。这通过调用 iget() 然后调用 d_alloc_root() 来实现。

调用 iget() 会回调到文件系统中,实际从磁盘读取索引节点。后续对同一索引节点的 iget() 调用将在缓存中找到该条目,从而避免进一步的文件系统访问。关于 uxfs 如何读取索引节点的详细信息,可参考后续相关内容。Linux 内核调用 find_inode() fs/inode.c )来扫描索引节点缓存以查找索引节点。如果未找到,则调用 get_new_inode()

调用 d_alloc_root() d_instantiate() 的包装器,它将目录项结构的 d_sb 字段初始化为引用新的 super_block 结构。请注意,访问任何其他索引节点将涉及访问已经存在且已由内核初始化的目录项。

此时,挂载完成。 super_block 结构已初始化,根目录可以通过 Linux 索引节点缓存/目录项缓存访问,并且内核可以访问根索引节点导出的函数数组,通过这些函数可以执行后续操作。

以下是在 gdb 中对 ux_read_inode() 函数设置断点的示例:

(gdb) b ux_read_inode
Breakpoint 2 at 0xd0855312: file ux_inode.c, line 54.
(gdb) c
Continuing.
Breakpoint 2, ux_read_inode (inode=0xcd235460) at ux_inode.c:54
54        unsigned long             ino = inode->i_ino;
(gdb) list
49
void
50
ux_read_inode(struct inode *inode)
51
{
52        struct buffer_head        *bh;
53        struct ux_inode           *di;
54        unsigned long             ino = inode->i_ino;
55        int                       block;
56
57        if (ino < UX_ROOT_INO || ino > UX_MAXFILES) {
58                printk("uxfs: Bad inode number %lu\n", ino);
(gdb) bt
#0  ux_read_inode (inode=0xcd235460) at ux_inode.c:54
#1  0xc015411a in get_new_inode (sb=0xcf15a400, ino=2, head=0xcfda3820, 
    find_actor=0, opaque=0x0) at inode.c:871
#2  0xc015439a in iget4 (sb=0xcf15a400, ino=2, find_actor=0, opaque=0x0)
    at inode.c:984
#3  0xd0855bfb in iget (sb=0xcf15a400, ino=2)
    at /usr/src/linux/include/linux/fs.h:1328
#4  0xd08558c3 in ux_read_super (s=0xcf15a400, data=0x0, silent=0)
    at ux_inode.c:272
#5  0xc0143868 in get_sb_bdev (fs_type=0xd0856a44, 
    dev_name=0xccf35000 "/dev/fd0", flags=0, data=0x0) at super.c:697
#6  0xc0143d2d in do_kern_mount (type=0xccf36000 "uxfs", flags=0, 
...
(gdb) print *(struct inode *)0xcd235460
$2 = {i_hash = {next = 0xce2c7400, prev = 0xcfda3820}, i_list = {
    next = 0xcf7aeba8, prev = 0xc0293d84}, i_dentry = {next = 0xcd235470, 
    prev = 0xcd235470}, i_dirty_buffers = {next = 0xcd235478, 
    prev = 0xcd235478}, i_dirty_data_buffers = {next = 0xcd235480, 
    prev = 0xcd235480}, i_ino = 2, i_count = {counter = 1}, i_dev = 512, 

i_mode = 49663, i_nlink = 1, i_uid = 0, i_gid = 0, 
i_rdev = 512, i_size = 0, 

由于索引节点结构的地址已知,因此可以随时显示它。只需进入 gdb 并再次运行上述命令即可。

6.4 将超级块写入磁盘

uxfs 超级块包含有关哪些索引节点和数据块已分配的信息以及这两部分信息的摘要。超级块驻留在单个 UX_MAXBSIZE 缓冲区中,在挂载期间一直保留。确保将脏缓冲区刷新到磁盘的常用方法是将缓冲区标记为脏,如下所示:

mark_buffer_dirty(bh);

然而,uxfs 超级块在文件系统卸载之前不会被释放。每次修改超级块时,将超级块的 s_dirt 字段设置为 1。这会通知内核, kupdate 守护进程应定期通知文件系统,该守护进程会定期调用以将脏缓冲区刷新到磁盘。 kupdate() 例程可以在 Linux 内核源码的 fs/buffer.c 中找到。

为了跟踪从 kupdate() 到文件系统的流程,执行以下任务:

# ./mkfs /dev/fd0
# mount -t uxfs /dev/fd0 /mnt
# touch /mnt/file

由于创建了一个新文件,会分配一个新的索引节点,这需要更新超级块中的信息。作为此处理的一部分,将内核中超级块的 s_dirt 字段设置为 1,以指示超级块已被修改。

ux_write_super() 函数(第 1218 行到第 1229 行)用于将超级块写入磁盘。使用 kdb 在 ux_write_super() 中设置断点,如下所示:

Entering kdb (current=0xcbe20000, pid 1320) on processor 0 due to
Keyboard Entry[0]kdb> bp ux_write_super
Instruction(i) BP #1 at 0xd08ab788 ([uxfs]ux_write_super)
    is enabled globally adjust 1

创建新文件后,最终会触发断点,如下所示:

Entering kdb (current=0xc1464000, pid 7) on processor 0 due to Breakpoint
@ 0xd08ab788
[0]kdb> bt
    EBP       EIP         Function(args)
0xc1465fc4 0xd08ab788 [uxfs]ux_write_super (0xcc53b400, 0xc1464000)
                               uxfs .text 0xd08aa060 0xd08ab788 0xd08ab7c4
           0xc014b242 sync_supers+0x142 (0x0, 0xc1464000)
                               kernel .text 0xc0100000 0xc014b100 0xc014b2c0
0xc1465fd4 0xc0149bd6 sync_old_buffers+0x66 (0xc1464000, 0x10f00, 
0xcffe5f9c, 0xc0105000)
                               kernel .text 0xc0100000 0xc0149b70 0xc0149cf0
0xc1465fec 0xc014a223 kupdate+0x273
                               kernel .text 0xc0100000 0xc0149fb0 0xc014a230
           0xc01057c6 kernel_thread+0x26
kernel .text 
0xc0100000 0xc01057a0 0xc01057e0

注意从 kupdate() sync_old_buffers() 的调用。进一步查看内核代码,会发现一个内联函数 write_super() ,它实际上调用了文件系统:

if (sb->s_root && sb->s_dirt)
    if (sb->s_op && sb->s_op->write_super)
        sb->s_op->write_super(sb);

因此,会调用超级块操作向量的 write_super 条目。对于 uxfs,只需将保存超级块的缓冲区标记为脏。虽然这不会立即将超级块刷新到磁盘,但它会在稍后的 kupdate() 处理中被写入(通常很快)。

ux_write_super() 函数的另一个任务是将内核中超级块的 s_dirt 字段设置回 0。如果将其保留为 1,每次 kupdate() 运行时都会调用 ux_writer_super() ,这实际上会导致系统锁定。

6.5 卸载文件系统

脏缓冲区和索引节点会分别刷新到磁盘,因此它们实际上并不是卸载文件系统的一部分。如果在发出卸载命令时文件系统正忙,内核会在返回 EBUSY 给用户之前不与文件系统进行通信。

如果系统上没有打开的文件,脏缓冲区和索引节点会被刷新到磁盘,并且内核会调用通过超级块操作向量导出的 put_super 函数。对于 uxfs,该函数是 ux_put_super() (第 1176 行到第 1188 行)。

进入 ux_put_super() 的调用路径如下:

Breakpoint 4, ux_put_super (s=0xcede4c00) at ux_inode.c:167
167        struct ux_fs        *fs = (struct ux_fs *)s->s_private;
(gdb) bt
#0  ux_put_super (s=0xcede4c00) at ux_inode.c:167
#1  0xc0143b32 in kill_super (sb=0xcede4c00) at super.c:800
#2  0xc01481db in path_release (nd=0xc9da1f80)
    at /usr/src/linux-2.4.18/include/linux/mount.h:50
#3  0xc0156931 in sys_umount (name=0x8053d28 "/mnt", flags=0)
    at namespace.c:395
#4  0xc015694e in sys_oldumount (name=0x8053d28 "/mnt") 
at namespace.c:406
#5  0xc010730b in system_call ()

ux_put_super() 只需执行以下两个任务:
- 将保存超级块的缓冲区标记为脏并释放它。
- 释放 ux_read_super() 期间分配的用于保存 ux_fs 结构的内存。

综上所述,本文详细介绍了 uxfs 文件系统的开发过程,包括构建文件系统、创建文件系统布局、初始化和反初始化模块、测试文件系统、挂载和卸载文件系统以及处理超级块的读写等操作。通过这些步骤和相关的代码实现,可以在 Linux 内核中成功开发和使用 uxfs 文件系统。

7. 关键操作的总结与对比

为了更清晰地理解 uxfs 文件系统开发过程中的关键操作,下面将对一些重要操作进行总结和对比,以表格的形式呈现:
| 操作 | 功能 | 关键步骤 | 涉及文件与代码位置 |
| — | — | — | — |
| 构建 uxfs 文件系统 | 生成可加载到内核的 uxfs 模块 | 1. 下载并解压源码
2. 构建命令
3. 修改 Makefile 并构建内核源码 | 源码下载自 www.wiley.com/compbooks/spate
命令构建在 cmds 目录, uxfs.h 需在 "../kern" 目录
内核 Makefile 及构建代码 |
| 创建 uxfs 文件系统 | 将初始文件系统布局放置到磁盘 | 1. 编写 mkfs 命令完成超级块、索引节点等初始化和分配记录
2. 编写 fsdb 命令验证 mkfs 写入信息 | mkfs 代码在第 104 - 262 行
fsdb 代码在第 264 - 393 行 |
| 模块初始化与反初始化 | 实现模块的加载与卸载功能 | 1. 定义模块类型声明
2. 定义模块加载和卸载函数 | ux_inode.c 第 1304 - 1317 行 |
| 挂载文件系统 | 将 uxfs 文件系统挂载到指定目录 | 1. 调用 ux_read_super() 初始化 super_block 结构
2. 扫描磁盘读取超级块
3. 读取根索引节点并实例化到 dcache | ux_read_super() 代码在 ux_inode.c 第 1240 - 1302 行 |
| 卸载文件系统 | 卸载已挂载的 uxfs 文件系统 | 1. 刷新脏缓冲区和索引节点到磁盘
2. 调用 ux_put_super() 释放相关资源 | ux_put_super() 代码在第 1176 - 1188 行 |

8. 开发中的注意事项与常见问题

8.1 调试方面
  • 断点设置 :在使用 gdb 进行调试时,要确保在合适的函数和位置设置断点。例如在 ux_read_super() ux_read_inode() 等关键函数处设置断点,可以帮助我们深入了解文件系统的挂载和索引节点读取过程。但要注意,断点设置过多可能会影响程序的执行效率,导致调试过程变慢。
  • 堆栈回溯 :使用 bt 命令查看堆栈回溯时,要理解函数调用的层次关系,这有助于定位问题所在。但有时候堆栈回溯信息可能会很长很复杂,需要耐心分析。
8.2 代码构建方面
  • Makefile 配置 :修改 KERNELDIR 变量时,要确保其正确指向内核源码目录,否则会导致内核构建失败。同时,要注意 CFLAGS 等编译选项的设置,避免因选项错误导致编译不通过。
  • 头文件位置 :构建命令时, uxfs.h 头文件必须位于 "../kern" 目录中,否则会出现找不到头文件的错误。
8.3 文件系统操作方面
  • 超级块处理 :超级块在文件系统中起着关键作用,要确保其信息的正确性和一致性。在修改超级块信息后,要及时将 s_dirt 字段设置为 1 以通知内核刷新,处理完成后再将其设置回 0,避免系统锁定。
  • 缓冲区管理 :在读取磁盘数据时,使用 sb_bread() 读取后要及时调用 brelse() 释放缓冲区,否则会导致文件系统阻塞。

9. 未来扩展与优化方向

9.1 功能扩展
  • 支持更多文件系统特性 :目前 uxfs 文件系统功能相对基础,可以考虑添加对文件权限管理、文件压缩、加密等特性的支持,以提高文件系统的安全性和性能。
  • 增加命令功能 fsdb 命令目前功能较为简单,可添加更多命令来显示更多文件系统信息,如块分配情况、索引节点详细信息等,方便开发者进行调试和监控。
9.2 性能优化
  • 缓存优化 :可以优化索引节点和数据块的缓存机制,减少磁盘 I/O 操作,提高文件系统的读写性能。例如,采用更高效的缓存淘汰算法,确保常用数据始终在缓存中。
  • 并发处理 :支持多线程并发操作,提高文件系统在多用户、高并发场景下的性能。可以通过对关键数据结构进行锁优化,减少锁竞争,提高并发处理能力。

10. 总结与展望

通过本文的详细介绍,我们全面了解了 uxfs 文件系统的开发过程,从内核级调试到文件系统的构建、创建、挂载、卸载等各个环节,都有了深入的认识。在开发过程中,我们使用了 gdb 进行调试,掌握了 Makefile 的配置和代码构建方法,同时也了解了文件系统的核心概念和操作原理。

未来,随着计算机技术的不断发展,文件系统也将面临更多的挑战和机遇。我们可以在 uxfs 文件系统的基础上进行扩展和优化,使其具备更多强大的功能和更高的性能。同时,我们也可以借鉴其他优秀文件系统的设计理念和技术,不断完善 uxfs 文件系统,为用户提供更加稳定、高效、安全的文件存储和管理解决方案。

希望本文能够为对文件系统开发感兴趣的开发者提供有价值的参考和指导,激发大家在这个领域进行更多的探索和创新。

graph LR
    A[开始开发 uxfs 文件系统] --> B[构建文件系统]
    B --> C[创建文件系统布局]
    C --> D[模块初始化与反初始化]
    D --> E[测试文件系统]
    E --> F[挂载文件系统]
    F --> G[文件系统操作(读写等)]
    G --> H[卸载文件系统]
    H --> I[未来扩展与优化]

以上流程图展示了 uxfs 文件系统开发的整体流程,从开始开发到最终的扩展优化,各个环节紧密相连。开发者可以根据这个流程逐步实现文件系统的开发和完善。

基于TROPOMI高光谱遥感仪器获取的大气成分观测资料,本研究聚焦于大气污染物一氧化氮(NO₂)的空间分布与浓度定量反演问题。NO₂作为影响空气质量的关键指标,其精确监测对环境保护与大气科学研究具有显著价值。当前,利用卫星遥感数据结合先进算法实现NO₂浓度的高精度反演已成为该领域的重要研究方向。 本研究构建了一套以深度学习为核心的技术框架,整合了来自TROPOMI仪器的光谱辐射信息、观测几何参数以及辅助气象数据,形成多维度特征数据集。该数据集充分融合了不同来源的观测信息,为深入解析大气中NO₂的时空变化规律提供了数据基础,有助于提升反演模型的准确性与环境预测的可靠性。 在模型架构方面,项目设计了一种多分支神经网络,用于分别处理光谱特征与气象特征等多模态数据。各分支通过独立学习提取代表性特征,并在深层网络中进行特征融合,从而综合利用不同数据的互补信息,显著提高了NO₂浓度反演的整体精度。这种多源信息融合策略有效增强了模型对复杂大气环境的表征能力。 研究过程涵盖了系统的数据处理流程。前期预处理包括辐射定标、噪声抑制及数据标准化等步骤,以保障输入特征的质量与一致性;后期处理则涉及模型输出的物理量转换与结果验证,确保反演结果符合实际大气浓度范围,提升数据的实用价值。 此外,本研究进一步对不同功能区域(如城市建成区、工业带、郊区及自然背景区)的NO₂浓度分布进行了对比分析,揭示了人类活动与污染物空间格局的关联性。相关结论可为区域环境规划、污染管控政策的制定提供科学依据,助力大气环境治理与公共健康保护。 综上所述,本研究通过融合TROPOMI高光谱数据与多模态特征深度学习技术,发展了一套高效、准确的大气NO₂浓度遥感反演方法,不仅提升了卫星大气监测的技术水平,也为环境管理与决策支持提供了重要的技术工具。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值