Skip to content

mkfs.c

TIP

引入标准输入输出库,用于 printf, fprintf 等函数

#include <stdio.h>

TIP

引入 Unix 标准库,用于 read, write, lseek, close 等函数

#include <unistd.h>

TIP

引入标准库,用于 exit, malloc, free 等函数

#include <stdlib.h>

TIP

引入字符串处理库,用于 memcpy, memset, strcmp, strcpy 等函数

#include <string.h>

TIP

引入文件控制库,用于 open 等函数,以及 O_RDWR, O_CREAT 等常量

#include <fcntl.h>

TIP

引入断言库,用于 assert 宏,在调试时验证条件

#include <assert.h>

TIP

为了避免与主机操作系统(例如 Linux)的 stat 结构体冲突, 将 xv6 内部使用的 stat 重命名为 xv6_stat。

#define stat xv6_stat

TIP

引入 xv6 的基本类型定义,如 uint, ushort, uchar 等

#include "kernel/types.h"

TIP

引入 xv6 文件系统的定义,如 superblock, dinode, FSMAGIC 等

#include "kernel/fs.h"

TIP

引入 xv6 的 stat 结构体定义

#include "kernel/stat.h"

TIP

引入 xv6 的系统参数定义,如 FSSIZE, MAXFILE 等

#include "kernel/param.h"

TIP

如果编译器不支持 static_assert,则定义一个兼容版本。 C11 标准引入了 static_assert,但旧的编译器可能不支持。

#ifndef static_assert
#define static_assert(a, b) do { switch (0) case 0: case (a): ; } while (0)
#endif

TIP

定义文件系统中 inode 的总数

#define NINODES 200

TIP

磁盘布局注释,描述了文件系统在磁盘上的组织方式: [ 引导块 | 超级块 | 日志区 | inode 块 | 空闲位图 | 数据块 ] - 引导块 (Boot block): 用于启动系统,通常是第 0 块。 - 超级块 (Superblock): 存储文件系统的元数据,如大小、inode 数量等。 - 日志区 (Log): 用于实现日志功能,保证文件系统操作的原子性。 - Inode 块: 存储 inode 结构体,每个 inode 描述一个文件或目录。 - 空闲位图 (Bitmap): 记录哪些数据块是空闲的。 - 数据块 (Data blocks): 存储文件和目录的实际内容。

int nbitmap = FSSIZE / BPB + 1;
int ninodeblocks = NINODES / IPB + 1;
int nlog = LOGSIZE;
int nmeta;
int nblocks;

int fsfd;
struct superblock sb;
char zeroes[BSIZE];
uint freeinode = 1;
uint freeblock;

TIP

函数前向声明

void balloc(int);
void wsect(uint, void*);
void winode(uint, struct dinode*);
void rinode(uint inum, struct dinode *ip);
void rsect(uint sec, void *buf);
uint ialloc(ushort type);
void iappend(uint inum, void *p, int n);
void die(const char *);

TIP

将一个 16 位无符号整数转换为 RISC-V 的小端字节序

ushort
xshort(ushort x)
{
  ushort y;
  uchar *a = (uchar*)&y;
  a[0] = x;
  a[1] = x >> 8;
  return y;
}

TIP

将一个 32 位无符号整数转换为 RISC-V 的小端字节序

uint
xint(uint x)
{
  uint y;
  uchar *a = (uchar*)&y;
  a[0] = x;
  a[1] = x >> 8;
  a[2] = x >> 16;
  a[3] = x >> 24;
  return y;
}

TIP

mkfs 主函数,创建文件系统镜像

int
main(int argc, char *argv[])
{
  int i, cc, fd;
  uint rootino, inum, off;
  struct dirent de;
  char buf[BSIZE];
  struct dinode din;

TIP

静态断言,确保 int 类型是 4 字节,这对于文件系统布局至关重要

  static_assert(sizeof(int) == 4, "Integers must be 4 bytes!");

TIP

检查命令行参数

  if(argc < 2){
    fprintf(stderr, "用法: mkfs fs.img files...\n");
    exit(1);
  }

TIP

断言,确保一个块可以容纳整数个 inode 和目录项

  assert((BSIZE % sizeof(struct dinode)) == 0);
  assert((BSIZE % sizeof(struct dirent)) == 0);

TIP

打开(或创建)文件系统镜像文件

  fsfd = open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0666);
  if(fsfd < 0)
    die(argv[1]);

TIP

计算元数据块和数据块的数量 nmeta = 引导块(1) + 超级块(1) + 日志块 + inode块 + 位图块

  nmeta = 2 + nlog + ninodeblocks + nbitmap;
  nblocks = FSSIZE - nmeta;

TIP

初始化超级块

  sb.magic = FSMAGIC;
  sb.size = xint(FSSIZE);
  sb.nblocks = xint(nblocks);
  sb.ninodes = xint(NINODES);
  sb.nlog = xint(nlog);
  sb.logstart = xint(2);
  sb.inodestart = xint(2 + nlog);
  sb.bmapstart = xint(2 + nlog + ninodeblocks);

TIP

打印文件系统布局信息

  printf("nmeta %d (boot, super, log blocks %u inode blocks %u, bitmap blocks %u) blocks %d total %d\n",
         nmeta, nlog, ninodeblocks, nbitmap, nblocks, FSSIZE);

TIP

第一个空闲数据块的起始地址是元数据区之后

  freeblock = nmeta;

TIP

将整个文件系统镜像文件初始化为零

  for(i = 0; i < FSSIZE; i++)
    wsect(i, zeroes);

TIP

将超级块写入磁盘的第 1 块(第 0 块是引导块)

  memset(buf, 0, sizeof(buf));
  memmove(buf, &sb, sizeof(sb));
  wsect(1, buf);

TIP

分配根目录的 inode

  rootino = ialloc(T_DIR);
  assert(rootino == ROOTINO);

TIP

创建根目录的 "." (当前目录) 目录项

  bzero(&de, sizeof(de));
  de.inum = xshort(rootino);
  strcpy(de.name, ".");
  iappend(rootino, &de, sizeof(de));

TIP

创建根目录的 ".." (父目录) 目录项

  bzero(&de, sizeof(de));
  de.inum = xshort(rootino);
  strcpy(de.name, "..");
  iappend(rootino, &de, sizeof(de));

TIP

遍历命令行中提供的文件,并将它们添加到文件系统中

  for(i = 2; i < argc; i++){
    char *shortname;

TIP

如果文件路径以 "user/" 开头,则去掉这个前缀

    if(strncmp(argv[i], "user/", 5) == 0)
      shortname = argv[i] + 5;
    else
      shortname = argv[i];
    

TIP

确保文件名中不包含 '/',即所有文件都直接放在根目录下

    assert(index(shortname, '/') == 0);

TIP

打开文件

    if((fd = open(argv[i], 0)) < 0)
      die(argv[i]);

TIP

xv6 用户程序在 Makefile 中通常有 '_' 前缀 (如 _cat), 以避免与宿主机的命令冲突。在这里去掉前缀。

    if(shortname[0] == '_')
      shortname++;

TIP

确保文件名长度不超过最大限制

    assert(strlen(shortname) <= DIRSIZ);

TIP

为文件分配一个新的 inode

    inum = ialloc(T_FILE);

TIP

在根目录中为该文件创建一个目录项

    bzero(&de, sizeof(de));
    de.inum = xshort(inum);
    strncpy(de.name, shortname, DIRSIZ);
    iappend(rootino, &de, sizeof(de));

TIP

读取文件内容,并将其追加到新创建的 inode 中

    while((cc = read(fd, buf, sizeof(buf))) > 0)
      iappend(inum, buf, cc);

TIP

关闭文件

    close(fd);
  }

TIP

所有文件都添加完毕后,修正根目录的大小

  rinode(rootino, &din);
  off = xint(din.size);
  off = ((off / BSIZE) + 1) * BSIZE;
  din.size = xint(off);
  winode(rootino, &din);

TIP

根据已使用的块数,更新位图

  balloc(freeblock);

TIP

正常退出

  exit(0);
}

TIP

将缓冲区 buf 的内容写入文件系统的指定扇区 sec

void
wsect(uint sec, void *buf)
{

TIP

移动文件指针到目标扇区

  if(lseek(fsfd, sec * BSIZE, 0) != sec * BSIZE)
    die("lseek");

TIP

写入 BSIZE 字节的数据

  if(write(fsfd, buf, BSIZE) != BSIZE)
    die("write");
}

TIP

将 inode ip 的内容写入磁盘上编号为 inum 的 inode

void
winode(uint inum, struct dinode *ip)
{
  char buf[BSIZE];
  uint bn;
  struct dinode *dip;

  bn = IBLOCK(inum, sb);
  rsect(bn, buf);
  dip = ((struct dinode*)buf) + (inum % IPB);
  *dip = *ip;
  wsect(bn, buf);
}

TIP

从磁盘读取编号为 inum 的 inode 到 ip

void
rinode(uint inum, struct dinode *ip)
{
  char buf[BSIZE];
  uint bn;
  struct dinode *dip;

  bn = IBLOCK(inum, sb);
  rsect(bn, buf);
  dip = ((struct dinode*)buf) + (inum % IPB);
  *ip = *dip;
}

TIP

从文件系统的指定扇区 sec 读取数据到缓冲区 buf

void
rsect(uint sec, void *buf)
{

TIP

移动文件指针到目标扇区

  if(lseek(fsfd, sec * BSIZE, 0) != sec * BSIZE)
    die("lseek");

TIP

读取 BSIZE 字节的数据

  if(read(fsfd, buf, BSIZE) != BSIZE)
    die("read");
}

TIP

分配一个类型为 type 的新 inode

uint
ialloc(ushort type)
{
  uint inum = freeinode++;
  struct dinode din;

  bzero(&din, sizeof(din));
  din.type = xshort(type);
  din.nlink = xshort(1);
  din.size = xint(0);
  winode(inum, &din);
  return inum;
}

TIP

更新数据块位图,标记前 used 个块为已分配

void
balloc(int used)
{
  uchar buf[BSIZE];
  int i;

  printf("balloc: first %d blocks have been allocated\n", used);
  assert(used < BPB*BSIZE);
  bzero(buf, BSIZE);

TIP

遍历所有已使用的块 (从元数据区开始)

  for(i = 0; i < used; i++){

TIP

在位图中将对应位置的 bit 设为 1

    buf[i/8] = buf[i/8] | (0x1 << (i%8));
  }
  printf("balloc: write bitmap block at sector %d\n", sb.bmapstart);

TIP

将更新后的位图写回磁盘

  wsect(xint(sb.bmapstart), buf);
}

TIP

返回 a 和 b 中的较小值

#define min(a, b) ((a) < (b) ? (a) : (b))

TIP

向 inode inum 对应的文件中追加 n 字节的数据,数据源是 xp

void
iappend(uint inum, void *xp, int n)
{
  char *p = (char*)xp;
  uint fbn, off, n1;
  struct dinode din;
  char buf[BSIZE];
  uint indirect[NINDIRECT];
  uint x;

  rinode(inum, &din);
  off = xint(din.size);
  
  while(n > 0){
    fbn = off / BSIZE;
    assert(fbn < MAXFILE);

    if(fbn < NDIRECT){
      if(xint(din.addrs[fbn]) == 0){
        din.addrs[fbn] = xint(freeblock++);
      }
      x = xint(din.addrs[fbn]);
    } else {
      if(xint(din.addrs[NDIRECT]) == 0){
        din.addrs[NDIRECT] = xint(freeblock++);
      }
      rsect(xint(din.addrs[NDIRECT]), (char*)indirect);
      if(indirect[fbn - NDIRECT] == 0){
        indirect[fbn - NDIRECT] = xint(freeblock++);
        wsect(xint(din.addrs[NDIRECT]), (char*)indirect);
      }
      x = xint(indirect[fbn-NDIRECT]);
    }

TIP

计算本次写入可以写入多少字节 (不能超过块的边界)

    n1 = min(n, (fbn + 1) * BSIZE - off);
    rsect(x, buf);
    bcopy(p, buf + off - (fbn * BSIZE), n1);
    wsect(x, buf);

TIP

更新循环变量

    n -= n1;
    off += n1;
    p += n1;
  }
  din.size = xint(off);
  winode(inum, &din);
}

TIP

打印错误信息 s 并退出程序

void
die(const char *s)
{
  perror(s);
  exit(1);
}