文件系统

对于文件系统,我们完成了比较大的改动,我们将自底而上地来介绍文件系统的每一个层次:
用户代码的加载
我们在mkfs.c中把所有的用户标准shell程序全部加载到fs.img中:
link_app.S中,我们规定了fs.img的数据全部放在数据段中:
.align 4
.section .data
.section .data.fs_img
.global fs_img_start
.global fs_img_end
fs_img_start:
.incbin "./fs.img"
fs_img_end:
.quad 0xffffffff
数据段的开头就是fs_img_start,结尾就是fs_img_end:
sd卡读写驱动
我们也实现了读取sd卡的模式,文件系统也可以不放在数据段,而放在sd卡中,此处参考了两个地方:
  • sifive官方提供的驱动u540-bootloader
  • 南开大学的ucore-SMP到sifive的移植 此处使用的是spi协议映射在0x10050000的地址处,但只能逐字读 注:在make时加入fat=SD即可使文件系统变成基于SD卡的模式
Ramdisk读写
最底层的就是磁盘硬件的读和写,对于磁盘的操作,我们可以使用sd卡,我们也可以使用内存来模拟磁盘(Ramdisk),现在介绍Ramdisk的相关读写操作.
  • 首先先从buf数据结构中找到磁盘块的扇区号.
  • 根据扇区号找到应该的内存地址.
  • 根据write的值确定是读操作还是写操作.
void
ramdisk_rw(struct buf *b, int write)
{
acquire(&ramdisklock);
uint sectorno = b->sectorno;
if(b->dev != ROOTDEV)
panic("wrong device number");
lookup_ramdisk_addr(sectorno);
char *addr = fs_img_start+sectorno*BSIZE;
if (write)
{
memmove((void*)addr, b->data, BSIZE);
}
else
{
memmove(b->data, (void*)addr, BSIZE);
}
release(&ramdisklock);
}
buf读写
对于block一级的读写,这一层读写为上层提供了在buf层级的读写:包括允许值,磁盘的扇区号以及暂时存储的位置.
struct buf {
int valid; // valid or not
int disk; // does disk "own" buf?
uint dev;
uint sectorno; // sector number
struct sleeplock lock;
uint refcnt;
struct buf *prev;//prev buf
struct buf *next;//next buf
uchar data[BSIZE];//data
};
实现方式很简单:根据设备的不同调用不同的底层读写接口.
void disk_read(struct buf *b)
{
#ifdef QEMU
#if QEMU!=SIFIVE_U
virtio_disk_rw(b, 0);
#else
// sdcard_read_sector(b->data, b->sectorno);
ramdisk_rw(b, 0);
#endif
#else
// sdcard_read_sector(b->data, b->sectorno);
ramdisk_rw(b, 0);
#endif
}
void disk_write(struct buf *b)
{
#ifdef QEMU
#if QEMU!=SIFIVE_U
virtio_disk_rw(b, 1);
#else
// sdcard_write_sector(b->data, b->sectorno);
ramdisk_rw(b, 1);
#endif
#else
// sdcard_write_sector(b->data, b->sectorno);
ramdisk_rw(b, 1);
#endif
}
这里是调用ramdisk提供的接口.
FAT32文件系统读写
对于每一个文件,都可以表示成FAT32中的一个表项,在FAT32文件系统中,我们维护一个叫做cluster的变量,所有的文件都在理论的cluster中存储.其中有对cluster最基本的读写操作:
  • 根据cluster的值找到扇区号.
  • 调用底层buf操作的接口获取对应扇区的存储数据.
  • 根据读还是写操作来进行读写:
static uint rw_clus(uint32 cluster, int write, int user, uint64 data, uint off, uint n)
{
if (off + n > fat.byts_per_clus)
panic("offset out of range");
uint tot, m;
struct buf *bp;
uint sec = first_sec_of_clus(cluster) + off / fat.bpb.byts_per_sec;
off = off % fat.bpb.byts_per_sec;
int bad = 0;
for (tot = 0; tot < n; tot += m, off += m, data += m, sec++) {
bp = bread(0, sec);
m = BSIZE - off % BSIZE;
if (n - tot < m) {
m = n - tot;
}
if (write) {
if ((bad = either_copyin(bp->data + (off % BSIZE), user, data, m)) != -1) {
bwrite(bp);
}
} else {
bad = either_copyout(user, data, bp->data + (off % BSIZE), m);
}
brelse(bp);
if (bad == -1) {
break;
}
}
return tot;
}
上层的文件系统调用rw_clus()是通过eread\ewrite函数通过调用的,eread\ewrite通过给定的文件描述符找到文件对应的cluster编号,然后再调用rw_clus.至于文件描述符struct dirent*,请读者自行查阅.
FAT32多文件系统
每一个FAT32文件系统主要由以下组成部分构成
struct fs{
uint devno; \\设备号
int valid; \\有效
struct dirent* image; \\镜像文件
struct Fat fat; \\FAT32相关信息
struct entry_cache ecache; \\路径节点cache
struct dirent root; \\文件系统根目录
void (*disk_init)(struct dirent*image); \\文件系统磁盘读写初始化
void (*disk_read)(struct buf* b,struct dirent* image); \\磁盘读
void (*disk_write)(struct buf* b,struct dirent* image); \\磁盘写
};
  • 对于一个FAT32文件系统首先需要知道其扇区块数、首扇区号等,这些信息存储在第0块扇区中,读出后放入Fat结构体
  • 每个文件系统的底层读写不一样,有的可能基于磁盘,有的基于某个镜像文件,根据需求填写对应image和磁盘读写函数等
关于挂载
挂载目录节点的mnt置1,访问此节点时,当作成对于其dirent设备号对应的文件系统的根目录进行处理
fileread和filewrite
首先我们先了解一下file的数据结构:
struct file {
enum { FD_NONE, FD_PIPE, FD_ENTRY, FD_DEVICE } type;
int ref; // reference count
char readable;
char writable;
struct pipe *pipe; // FD_PIPE
struct dirent *ep;
uint off; // FD_ENTRY
short major; // FD_DEVICE
};
数据结构有以下元素:
  • 文件类型
  • 引用数
  • 读写性
  • 文件描述符
filereadfilewrite会根据不同的文件类别进行读写操作,这一点我们在控制台输入输出的时候已经说明,具体分成三类:
  • FD_PIPE:管道,直接调用piperead\pipewrite.
  • FD_DEVICE:设备,调用设备相关的读写函数,其函数存放在ftable.
  • FD_ENTRY:普通文件,引用文件控制块并调用eread\ewrite.把文件操作交付给FAT32模块.