文件系统

对于文件系统,我们完成了比较大的改动,我们将自底而上地来介绍文件系统的每一个层次:

用户代码的加载

我们在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模块.

Last updated