开发 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 文件系统开发的整体流程,从开始开发到最终的扩展优化,各个环节紧密相连。开发者可以根据这个流程逐步实现文件系统的开发和完善。
超级会员免费看
1

被折叠的 条评论
为什么被折叠?



