xv6Ftable - ccc-sp/riscv2os GitHub Wiki
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;
}