Appearance
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_statTIP
引入 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);
}