【操作系统】xv6源代码分析

考试内容(?)

解析题

fs.c
entry.S xv6初始化代码

编程题

fs.c 文件逻辑地址向物理地址转换 bmap
fs.c 查找磁盘块的位图的算法 balloc

代码分析

磁盘块分配 balloc

// 分配一个新的磁盘块,并将其清零。该函数会从设备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中的逻辑块号到对应的物理块号

// 与每个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()

# 多引导头部,用于指示多引导加载器如何加载 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的内核堆栈

你可能感兴趣的:(操作系统,服务器,后端,算法,linux,系统架构)