1. 程式人生 > 實用技巧 >MIT-6.S081-2020實驗(xv6-riscv64)九:fs

MIT-6.S081-2020實驗(xv6-riscv64)九:fs

實驗文件

概述

這次實驗涉及檔案系統,重點是對inode節點的操作。

內容

Large files

這個任務主要目的是支援更大的檔案。和記憶體對映類似,檔案系統中也有一個類似“頁表”的結構,每個檔案(inode)都有自己的一個“頁表”,維護自己檔案佔用的檔案塊。和記憶體不同的是,這個“頁表”的級別是自定義的,原始的xv6的表有13項,前12項直接包含檔案塊的地址,第13項是二級表的地址,二級表包含256項,每項都是檔案塊的地址,所以單個檔案最大為12+256個檔案塊,為了支援更大的檔案,需要將直接包含檔案塊地址的表項中取一項支援三級表,這樣單個檔案就可以擴大到11+256+256*256塊。整體思路還是很清晰的,bmap函式新增:

  bn -= NINDIRECT;

  if(bn < NININDIRECT){
      int lev1 = bn / NINDIRECT, lev2 = bn % NINDIRECT;
    // Load indirect block, allocating if necessary.
    if((addr = ip->addrs[NDIRECT + 1]) == 0)
      ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev);
    bp = bread(ip->dev, addr);
    a = (uint*)bp->data;
    if((addr = a[lev1]) == 0){
      a[lev1] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);
    bp = bread(ip->dev, addr);
    a = (uint*)bp->data;
    if((addr = a[lev2]) == 0){
      a[lev2] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);
    return addr;
  }
  panic("bmap: out of range");

itrunc函式也做相應的新增:

  if(ip->addrs[NDIRECT + 1]){
    bp = bread(ip->dev, ip->addrs[NDIRECT + 1]);
    a = (uint*)bp->data;
    for(j = 0; j < NINDIRECT; j++) {
      if(a[j]) {
        bp2 = bread(ip->dev, a[j]);
        a2 = (uint*)bp2->data;
        for(k = 0; k < NINDIRECT; k++) if (a2[k]) bfree(ip->dev, a2[k]);
        brelse(bp2);
        bfree(ip->dev, a[j]);
      }
    }
    brelse(bp);
    bfree(ip->dev, ip->addrs[NDIRECT + 1]);
    ip->addrs[NDIRECT + 1] = 0;

這個任務主要實現軟符號連結,個人還是覺得有點難度,需要對inode節點的各類操作比較熟悉。首先是給inode和dinode新增一個字串屬性用來儲存符號連結中的目標路徑,比如dinode:

struct dinode {
  short type;           // File type
  short major;          // Major device number (T_DEVICE only)
  short minor;          // Minor device number (T_DEVICE only)
  short nlink;          // Number of links to inode in file system
  uint size;            // Size of file (bytes)
  uint addrs[NDIRECT+2];   // Data block addresses
  char target[MAXTARGET];
};

主要這個MAXTARGET這個常量是有講究的,因為dinode是一個一個排布在一個硬碟塊裡面的,所以硬碟塊的大小必須是dinode結構體大小的倍數,硬碟塊的大小是常量BSIZE的大小,即1024位元組,當沒有target屬性時,dinode的大小為2*4+4+4*13=64,剛好被1024整除,同時檢視param.h可以發現xv6規定的路徑長度最大可以為128,所以MAXTARGET還得大於128,因此我取MAXTARGET=192,192+64=256,被1024整除。

然後就是sys_symlink:

uint64 sys_symlink(void) {                                                                                       char target[MAXPATH], path[MAXPATH];
    if(argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)
        return -1;
    begin_op();
    struct inode *ip = create(path, T_SYMLINK, 0, 0);
    if (ip == 0) {
        end_op(); return -1;
    }
    memmove(ip->target, target, sizeof(target));
    iupdate(ip); iunlockput(ip);
    end_op(); return 0;
}

首先需要注意獲得傳入系統呼叫的字串需要用argstr,不能用argaddr獲得地址後自己複製,因為那個地址是使用者記憶體的虛擬地址,現在在核心態,頁表已經被換掉了。這裡呼叫了create函式來建立符號檔案,因為create函式裡沒有對inode的target屬性賦值,所以需要在這裡處理。另外就是create函式返回的有效inode是已經經過iget和ilock的了,所以這裡create完就直接賦值target屬性並更新到對應的dinode,然後iunlock解鎖再iput釋放inode指標(合起來是iunlockput函式)。

接著是對open函式的修改,主要就是新增一個沿著符號連結不斷查詢的過程,經過查詢後得到的路徑才是需要open的真正路徑:

  if(omode & O_CREATE){
      ......
  } else {
      int cnt = 0;
      for (;;) {
          ip = namei(path);
          if (ip == 0) {
              end_op(); return -1;
          }
          ilock(ip);
          if(ip->type != T_SYMLINK || (omode & O_NOFOLLOW)) break;
          memmove(path, ip->target, MAXPATH);
          iunlockput(ip);
          cnt++;
          if (cnt > 9) {
              end_op(); return -1;
          }
      }
      ......

namei得到的inode是經過iget但沒ilock的,所以取屬性和修改屬性需先ilock。另外就是memmove引數的最後一個引數需要是MAXPATH而不是sizeof(ip->target),因為inode的target屬性我跟隨dinode的target屬性都設成192位元組的字串,大小大於MAXPATH即128,所以如果按sizeof來複制會溢位。

由於我使用的是虛擬機器,雖然我已經通過使用無桌面版arch linux+ssh控制來儘可能減少CPU和記憶體的消耗了,但檔案讀寫還是非常難頂。雖然程式單獨測試正確,思路也和網上別人通過的程式思路基本一樣,最後make grade的時候還是超時了,不得已,把測試腳本里的bigfile和usertests測試時限都改成10分鐘才通過(usertests裡的writebig好像在這次實驗裡格外耗時)。估計使用真機或WSL讀寫真磁碟來完成本實驗情況會好一些。