实验4补充材料:辅助函数与宏定义速查¶
定位
本文是 实验4:语义文件系统 的补充材料,列出 kernel/fs/fat16fs.c 和 kernel/fs/bptree.c 中你需要实现的部分代码中可能会用到的宏定义、结构体字段和辅助函数。
建议同学们先阅读实验 4 的实验要求与 FAT16 文件系统简介,了解本操作系统实现的文件系统与文件属性系统后,在实现时对照此文档使用。如果你在阅读时发现有名词看不懂或者有歧义,可以在实验要求与 FAT16 文件系统简介中先行查询,如果仍有困惑,请直接反馈给助教。
标题中标有 需要实现 的函数是同学们需要先自行实现然后再在其他函数中调用的;标题中标有 Bonus 需要实现 的函数属于 B+ 树 Bonus。
1. 基础结构¶
1.1 struct dirent¶
即文件目录项,位置:kernel/include/file.h,定义如下:
struct dirent {
uchar name[11];
uchar attr;
uchar ntres;
uchar crt_time_tenth;
ushort crt_time;
ushort crt_date;
ushort last_acc_date;
ushort first_cluster_hi;
ushort wrt_time;
ushort wrt_date;
ushort first_cluster_lo;
uint file_size;
} __attribute__((packed));
重要字段:
name[11]:FAT16 8.3 短文件名,前 8 字节为主文件名,后 3 字节为扩展名。attr:属性位,标志该文件是只读的、隐藏的、普通文件或目录等信息。first_cluster_hi、first_cluster_lo:首簇号的高 16 位和低 16 位。file_size:普通文件的有效字节长度;目录固定为 0。
1.2 struct inode¶
即文件在内存中的索引结点,位置:kernel/include/file.h
重要字段:
ip->dev:设备号,读写文件数据扇区时传给bread()。ip->type:上层 inode 类型,常见值为T_FILE和T_DIR。ip->size:普通文件为有效字节数;目录为该目录可以使用的字节长度。ip->addrs[0]:FAT16 文件或子目录的首簇号。ip->addrs[1]:该 inode 对应标准目录项所在扇区号。ip->addrs[2]:该 inode 对应标准目录项在扇区内的字节偏移。
1.3 struct fat16_slot¶
即 FAT16 文件槽,也是在内存中的,磁盘上没有这个结构,位置:kernel/fs/fat16fs.c
字段:
entry:该槽位中的 FAT16 32B 目录项内容。sector:该目录项所在扇区号。offset:该目录项在扇区内的字节偏移。index:该目录项在所在目录中的按目录项索引,即如果它是第 0 个目录项则该字段为 0。
2. 重要宏定义¶
2.1 磁盘块与 FAT16 区域¶
| 名称 | 位置 | 含义 |
|---|---|---|
BSIZE |
kernel/include/buf.h |
磁盘块大小,当前为 512 字节 |
FAT16_DIR_ENTRY_SIZE |
kernel/fs/fat16fs.c |
FAT16 目录项大小,固定为 32 字节 |
FAT16_ROOT_INUM |
kernel/fs/fat16fs.c |
FAT16 根目录的内存 inode 编号 |
FAT16_ENTRIES_PER_SECTOR |
kernel/fs/fat16fs.c |
每个扇区包含的目录项数量 |
2.2 FAT 表与簇链¶
| 名称 | 位置 | 含义 |
|---|---|---|
FAT16_CLUSTER_FREE |
kernel/fs/fat16fs.c |
FAT 表项值 0x0000,表示空闲簇 |
FAT16_CLUSTER_MIN |
kernel/fs/fat16fs.c |
第一个有效数据簇号,固定为 2 |
FAT16_CLUSTER_EOC |
kernel/fs/fat16fs.c |
EOC 范围起点,大于等于该值表示簇链结束 |
FAT16_CLUSTER_END |
kernel/fs/fat16fs.c |
本实验写入 FAT 表时使用的链尾值 |
2.3 目录项属性与空槽标记¶
| 名称 | 位置 | 含义 |
|---|---|---|
FAT16_ATTR_DIRECTORY |
kernel/fs/fat16fs.c |
attr 中的目录标记 |
FAT16_ATTR_KW |
kernel/fs/fat16fs.c |
本实验关键词伪目录项使用的属性值 |
FAT16_NAME_FREE |
kernel/fs/fat16fs.c |
name[0] == 0x00,表示该槽位未使用,并且按 FAT 约定后续槽位也未使用 |
FAT16_NAME_DELETED |
kernel/fs/fat16fs.c |
name[0] == 0xe5,表示该槽位曾经使用过、现在已删除,可复用 |
注意事项:
- 删除文件时应标记为
FAT16_NAME_DELETED,不能把中间项改成FAT16_NAME_FREE后又在它后面保留有效项,因此在扫描时遇到FAT16_NAME_FREE即可停止。
2.4 关键词伪目录项¶
| 名称 | 位置 | 含义 |
|---|---|---|
FAT16_KW_MAX_ENTRIES |
kernel/fs/fat16fs.c |
单个文件最多允许使用的关键词伪目录项数量 |
FAT16_KW_BYTES_PER_ENTRY |
kernel/fs/fat16fs.c |
每个关键词伪目录项可存放的关键词数据字节数 |
FAT16_KW_MAX_BYTES |
kernel/fs/fat16fs.c |
单个文件关键词字符串最大字节数 |
3. 磁盘 I/O 与内存拷贝¶
3.1 struct buf¶
即用来保存磁盘块上数据的结构,位置:kernel/include/buf.h
重要字段:
bp->data:长度为BSIZE的块数据。
注意事项:
- 使用完必须调用
brelse()。
3.2 bread()¶
函数原型:
参数:
dev:设备号。在当前实现下meta.dev和ip->dev总是一样的。blockno:磁盘块号,也就是扇区号。
返回值:
- 返回已锁定的
struct buf *。
3.3 bwrite()¶
函数原型:
参数:
b:由bread()返回且仍然持锁的 buffer。
注意事项:
- 修改 FAT 表、目录项、文件数据后,都要调用
bwrite()才会持久化到磁盘。
3.4 brelse()¶
函数原型:
参数:
b:由bread()返回且仍然持锁的 buffer。
3.5 memmove() 与 memset()¶
函数原型:
参数:
dst:目标地址。src:源地址。c:填充值。n:字节数。
返回值:
- 返回
dst。
4. FAT16 辅助函数¶
4.1 get16() 与 get32()¶
用于获取内存数据的十进制值,位置:kernel/fs/fat16fs.c
函数原型:
参数:
p:指向小端序字节序列的内存地址。
返回值:
get16()返回 2 字节整数。get32()返回 4 字节整数。
4.2 fat16_cluster_first_sector()(需要实现)¶
函数原型:
参数:
cluster:数据簇号。
返回值:
- 返回该簇在磁盘上的第一个扇区号。
4.3 fat16_read_fat()(需要实现)¶
函数原型:
参数:
cluster:FAT 表项索引。
返回值:
- 返回
FAT[cluster]的 16 位值。
4.4 fat16_read_entry()¶
用于读某位置的目录项,函数原型:
参数:
sector:目录项所在扇区。offset:目录项在扇区内的字节偏移。entry:输出参数,写入读出的 32B 目录项。
4.5 fat16_slot_by_index()(需要实现根目录分支)¶
根据目录项在 dp(即 directory pointer,标明这个inode是目录)目录下的索引,设置内存中的 FAT16 slot,包括扇区号、扇区内偏移与目录项结构体,函数原型:
参数:
dp:目录 inode。index:目录项索引,从 0 开始。slot:FAT16 目录项在内存中的槽位。
返回值:
- 成功返回
0。 - 越界或定位失败返回
-1。
4.6 fat16_fat_value_is_eoc()、fat16_cluster_inuse()¶
函数原型:
参数:
fat_value:FAT 表项值。cluster:簇号。
返回值:
fat16_fat_value_is_eoc()在表项表示链尾时返回非 0。fat16_cluster_inuse()在簇号是数据簇时返回非 0。
4.7 fat16_alloc_cluster() 与 fat16_write_fat()¶
函数原型:
参数:
out:成功分配新簇时写入新簇号。cluster:簇号,即要写入的 FAT 表项索引。value:要写入的 FAT 表项值。
返回值:
fat16_alloc_cluster()成功返回0,无空闲簇时返回-1。
注意事项:
- 写路径扩展簇链时,通常先
fat16_alloc_cluster(&new_cluster),再fat16_write_fat(current_cluster, new_cluster)。
4.8 fat16_cluster_for_offset()(需要实现)¶
根据文件内偏移 off,读取该 off 对应的簇或者将文件的簇扩展到能容纳该 off 的大小,函数原型:
参数:
ip:普通文件 inode。off:文件内字节偏移。alloc:是否允许分配新簇。cluster_out:输出参数,写入off所在簇号。
返回值:
- 成功返回
0。 - 读到不存在的簇、分配失败或簇链非法时返回
-1。
注意事项:
- 读路径调用时
alloc == 0,不能扩展簇链。 - 写路径调用时
alloc != 0,可以在遇到链尾时追加新簇。
5. 文件属性系统辅助函数¶
5.1 fat16_dirent_is_keyword()¶
函数原型:
参数:
entry:要检查的 FAT16 目录项。
返回值:
- 是关键词伪目录项时返回非 0,否则返回 0.
5.2 fat16_keyword_count_before()(需要实现)¶
函数原型:
参数:
dp:文件所在目录 inode。std_index:标准文件目录项索引。
返回值:
- 返回标准文件目录项前面连续的关键词伪目录项数量。
5.3 fat16_kw_get_data_byte()¶
函数原型:
参数:
entry:关键词伪目录项。pos:关键词数据区内偏移,范围为[0, FAT16_KW_BYTES_PER_ENTRY)。
返回值:
- 返回该位置保存的关键词数据字节。
pos非法时返回 0。
5.4 fat16_read_keywords_before()(需要实现)¶
函数原型:
参数:
dp:文件所在目录 inode。std_index:标准文件目录项索引。buf:输出缓冲区。max:输出缓冲区容量。
返回值:
- 成功返回写入
buf的关键词字节数,不包括结尾\0。 - 无关键词时返回
0。 - 伪目录项格式错误或缓冲区不足时返回
-1。
注意事项:
- 输出 buf 以
\0结尾。
5.5 fat16_slot_available_for_run()¶
用于检查目录下某处 32B 的连续空间是否能用来组成新的关键词伪目录项序列,函数原型:
static int fat16_slot_available_for_run(struct inode *dp, uint index,
uint old_start, uint old_count);
参数:
dp:目录 inode。index:待检查的目录项索引。old_start:当前文件目录项旧连续范围起点。old_count:当前文件目录项旧连续范围长度。
返回值:
- 槽位可用于新的连续范围时返回非 0,否则返回 0.
注意事项:
- 更新关键词时,旧范围内的关键词目录项将标记为删除,因此旧范围内的关键词目录项可以视为可复用。
5.6 fat16_dir_slot_count() 与 fat16_grow_dir()¶
函数原型:
参数:
dp:目录 inode。
返回值:
fat16_dir_slot_count()返回该目录当前可容纳的 32B 目录项数量。fat16_grow_dir()成功扩展子目录时返回0,失败返回-1。
注意事项:
- 根目录大小固定,不能通过
fat16_grow_dir()增长。
5.7 fat16_find_free_run()(需要实现)¶
用于为设置关键词时找到一个连续的目录项序列,函数原型:
static int fat16_find_free_run(struct inode *dp, uint need, uint old_start, uint old_count,
struct fat16_slot *first_slot, uint *first_index);
参数:
dp:目标目录 inode。need:需要的连续槽位数量。old_start:旧连续范围起点。old_count:旧连续范围长度。first_slot:输出参数,成功时写入连续范围第一个槽位。first_index:输出参数,成功时写入连续范围起始索引。
返回值:
- 找到连续范围时返回
0。 - 找不到或目录不能增长时返回
-1。
注意事项:
- 关键词伪目录项和标准文件目录项必须连续,不能用零散空槽拼接。
- 如果普通子目录空间不足,可以增长目录后重新扫描;根目录不能增长。
5.8 fat16_mark_range_deleted_except()¶
用于删除除保留范围外的旧范围内关键词伪目录项,函数原型:
static void fat16_mark_range_deleted_except(struct inode *dp, uint start, uint count,
uint keep_start, uint keep_count);
参数:
dp:目录 inode。start:要删除的旧范围起点。count:旧范围长度。keep_start:保留范围起点。keep_count:保留范围长度。
5.9 fat16_write_keywords_at()¶
函数原型:
参数:
dp:目录 inode。start:第一条关键词伪目录项索引。keywords:关键词字符串。nentry:要写入的关键词伪目录项数量。
返回值:
- 成功返回
0。 - 参数非法或写入失败返回
-1。
5.10 fat16_slot_inum() 与 fat16_find_loaded()¶
函数原型:
static uint fat16_slot_inum(uint sector, uint offset);
static struct inode *fat16_find_loaded(uint inum);
参数:
sector、offset:标准目录项的新旧磁盘位置。inum:由fat16_slot_inum()计算出的 inode 编号。
返回值:
fat16_slot_inum()返回与目录项位置对应的 inode 编号。fat16_find_loaded()找到已加载 inode 时返回其指针,否则返回 0。
注意事项:
- 移动标准文件目录项后,已加载 inode 的
inum、addrs[1]、addrs[2]也要同步修改。
5.11 fat16_kw_index_update_file()¶
用于在修改目录项布局后同步更新驻于内存的 B+ 树索引,函数原型:
static void fat16_kw_index_update_file(uint old_sector, uint old_offset,
uint new_sector, uint new_offset,
const char *path,
const char *old_keywords,
const char *new_keywords);
参数:
old_sector、old_offset:旧标准目录项位置。new_sector、new_offset:新标准目录项位置。path:文件路径。old_keywords:旧关键词字符串。new_keywords:新关键词字符串。
6. B+ 树¶
6.1 B+ 树宏和结构¶
| 名称 | 位置 | 含义 |
|---|---|---|
BPTREE_MAX_KEYS |
kernel/include/bptree.h |
单个 B+ 树节点最多容纳的键数 |
BPTREE_VALUES_PER_CHUNK |
kernel/include/bptree.h |
B+ 树存储的值块链表中每个值块最多容纳的值数量 |
struct bptree_values |
kernel/include/bptree.h |
一个关键词对应的值块链表 |
struct bptree_value_chunk |
kernel/include/bptree.h |
值链表的节点,称为值块,每个块中有多个值 |
注意事项:
- 本实验中 B+ 树的值是
struct fat16_kw_index_file *。 key参数是长度为len的 token(注意这里的 token 可以理解为一个词)片段。
6.2 bptree_keycmp_token()¶
函数原型:
参数:
key:B+ 树中保存的字符串。token:待比较关键词片段。len:token的长度。
返回值:
- 小于 0:
key < token。 - 等于 0:二者相等。
- 大于 0:
key > token。
6.3 bptree_strdup_key()¶
用于在 B+ 树管理的内存中分配一块地址给新的键并将键值设为 key,函数原型:
参数:
tree:目标 B+ 树。key:关键词片段起始地址。len:关键词长度。
返回值:
- 成功返回新复制的 key。
- 分配失败返回 0。
6.4 bptree_values_add()¶
为目标值块链表加入一条新记录 value。
函数原型:
参数:
tree:目标 B+ 树。values:目标值块链表。value:要加入的文件记录指针。
返回值:
- 成功返回
0。 - 参数非法或分配失败返回
-1。
6.5 bptree_split_leaf()(Bonus 需要实现)¶
用于分裂一个叶子结点,函数原型:
static int bptree_split_leaf(struct bptree *tree, struct bptree_node *node,
char **promoted, struct bptree_node **right);
参数:
tree:目标 B+ 树。node:溢出的叶子节点。promoted:输出参数,写入复制到父节点的 key。right:输出参数,写入新右叶子节点。
返回值:
- 成功返回
0。 - 分配失败返回
-1。
6.6 bptree_split_internal()(Bonus 需要实现)¶
用于分裂一个内部结点,函数原型:
static int bptree_split_internal(struct bptree *tree, struct bptree_node *node,
char **promoted, struct bptree_node **right);
参数:
tree:目标 B+ 树。node:溢出的内部节点。promoted:输出参数,写入提升到父节点的分隔 key。right:输出参数,写入新右内部节点。
返回值:
- 成功返回
0。 - 分配失败返回
-1。
6.7 bptree_insert_rec()(Bonus 需要实现)¶
用于为键 key 插入值 value,函数原型:
static int bptree_insert_rec(struct bptree *tree, struct bptree_node *node,
const char *key, int len, void *value,
char **promoted, struct bptree_node **right);
参数:
tree:目标 B+ 树。node:当前递归节点。key:关键词片段。len:关键词长度。value:文件记录指针。promoted:输出参数,当前节点分裂时写入提升 key,否则写入 0。right:输出参数,当前节点分裂时写入右节点,否则写入 0。
返回值:
- 成功返回
0。 - 插入失败返回
-1。