xv6Ftable - ccc-sp/riscv2os GitHub Wiki

xv6: UNIX 是如何做到一切皆檔案的?

UNIX 成功的一個重要設計理念是一切皆檔案,也就是可以把《輸出入裝置》也都看成檔案,然後用 read, write 就可以存取。

檔案表 File Table 是達成這種功能的主要原因,請參考下圖:

檔案表的 0, 1, 2 格,一開始就被填入 STDIN, STDOUT, STDERR 的處理函數,對應到 console 的輸出入。

而第 3 格之後,則是在你用 open() 去開檔時,才會被填入的,通常對應到磁碟中的 inode 。

但如果我們用 dup()/open()/close() 等函數去搭配,例如:

close(1); // STDOUT
open("logfile", O_WRONLY);

這樣就會導致原本輸出到 STDOUT 的內容,改被輸出到 logfile 當中。

檔案表每一格的結構如下:

kernel/file.h

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
};

一個行程最多可以開啟 NFILE 個檔案。

kernel/file.c

struct {
  struct spinlock lock;
  struct file file[NFILE];
} ftable; // 檔案表

當執行 open() 或 dup() 時,會找到第一個沒被使用的格子分配出去:

// Allocate a file structure. (open 時會找出 ftable 中哪格可以用)
struct file*
filealloc(void)
{
  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;
}

當我們用 read/write 去讀寫時,通常是讀取《檔案》,但也有可能是《裝置或 pipe》。

// Read from file f.
// addr is a user virtual address.
int
fileread(struct file *f, uint64 addr, int n) // 檔案讀取
{
  int r = 0;

  if(f->readable == 0) // 若非可讀,離開
    return -1;

  if(f->type == FD_PIPE){ // 若是管道
    r = piperead(f->pipe, addr, n); // 呼叫 piperead
  } 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); // 呼叫裝置 read 函數
  } else if(f->type == FD_INODE){ // 若是磁碟 inode
    ilock(f->ip);
    if((r = readi(f->ip, 1, addr, f->off, n)) > 0) // 呼叫 readi
      f->off += r;
    iunlock(f->ip);
  } else {
    panic("fileread");
  }

  return r;
}

例如 0,1,2 這幾格,一開始就都被對應到 console 裝置,因此 write(1, "Hello!") 會印出 Hello 到終端機。

kernel/console.c

void
consoleinit(void) // 初始化本模組
{
  initlock(&cons.lock, "cons");

  uartinit(); // 初始化 UART

  // connect read and write system calls
  // to consoleread and consolewrite.
  // 第一號裝置 devsw[1] 為 console
  devsw[CONSOLE].read = consoleread;    // 設定 console 的 read 函數
  devsw[CONSOLE].write = consolewrite;  // 設定 console 的 write 函數
}


//
// user write()s to the console go here.
//
int
consolewrite(int user_src, uint64 src, int n) //  write() 呼叫的 console 版本
{
  int i;

  for(i = 0; i < n; i++){
    char c;
    if(either_copyin(&c, user_src, src+i, 1) == -1) // 從作業系統取得 src[i] 的字元
      break;
    uartputc(c); // 透過 uart 印出
  }

  return i;
}
⚠️ **GitHub.com Fallback** ⚠️