Loading

记录做课下实验时的一些闲言碎语 -- lab5-2

exercise是头铁顺着做的,因此有些说法难免囿于一隅...我且说之,您且听之,咱就是图一乐~

exercise 5.3

nbitblock = (NBLOCK + BIT2BLK - 1) / BIT2BLK;
/*
NBLOCK = 1024
BIT2BLK = BY2BLK*8 = BY2PG*8 = 4096*8
nbitblock = 1

为什么blockno 不能为0? Block 0 是引导扇区 和分区表,写死了的,不可修改。

struct Block {
   uint8_t data[BY2BLK];
   uint32_t type;
} disk[NBLOCK];

一块盘里有NBLOCK个磁盘块。NBLOCK = 1024。

disk[NBLOCK]与我们的直觉相悖——它并不是说有NBLOCK个disk,而是说一个disk有NBLOCK个块。这里,disk[x]相当于disk的第x个块。

且看下面的代码

// Initial the disk. Do some work with bitmap and super block.
void init_disk() {
   int i, r, diff;

   // Step 1: Mark boot sector block.
   disk[0].type = BLOCK_BOOT; // 将第一个块标记为BOOT块。

   // Step 2: Initialize boundary.
   nbitblock = (NBLOCK + BIT2BLK - 1) / BIT2BLK; // 算出来似乎是1
   nextbno = 2 + nbitblock;

   // Step 2: Initialize bitmap blocks.
   for(i = 0; i < nbitblock; ++i) {
       disk[2+i].type = BLOCK_BMAP;
  }
   for(i = 0; i < nbitblock; ++i) {
       memset(disk[2+i].data, 0xff, BY2BLK);
  }
   if(NBLOCK != nbitblock * BIT2BLK) {
       diff = NBLOCK % BIT2BLK / 8;
       memset(disk[2+(nbitblock-1)].data+diff, 0x00, BY2BLK - diff);
  }

   // Step 3: Initialize super block.
   disk[1].type = BLOCK_SUPER;
   super.s_magic = FS_MAGIC;
   super.s_nblocks = NBLOCK;
   super.s_root.f_type = FTYPE_DIR;
   strcpy(super.s_root.f_name, "/");
}

• s_magic:魔数,用于识别该文件系统,为一个常量。

• s_nblocks:记录本文件系统有多少个磁盘块,本文件系统为 1024。

• s_root为根目录,其f_type为FTYPE_DIR,f_name为”/”。

enum {
   BLOCK_FREE  = 0,
   BLOCK_BOOT  = 1,
   BLOCK_BMAP  = 2,
   BLOCK_SUPER = 3,
   BLOCK_DATA  = 4,
   BLOCK_FILE  = 5,
   BLOCK_INDEX = 6,
};
// Save block link.
void save_block_link(struct File *f, int nblk, int bno)
{
   assert(nblk < NINDIRECT); // if not, file is too large !

   if(nblk < NDIRECT) {
       f->f_direct[nblk] = bno;
  }
   else {
       if(f->f_indirect == 0) {
           // create new indirect block.
           f->f_indirect = next_block(BLOCK_INDEX);
      }
      ((uint32_t *)(disk[f->f_indirect].data))[nblk] = bno;
  }
}
// Get next block id, and set `type` to the block's type.
int next_block(int type) {
   disk[nextbno].type = type;
   return nextbno++;
}
// Make new block contians link to files in a directory.
int make_link_block(struct File *dirf, int nblk) {
   int bno = next_block(BLOCK_FILE);
   save_block_link(dirf, nblk, bno);
   dirf->f_size += BY2BLK;
   return bno;
}
// file control blocks, defined in include/fs.h
struct File {
 u_char f_name[MAXNAMELEN]; // filename
 u_int f_size; // file size in bytes
 u_int f_type; // file type
 u_int f_direct[NDIRECT];
 u_int f_indirect;
 struct File *f_dir;
 u_char f_pad[BY2FILE - MAXNAMELEN - 4 - 4 - NDIRECT * 4 - 4 - 4];
};

总结

1

我们把磁盘划分成若干个连续的块。我们用文件控制块来控制读写(或者说组织)文件,一个文件控制块占1/8个磁盘块,也就是一个磁盘块能放8个文件控制块。

文件控制块只是磁盘块能存放的数据形式的其中一种。我们还可以往磁盘块里放文件指针,还可以放文件内容。

说白了,文件控制块struct File和磁盘块struct Block都是描述虚拟空间的方式。取决于coder选择用何种方式解读磁盘。

2 在create_file,我们在做什么

这个函数可以说是整个lab5最难的一个点了,填的我是一头雾水。主要原因还是在于当时对FCB、Block只有一个浅层的认识,对于它们在具体的fs里是如何被使用的还完全不了解。个人认为是本次lab跨度最大的一个坎。

create_file为新文件返回一个”新的、或曾经用过但当前可用的FCB“。传入的dirf为超级块管理的根目录,dirf->f_size表示整个根目录下的文件总大小。(完成后修正:dirf应该不限于根目录,只要是个目录型FCB就OK)我们要新建一个文件,就需要知道当前一共用了多少大小了,也就是用了多少个块了。这样我们才能不覆盖FCB、不跳过空闲空间。

int nblk = dirf->f_size / BY2BLK;

上面这句话是整个函数唯一给我们的提示。BY2BLK即一块的字节数。

nblk算出来等于“新文件FCB应该在第几个块中"。

下面让我们看看接下来做什么。

/* 对于第x个这种可能有二义性的描述,我用汉字数字表示中文语义上的第x个,用阿拉伯数字表示计算机科学语义上的第x个。e.g. 第一个 == 第0个,中文不会讲第零个。 */
if(nblk == 0) { // 表示如果我们还没有文件,也就是没有占据任何块。
       bno = make_link_block(dirf, nblk) // 在执行make_link_block后,dirf->f_size+=BY2BLK,返回值是顺次开辟的一个磁盘块的编号,现在bno就是这个编号了。同时会把dirf中nblk对应的文件指针置为bno。
       return (struct File *)(disk[bno].data); // 这时新块第一个位置,我们把它看作FCB返回就行了。
  }
   if(nblk <= NDIRECT) { // 我们占据的块数尚在直接指针的表示范围内。注意这里有=。
       bno = dirf -> f_direct[nblk - 1]; // 我们应该先假设最近开辟的块还没有被用完。
  } else { // 在间接指针的范围内,但不在直接指针的范围内。
       bno = ((uint32_t *)(disk[dirf->f_indirect].data))[nblk - 1]; // 同上
  }
   dirblk = (struct File *)disk[bno].data; // 我们从这个磁盘块的第一个FCB开始找,也就是该块上的第0个FCB。

   // Step2: Find an unused pointer
   for(i = 0; i < FILE2BLK; ++ i){ // FILE2BLK 表示 FCB个数 to Block,也就是“对于Block来说,有多少个FCB”。多插一句:之前我一直把"xx2yy"当做“xx转化成yy",怎么也解释不通。
       if(dirblk[i].f_name[0] == '\0') { // 我们发现这个FCB没用过,或者用过但是废弃了
           return &dirblk[i]; // 返回这个FCB
      }
  }
//not found
  // 说明最近的一个块已经被FCB占满,需要新开辟一个块来装FCB。
bno = make_link_block(dirf, nblk);
   return (struct File *)(disk[bno].data);

注意,我们只在最近新开辟的Block内为新的FCB找空间,不会遍历更早的Block。这可能导致很久以前的FCB被废弃后,无法再利用。可能是一个可以优化的点。

3

fs.c: dir_lookup(struct File *dir, char *name, struct File **file);

该函数调用 file_get_block(struct File *f, u_int filebno, void **blk)

int
file_get_block(struct File *f, u_int filebno, void **blk)
{
int r;
u_int diskbno;
u_int isnew;

// Step 1: find the disk block number is `f` using `file_map_block`.
if ((r = file_map_block(f, filebno, &diskbno, 1)) < 0) {
return r;
}

// Step 2: read the data in this disk to blk.
if ((r = read_block(diskbno, blk, &isnew)) < 0) {
return r;
}
return 0;
}

file_get_block*blk设为File f的第filebno个块的地址,即指向FIle f的第filebno个块。首先调用file_map_block

int
file_map_block(struct File *f, u_int filebno, u_int *diskbno, u_int alloc)
{
int r;
u_int *ptr;

// Step 1: find the pointer for the target block.
if ((r = file_block_walk(f, filebno, &ptr, alloc)) < 0) {
return r;
}

// Step 2: if the block not exists, and create is set, alloc one.
if (*ptr == 0) {
if (alloc == 0) {
return -E_NOT_FOUND;
}

if ((r = alloc_block()) < 0) {
return r;
}
*ptr = r;
}

// Step 3: set the pointer to the block in *diskbno and return 0.
*diskbno = *ptr;
return 0;
}

file_map_block把传入的*diskbno设置为File f的第filebno个文件块的"指针"(其实是block编号)。

  • 先看file_block_walk

    • int
      file_block_walk(struct File *f, u_int filebno, u_int **ppdiskbno, u_int alloc)
      {
      int r;
      u_int *ptr;
      void *blk;

      if (filebno < NDIRECT) {
      // Step 1: if the target block is corresponded to a direct pointer, just return the
      // disk block number.
      ptr = &f->f_direct[filebno];
      } else if (filebno < NINDIRECT) {
      // Step 2: if the target block is corresponded to the indirect block, but there's no
      // indirect block and `alloc` is set, create the indirect block.
      if (f->f_indirect == 0) {
      if (alloc == 0) {
      return -E_NOT_FOUND;
      }

      if ((r = alloc_block()) < 0) {
      return r;
      }
      f->f_indirect = r;
      }

      // Step 3: read the new indirect block to memory.
      if ((r = read_block(f->f_indirect, &blk, 0)) < 0) {
      return r;
      }
      ptr = (u_int *)blk + filebno;
      } else {
      return -E_INVAL;
      }

      // Step 4: store the result into *ppdiskbno, and return 0.
      *ppdiskbno = ptr;
      return 0;
      }
    • 最终,pdiskbno(也就是*ppdiskbno)(也就是上层函数的ptr)被设置为File f的第filebno个块的表项(表项实际上是数组的某一项)的地址。*ptr就是表项存放的block编号或者说索引号了。

    • 所以file_block_walk将diskbno设置为File f的第filebno个数据块的索引号。(if success)

  • 剩下的无非目前还没有第filebno个块,需要根据情况新开一个块,返回它的索引号。

再来看read_block。这个函数比较简单(指读起来,写起来难爆了),它"确保"指定的块已经被读入到内存中。顺带把*blk设为了diskaddr(blockno),指向这个块的首地址。那么我们在调用者函数里,就可以用*xxx来访问这个"指定的块"啦。

综上,file_get_block确保File f的第filebno个数据块已经被读取到内存中,且blk被设为指向这个数据块实际位于的磁盘块。成功返回0,失败返回<0。

4

有了前面的基础,我们可以看看dir_lookup了。

这里有一个问题:我们在file_get_block的分析里已经指出,这个函数的操作结果之一是得到一个指定的数据块,我们保存了指向该数据块的指针,以备后续访问。在指导书FCB的结构剖析中,我们看到,一个FCB可以保存多个数据块的索引,数据块是真正存放数据的地方。而在这个函数里,我们却要把数据块解析成FCB。这种转换一定可以成立吗?

阅读代码注释后发现,dir_lookup是在一个类型为目录的FCB里以文件名为关键字查找文件。成功则把传入的file指针设为指向命中的文件,并返回一个0,失败返回<0(根据失败原因,返回值也不同)。既然传入的FCB是一个目录型FCB,那么它的数据块存放的就不会是普通的文件数据,而是其他文件或目录的FCB。这就保证了转换可以成立。同时,可想而知,如果传入的是一个文件型FCB,那么就会解析到错误的数据。

换言之,这里需要一个PRE-CONDITION:传入的FCB指针一定指向目录型的FCB。

5 文件描述符

文件描述符FD是一个结构体。在我们的fs中,一个FD独占一页。先来看一下fd_alloc函数。

int
fd_alloc(struct Fd **fd)
{
// Find the smallest i from 0 to MAXFD-1 that doesn't have
// its fd page mapped. Set *fd to the fd page virtual address.
// (Do not allocate a page. It is up to the caller to allocate
// the page. This means that if someone calls fd_alloc twice
// in a row without allocating the first page we return, we'll
// return the same page the second time.)
// Return 0 on success, or an error code on error.
u_int va;
u_int fdno;

for (fdno = 0; fdno < MAXFD - 1; fdno++) {
va = INDEX2FD(fdno);

if (((* vpd)[va / PDMAP] & PTE_V) == 0) {
*fd = (struct Fd *)va;
return 0;
}

if (((* vpt)[va / BY2PG] & PTE_V) == 0) { //the fd is not used
*fd = (struct Fd *)va;
return 0;
}
}

return -E_MAX_OPEN;
}

这个函数找到index最小的、未被分配的FD。这里,MAXFD被定义为32,说明我们的fs最多支持32个文件描述符。注意,这个函数仅返回一个未分配的FD,不涉及物理页的映射。期望由调用者完成映射。

后面小咕一下

6

后面的exercises纯靠自己写难度有亿点大,好在有别的函数可以借鉴,泪目。基本上对照着write、close之类的就可以搞得差不多。

posted @ 2022-06-09 00:15  Barque  阅读(13)  评论(1编辑  收藏  举报