考试内容(?)
解析题
fs.c
entry.S xv6初始化代码
编程题
fs.c 文件逻辑地址向物理地址转换 bmap
fs.c 查找磁盘块的位图的算法 balloc
代码分析
磁盘块分配 balloc
c
// 分配一个新的磁盘块,并将其清零。该函数会从设备dev上寻找一个未被使用的磁盘块,并返回其块号。
static uint
balloc(uint dev)//dev:设备号
{
int m;//m:一个二进制数,只有一位是1,其余位都是0。用于检查和设置位图中的特定位。
struct buf *bp; //缓冲区指针
bp = 0;
// 遍历所有的磁盘块,每次步进BPB(一个块中的位数)
for(int b = 0; b < sb.size; b += BPB){//sb是全局变量superblock超级块
// 读取当前块的位图块到缓冲区
bp = bread(dev, BBLOCK(b, sb));//BBLOCK:给定一个块号b和超级块信息sb,计算该块在位图中对应的块位置。
// 遍历当前位图块中的所有位,查找未使用的块。b+bi是当前正在检查的磁盘块的块号,确保在分配磁盘块时不会超出文件系统的总大小。
for(int bi = 0; bi < BPB && b + bi < sb.size; bi++){
// 计算当前位的掩码,把1挪到位图块的特定位。
//bi%8:bi在它所在的字节中的具体位置
m = 1 << (bi % 8);
// 如果当前块未被使用(位图中对应的位为0)
//bi/8:此处拿出所在的那个字节来运算
if((bp->data[bi/8] & m) == 0){
// 标记当前块为已使用(在位图中将对应的位设置为1)
bp->data[bi/8] |= m;
// 将改动写入日志
log_write(bp);
// 释放缓冲区
brelse(bp);
// 将新分配的块清零
bzero(dev, b + bi);
// 返回新分配的块号
return b + bi;
}
}
// 释放缓冲区
brelse(bp);
}
}
块号映射 bmap
映射一个inode中的逻辑块号到对应的物理块号
c
// 与每个inode关联的内容(数据)存储在磁盘的块中。
// 前NDIRECT个块号列在ip->addrs[]中。
// 接下来的NINDIRECT块列在块ip->addrs[NDIRECT]中。
// 返回inode ip中第bn个块的磁盘块地址。
// 如果没有这样的块,bmap会分配一个。
static uint bmap(struct inode *ip, uint bn)
{
uint addr, *a;//a:间接块的指针,通过a[]来访问间接块位置
struct buf *bp;
// 处理直接块
// 如果请求的块号在直接块的范围内(即小于NDIRECT)
//NDIRECT:直接映射的块数,非直接映射起始的下标
if(bn < NDIRECT){
// 获取inode中对应的磁盘块地址
// 如果该地址为0,表示这个直接块还没有被分配
if((addr = ip->addrs[bn]) == 0)
// 为该直接块分配一个新的物理块
ip->addrs[bn] = addr = balloc(ip->dev);
return addr; // 返回物理块号
}
bn -= NDIRECT;
// 处理间接块
// 如果请求的块号在间接块的范围内(即在NDIRECT到NDIRECT+NINDIRECT之间)
//NINDIRECT:一个磁盘块可以存储多少块地址
if(bn < NINDIRECT){
// 获取inode中间接块的地址
// 如果该地址为0,表示间接块还没有被分配
if((addr = ip->addrs[NDIRECT]) == 0)
// 为间接块分配一个新的物理块
ip->addrs[NDIRECT] = addr = balloc(ip->dev);
// 读取间接块,间接块存放了指向实际数据块的指针
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
// 获取间接块中对应的物理块地址
// 如果该地址为0,表示数据块还没有被分配
if((addr = a[bn]) == 0){
// 为数据块分配一个新的物理块,并更新间接块
a[bn] = addr = balloc(ip->dev);
// 将更新后的间接块写回磁盘
log_write(bp);
}
// 释放缓冲区
brelse(bp);
return addr; // 返回物理块号
}
// 如果块号超出了直接块和间接块的范围,抛出panic
panic("bmap: out of range");
}
启动 entry.S
这个文件包含了在多引导环境下,xv6 操作系统从实模式切换到保护模式并开始执行的代码。这里的代码主要设置了 CPU 的工作模式、内存分页、堆栈初始化,以及跳转到内核的主函数 main()
asm
# 多引导头部,用于指示多引导加载器如何加载 xv6。它定义了特定的魔数、标志和校验和,确保加载器正确识别和加载 xv6。
.p2align 2 # 内存对齐,确保Multiboot头部正确放置
.text # 指示接下来的部分是代码段
.globl multiboot_header # 定义全局符号multiboot_header
multiboot_header:
#define magic 0x1badb002 # Multiboot头部的魔数
#define flags 0 # Multiboot头部的标志位
.long magic # 写入魔数
.long flags # 写入标志位
.long (-magic-flags) # 写入校验和
定义了 `_start` 符号作为 ELF 的入口点。在 xv6 的上下文中,此时还未设置虚拟内存,因此入口点是 `entry` 的物理地址。
.globl _start
_start = V2P_WO(entry) # 设置ELF入口点为'entry'的物理地址
进入 xv6 的启动处理器时,分页尚未开启。这几行代码开启了页大小扩展,以支持 4MB 大小的页面。
.globl entry
entry:
movl %cr4, %eax # 读取CR4寄存器到EAX
orl $(CR4_PSE), %eax # 开启页大小扩展位
movl %eax, %cr4 # 将修改后的值写回CR4
这里设置了页目录的地址。`entrypgdir` 是页目录的开始,地址转换为物理地址,并存放在控制寄存器 `CR3` 中。
movl $(V2P_WO(entrypgdir)), %eax # 将entrypgdir的物理地址加载到EAX
movl %eax, %cr3 # 设置CR3寄存器为页目录的地址
启用分页机制。通过设置控制寄存器 CR0 中的 PG 位来启动分页。
movl %cr0, %eax # 读取CR0寄存器到EAX
orl $(CR0_PG|CR0_WP), %eax # 开启分页和写保护
movl %eax, %cr0 # 将修改后的值写回CR0
设置堆栈指针。stack 是内核堆栈的开始,KSTACKSIZE 是其大小。这行代码将堆栈指针 ESP 设置为堆栈的顶部。
movl $(stack + KSTACKSIZE), %esp # 设置堆栈指针
跳转到内核的主函数 main()。由于直接跳转会产生 PC 相对指令,因此这里使用间接跳转。
mov $main, %eax # 将main函数的地址加载到EAX
jmp *%eax # 间接跳转到main
声明内核堆栈的大小
.comm stack, KSTACKSIZE # 声明名为stack的内核堆栈