文件系统
对于文件系统,我们完成了比较大的改动,我们将自底而 上地来介绍文件系统的每一个层次:
用户代码的加载
我们在
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
};
数据结构有以下元素:
- 文件类型
- 引用数
- 读写性
- 文件描述符
fileread
和filewrite
会根据不同的文件类别进行读写操作,这一点我们在控制台输入输出的时候已经说明,具体分成三类:FD_PIPE
:管道,直接调用piperead\pipewrite
.FD_DEVICE
:设备,调用设备相关的读写函数,其函数存放在ftable
.FD_ENTRY
:普通文件,引用文件控制块并调用eread\ewrite
.把文件操作交付给FAT32模块.
Last modified 5mo ago