iT邦幫忙

0

[6.1810][code] xv6 的 FileSystem (八) : File Descriptor Layer

  • 分享至 

  • xImage
  •  

系列文章 : [6.1810] 跟著 MIT 6.1810 學習基礎作業系統觀念

大綱

  • kernel/file.h/file
  • kernel/file.h/struct devsw
  • kernel/file.c/ftable
  • kernel/file.c/devsw[NDEV]
  • kernel/file.c/fileinit
  • kernel/file.c/filealloc
  • kernel/file.c/filedup
  • kernel/file.c/fileclose
  • kernel/file.c/filestat
  • kernel/file.c/fileread
  • kernel/file.c/filewrite

kernel/file.h/file

struct file {
  enum { FD_NONE, FD_PIPE, FD_INODE, FD_DEVICE } type;
  int ref; // reference count
  char readable;
  char writable;
  struct pipe *pipe; // FD_PIPE
  struct inode *ip;  // FD_INODE and FD_DEVICE
  uint off;          // FD_INODE
  short major;       // FD_DEVICE
};
  • 這個 structure 代表了一個正在被打開的檔案的狀態
    • 當我們想要使用一個 regular-file, device, 或是 pipe 的時候,我們不會直接去使用底下的 data-structure,而是會使用 struct-file 這一層的 function ( e.g. fileread, filewrite )。
    • 這讓使用者在使用的時候,只需要使用同樣的介面 ( struct-file 的 interface ),就可以使用 regular-file, device, pipe。
  • type
    • FD_NONE : 目前這個 struct-file 沒有被使用
    • FD_PIPE : 這個 strcture-file 實際上是一個 pipe
    • FD_INODE : 這個 structure-file 實際上是一個在 disk-hardware 裡面的 regular-file
    • FD_DEVICE : 這個 structure-file 實際上是一個 device ( e.g. uart )
  • int ref
    • reference count
    • 開起檔案 ( filealloc ),或是 filedup 都會增加 reference count
    • 關掉檔案 ( fileclose ) 會減少 reference count
    • 底層實作的資源 ( e.g. inode, pipe ) 只有在 ref count == 0 的時候,才會釋放掉。
  • char readable / char writeable
    • readable 跟 writable 都是 boolean flag,表示這個檔案的權限。
    • readable : 是否可讀
    • writable : 是否可寫
  • struct pipe *pipe
    • 當 struct-file 的類型是 FD_PIPE 的時候才會有意義。
    • 指向 struct pipe,常用於 inter-process communication ( IPC )。
  • struct inode *ip
    • 當 struct-file 的類型是 FD_INODE 或是 FD_DEVICE 才會有意義。會指向 struct-inode。
  • uint off
    • 當 struct-file 的類型是 FD_INODE 的時候,會有意義,代表目前讀寫檔案的 offset。
    • 每次進行 read/write 的時候,會改變 offset
  • short major
    • 當 struct-file 的類型是 FD_DEVICE 的時候會有意義。這代表了 kernel/file.c/struct-devsw 陣列的 index,讓我們知道該用什麼 read/write function。


kernel/file.h/struct devsw

// map major device number to device functions.
struct devsw {
  int (*read)(int, uint64, int);
  int (*write)(int, uint64, int);
};
  • 這個 structure 會記錄某個特定硬體的 read function 跟 write function。當某個檔案的類型是一個 device 的時候 ( FD_DEVICE )
    • 我們想寫入這個檔案的時候,就會呼叫該 device 的 write function。
    • 我們想讀取這個檔案的時候,就會呼叫該 device 的 read function。
  • read : 該 device 的 read function
  • write : 該 device 的 write function


kernel/file.c/ftable

struct {
  struct spinlock lock;
  struct file file[NFILE];
} ftable;
  • xv6-riscv 作業系統靠 ftable 去追蹤目前被任何 process 打開的檔案。
  • struct spinlock lock
    • 不論是直接對 ftable.file[N] 陣列本身進行操作,或是對該陣列的元素 ( struct file ) 進行操作,都需要取用這個 spinlock。
    • Q: 為什麼對個別檔案進行操作的時候,不拿該 struct file 本身的 spinlock 呢 ?
  • struct file file[NFILE] : 這個陣列中的每一個元素 ( strcut-file ),都代表了一個被開啟的檔案 ( 這個檔案可能是個 pipe, device 或是 regular-file, FD_PIPE, FD_DEVICE, FD_INODE )。
    • 多個 struct-file 可以指向同一個 struct-inode,表示一個在 disk-hardware 上的檔案可以被重複開啟多次,每多開啟一次,就會多一個 struct-file。


kernel/file.c/devsw[NDEV]

當我們想透過 struct-file 去呼叫某個 device 的 read/write function 的時候,會用該 device 的 major-number ( struct-file->major ) 去找出相對應的 struct devsw



kernel/file.c/fileinit

void
fileinit(void)
{
  initlock(&ftable.lock, "ftable");
}

在開機的時候,會由 cpuid == 0 的 CPU 呼叫這個 function 來進行初始化。
這裡會對 ftable 的 spinlock 進行初始化。



kernel/file.c/filealloc

// Allocate a file structure.
struct file*
filealloc(void)
{
  • 這個 function 會嘗試去 allocate 一個 struct-file。會去 global open file table 裡面去尋找可使用的 ( 目前沒被使用的 ) struct-file。
  • return value
    • 成功 : allocate 出來的 struct-file 的指標。
    • 失敗 : return 0。

  struct file *f;

  acquire(&ftable.lock);
  for(f = ftable.file; f < ftable.file + NFILE; f++){
    if(f->ref == 0){
      f->ref = 1;
      release(&ftable.lock);
      return f;
    }
  }
  release(&ftable.lock);
  return 0;
}
  • 在 ftable.file[N] 陣列裡面,尋找 ref == 0 ( 目前沒人使用的 ) struct-file。
  • 有的話,就 ref 設為 1,並 return 該 struce file 的 pointer
  • 找不到的話,就 return 0。


kernel/file.c/filedup

// Increment ref count for file f.
struct file*
filedup(struct file *f)
{
  acquire(&ftable.lock);
  if(f->ref < 1)
    panic("filedup");
  f->ref++;
  release(&ftable.lock);
  return f;
}

會把 struct-file 的 ref + 1,代表多一個人想要使用該 struct-file。



kernel/file.c/fileclose

// Close file f.  (Decrement ref count, close when reaches 0.)
void
fileclose(struct file *f)
{
  • 關掉檔案
  • f->ref--
  • 假如 f->ref 最後變成 0 了,就真的把這個檔案關起來。

  struct file ff;

  acquire(&ftable.lock);
  if(f->ref < 1)
    panic("fileclose");
  if(--f->ref > 0){
    release(&ftable.lock);
    return;
  }
  ff = *f;
  f->ref = 0;
  f->type = FD_NONE;
  release(&ftable.lock);

  if(ff.type == FD_PIPE){
    pipeclose(ff.pipe, ff.writable);
  } else if(ff.type == FD_INODE || ff.type == FD_DEVICE){
    begin_op();
    iput(ff.ip);
    end_op();
  }
}
  • type == FD_PIPE 的話,就 pipeclose
  • type == FD_INODE || type == FD_DEVICE 的話,就用 iput 把檔案內容寫進 disk-hardware
    • TODO … FD_DEVICE 為什麼會需要寫資料進 disk-hardware ?


kernel/file.c/filestat

// Get metadata about file f.
// addr is a user virtual address, pointing to a struct stat.
int
filestat(struct file *f, uint64 addr)
{
  • 讀取一個 struct file *f 的 metadata。
  • struct file *f : 想要讀取 metadata 的目標 struct file
  • addr : user space virtual address
  • return value
    • 0 : 成功
    • -1 : 失敗

  struct proc *p = myproc();
  struct stat st;
  
  if(f->type == FD_INODE || f->type == FD_DEVICE){
    ilock(f->ip);
    stati(f->ip, &st);
    iunlock(f->ip);
    if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0)
      return -1;
    return 0;
  }
  return -1;
}
  • ilock
    • 拿取 ip 的 sleep-lock
    • 需要的話,會從 disk-hardware 讀取 struct-dinode 到 struct-inode
  • stati
    • 拿取特定 struct-inode 的 metadata
  • iunlock
    • 釋放掉 ip 的 sleep-lock
  • copyout
    • 這個 kernel function 可以把 kernel space 的資料複製到 user space ( from kernel space to user space )。
    • pagetable_t pagetable
      • The user process's page table.
    • uint64 dstva
      • The destination virtual address in the user's address space.
    • char *src
      • The source memory address in the kernel's address space.
    • uint64 len
      • 要複製多少 bytes 的資料
    • return value
      • 0 : success
      • -1 : error


kernel/file.c/fileread

// Read from file f.
// addr is a user virtual address.
int
fileread(struct file *f, uint64 addr, int n)
{
  • struct file *f 讀取資料
    • struct-file->type 有許多 type,這邊會依靠 type 的類型 ( pipe, device, or inode/regular-file ) 來決定要呼叫什麼樣的 fuction。
  • struct file *f
    • 要從這個 struct file 讀取資料
  • uint64 addr
    • 讀取出來的資料,要放在這個 addr。
    • 這個 addr 是 user space virtual address
  • int n
    • 要讀取多少 bytes
  • return value
    • value >= 0 : 實際上讀取了多少 bytes
    • value == -1 : 寫入過程中發生錯誤

  int r = 0;

  if(f->readable == 0)
    return -1;

  if(f->type == FD_PIPE){
    r = piperead(f->pipe, addr, n);
  } else if(f->type == FD_DEVICE){
    if(f->major < 0 || f->major >= NDEV || !devsw[f->major].read)
      return -1;
    r = devsw[f->major].read(1, addr, n);
  } else if(f->type == FD_INODE){
    ilock(f->ip);
    if((r = readi(f->ip, 1, addr, f->off, n)) > 0)
      f->off += r;
    iunlock(f->ip);
  } else {
    panic("fileread");
  }

  return r;
}
  • piperead : 假如這個 file 是 FD_PIPE,則呼叫 pipe 相對應的 read function。
  • devsw[f->major].read(1, addr, n) : 假如這個 file 是 FD_DEVICE,則呼叫 device 對應的 read function。不同的 device 可以有自己的 read function。
  • readi : 假如這個 file 是 regular-file ( FD_FILE ),則呼叫 readi,嘗試去 disk-hardware ( VirtIO Block Device ) 讀取出資料。


kernel/file.c/filewrite

// Write to file f.
// addr is a user virtual address.
int
filewrite(struct file *f, uint64 addr, int n)
{
  • 把資料寫入 struct file *f。一樣會去看 struct-file->type 來決定要呼叫什麼樣的 fuction。
  • struct file *f
    • 要對這個 struct-file 寫入資料
  • uint64 addr
    • 準備要寫入的資料,會放在這個 address
    • 這個 address 是 user space virtual address
  • int n
    • 要寫入多少 bytes
  • return value
    • value >= 0 : 實際上寫入了多少 bytes
    • value == -1 : 寫入過程中發生錯誤

  int r, ret = 0;

  if(f->writable == 0)
    return -1;

  if(f->type == FD_PIPE){
    ret = pipewrite(f->pipe, addr, n);
  } else if(f->type == FD_DEVICE){
    if(f->major < 0 || f->major >= NDEV || !devsw[f->major].write)
      return -1;
    ret = devsw[f->major].write(1, addr, n);
  } else if(f->type == FD_INODE){
    // write a few blocks at a time to avoid exceeding
    // the maximum log transaction size, including
    // i-node, indirect block, allocation blocks,
    // and 2 blocks of slop for non-aligned writes.
    int max = ((MAXOPBLOCKS-1-1-2) / 2) * BSIZE;
    int i = 0;
    while(i < n){
      int n1 = n - i;
      if(n1 > max)
        n1 = max;

      begin_op();
      ilock(f->ip);
      if ((r = writei(f->ip, 1, addr + i, f->off, n1)) > 0)
        f->off += r;
      iunlock(f->ip);
      end_op();

      if(r != n1){
        // error from writei
        break;
      }
      i += r;
    }
    ret = (i == n ? n : -1);
  } else {
    panic("filewrite");
  }

  return ret;
}
  • FD_PIPE
    • 呼叫 pipewrite
  • FD_DEVICE
    • 呼叫相對應 device 的 write function
  • FD_INODE
    • max = ((MAXOPBLOCKS-1-1-2) / 2) * BSIZE : 計算一次最多可以放多少個 block ( 每個 block 的 size 為 BSIZE ),因為 log layer 的 log blocks 有個數上的限制,所以沒辦法一次寫入太多 block。
    • 接下來就是經典的 pattern
      • begin_op() : 表示要開始進入一個 transaction
      • ilock(f->ip) : 拿 sleep-lock,從 disk-hardware 載入 struct-dinode
      • writei : 寫資料到相對應 inode 的 content blocks
      • iunlock(f->ip) : 釋放 sleep-lock
      • end_op() : 表示要離開一個 transaction。有機會觸發 commit,真的將內容寫進 log-blocks 後,再寫進 data-blocks。



圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言