XV6源代碼閱讀-文件系統


Exercise1 源代碼閱讀

文件系統部分 buf.h fcntl.h stat.h fs.h file.h ide.c bio.c log.c fs.c file.c sysfile.c exec.c

1.buf.h:對xv6中磁盤塊數據結構進行定義,塊大小為512字節。

// xv6中磁盤塊數據結構,塊大小512字節
struct buf {
  int flags; // DIRTY, VALID
  uint dev;
  uint sector; // 對應扇區
  struct buf *prev; // LRU cache list
  struct buf *next; // 鏈式結構用於連接
  struct buf *qnext; // disk queue
  uchar data[512];
};
#define B_BUSY  0x1  // buffer is locked by some process
#define B_VALID 0x2  // buffer has been read from disk
#define B_DIRTY 0x4  // buffer needs to be written to disk

2.fcntl.h:宏定義操作權限。

#define O_RDONLY  0x000 // 只讀
#define O_WRONLY  0x001 // 只寫
#define O_RDWR    0x002 // 讀寫
#define O_CREATE  0x200 // 創建

3.stat.h:聲明文件或目錄屬性數據結構。

#define T_DIR  1   // Directory
#define T_FILE 2   // File
#define T_DEV  3   // Device

struct stat {
  short type;  // Type of file
  int dev;     // File system's disk device
  uint ino;    // Inode number
  short nlink; // Number of links to file
  uint size;   // Size of file in bytes
};

4.fs.h / fs.c:聲明超級塊、dinode、文件和目錄數據結構,以及相關的宏定義。

#define ROOTINO 1  // root i-number
#define BSIZE 512  // block size

// File system super block
struct superblock {
  uint size;         // Size of file system image (blocks)
  uint nblocks;      // Number of data blocks
  uint ninodes;      // Number of inodes.
  uint nlog;         // Number of log blocks
};

#define NDIRECT 12
#define NINDIRECT (BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + NINDIRECT)

// 磁盤上inode節點體現形式
// On-disk inode structure
struct dinode {
  short type;           // File type
  short major;          // Major device number (T_DEV only)
  short minor;          // Minor device number (T_DEV only)
  short nlink;          // Number of links to inode in file system
  uint size;            // Size of file (bytes)
  uint addrs[NDIRECT+1];   // Data block addresses
};

// Inodes per block.
#define IPB           (BSIZE / sizeof(struct dinode))

// Block containing inode i
#define IBLOCK(i)     ((i) / IPB + 2)

// Bitmap bits per block
#define BPB           (BSIZE*8)

// Block containing bit for block b
#define BBLOCK(b, ninodes) (b/BPB + (ninodes)/IPB + 3)

// Directory is a file containing a sequence of dirent structures.
#define DIRSIZ 14

// 文件或目錄據結構,目錄本身是以文件的方式存儲到磁盤上的,叫做目錄文件。
struct dirent {
  ushort inum; // i節點
  char name[DIRSIZ]; // 文件或目錄名
};

5.file.h:聲明inode、file數據結構。

struct file {
  // 分為管道文件,設備文件,普通文件
  enum { FD_NONE, FD_PIPE, FD_INODE } type; 
  int ref; // reference count
  char readable;
  char writable;
  struct pipe *pipe;
  struct inode *ip; // 指向inode節點
  uint off;
};

// 在內存中inode節點體現形式
// in-memory copy of an inode
struct inode {
  uint dev;           // Device number
  uint inum;          // Inode number
  int ref;            // Reference count
  int flags;          // I_BUSY, I_VALID

	  // 下面這些編程都是dinode的拷貝
	  // copy of disk inode
  short type;         
  short major;
  short minor;
  short nlink;
  uint size;
  uint addrs[NDIRECT+1];
};
#define I_BUSY 0x1
#define I_VALID 0x2

// table mapping major device number to device functions
struct devsw {
  int (*read)(struct inode*, char*, int);
  int (*write)(struct inode*, char*, int);
};

extern struct devsw devsw[];

#define CONSOLE 1

6.ide.c:磁盤IO的具體實現,xv6維護了一個進程請求磁盤操作的隊列(idequeue)。當進程調用void iderw(struct buf *b)請求讀寫磁盤時,該請求被加入等待隊列idequeue,同時進程進入睡眠狀態。當一個磁盤讀寫操作完成時,會觸發一個中斷,中斷處理程序ideintr()會移除隊列開頭的請求,喚醒隊列開頭請求所對應的進程。

// idequeue points to the buf now being read/written to the disk.
// idequeue->qnext points to the next buf to be processed.
// You must hold idelock while manipulating queue.

static struct spinlock idelock; // 保護 idequeue
static struct buf *idequeue; // 磁盤讀寫操作的請求隊列
……
// 等待磁盤進入空閑狀態
// Wait for IDE disk to become ready.
static int idewait(int checkerr)
{
  ……
  // 
  while(((r = inb(0x1f7)) & (IDE_BSY|IDE_DRDY)) != IDE_DRDY);
  ……
}

// 初始化IDE磁盤IO
void ideinit(void)
{
  ……
}

// 開始一個磁盤讀寫請求
// Start the request for b.  Caller must hold idelock.
static void idestart(struct buf *b)
{
  ……
}

// 當磁盤請求完成后中斷處理程序會調用的函數
// Interrupt handler.
void ideintr(void)
{
  …… // 處理完一個磁盤IO請求后,喚醒等待在等待隊列頭的那個進程
  wakeup(b);
  
  // 如果隊列不為空,繼續處理下一個磁盤IO任務
  // Start disk on next buf in queue.
  if(idequeue != 0)
    idestart(idequeue);
  ……
}

//PAGEBREAK!  上層文件系統調用的磁盤IO接口
// Sync buf with disk. 
// If B_DIRTY is set, write buf to disk, clear B_DIRTY, set B_VALID.
// Else if B_VALID is not set, read buf from disk, set B_VALID.
void iderw(struct buf *b)
{
  …… // 競爭鎖
  acquire(&idelock);  //DOC:acquire-lock

  // Append b to idequeue.
  b->qnext = 0;
  for(pp=&idequeue; *pp; pp=&(*pp)->qnext)  //DOC:insert-queue
    ;
  *pp = b;
  
  // Start disk if necessary.  開始處理一個磁盤IO任務
  if(idequeue == b)
    idestart(b);
  
  // Wait for request to finish.  睡眠等待
  while((b->flags & (B_VALID|B_DIRTY)) != B_VALID){
    sleep(b, &idelock);
  }

  release(&idelock);  // 釋放鎖
}

7.bio.c:Buffer Cache的具體實現。因為讀寫磁盤操作效率不高,根據時間與空間局部性原理,這里將最近經常訪問的磁盤塊緩存在內存中。主要接口有struct buf* bread(uint dev, uint sector)、void bwrite(struct buf *b),bread會首先從緩存中去尋找塊是否存在,如果存在直接返回,如果不存在則請求磁盤讀操作,讀到緩存中后再返回結果。bwrite直接將緩存中的數據寫入磁盤。
8.log.c:該模塊主要是維護文件系統的一致性。引入log模塊后,對於上層文件系統的全部磁盤操作都被切分為transaction,每個transaction都會首先將數據和其對應磁盤號寫入磁盤上的log區域,且只有在log區域寫入成功后,才將log區域的數據寫入真正存儲的數據塊。因此,如果在寫log的時候宕機,重啟后文件系統視為該log區的寫入不存在,如果從log區寫到真實區域的時候宕機,則可根據log區域的數據恢復。
9.sysfile.c:主要定義了與文件相關的系統調用。主要接口及含義如下:

// Allocate a file descriptor for the given file.
// Takes over file reference from caller on success.
static int fdalloc(struct file *f)
{
  …… // 申請一個未使用的文件句柄
}

int sys_dup(void)
{
  …… // 調用filedup對文件句柄的引用計數+1
  filedup(f);
  return fd;
}

int sys_read(void)
{
  …… // 讀取文件數據
  return fileread(f, p, n);
}

int sys_write(void)
{
  …… // 向文件寫數據
  return filewrite(f, p, n);
}

int sys_close(void)
{
  …… // 釋放文件句柄資源
  fileclose(f);
  return 0;
}

int sys_fstat(void)
{
  …… // 修改文件統計信息
  return filestat(f, st);
}

// Create the path new as a link to the same inode as old.
int sys_link(void)
{
  …… // 為已有的inode創建一個新名字
}

//PAGEBREAK!
int sys_unlink(void)
{
  …… // 解除inode中的某個名字, 若名字全被移除, inode回被釋放
}

static struct inode* create(char *path, short type, 
	    short major, short minor)
{
  …… // 
}

int sys_mkdir(void)
{
  …… // 創建一個目錄
}

int sys_mknod(void)
{
  …… // 創建一個新文件
}

int sys_chdir(void)
{
  …… // 切換目錄
}

int sys_pipe(void)
{
  …… // 創建一個管道文件
}

10.exec.c:只有一個exec接口,實質就是傳入elf格式的可執行文件,裝載到內存並分配內存頁,argv是一個指針數組,用於攜帶參數。

int exec(char *path, char **argv)
{
  …… // 判斷文件是否存在
  if((ip = namei(path)) == 0)
    return -1;
  ilock(ip);
  pgdir = 0;

  // Check ELF header  檢查elf頭是否合法
  if(readi(ip, (char*)&elf, 0, sizeof(elf)) < sizeof(elf))
    goto bad;
  ……
  
  // Load program into memory.
  sz = 0;
  for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
    if(readi(ip, (char*)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;
    if(ph.type != ELF_PROG_LOAD)
      continue;
    if(ph.memsz < ph.filesz)
      goto bad;
    if((sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0)
      goto bad;
    if(loaduvm(pgdir, (char*)ph.vaddr, ip, ph.off, ph.filesz) < 0)
      goto bad;
  }
  iunlockput(ip);
  ip = 0;

  // Allocate two pages at the next page boundary.
  // Make the first inaccessible.  Use the second as the user stack.
  sz = PGROUNDUP(sz);
  if((sz = allocuvm(pgdir, sz, sz + 2*PGSIZE)) == 0)
    goto bad;
  clearpteu(pgdir, (char*)(sz - 2*PGSIZE));
  sp = sz;

  // Push argument strings, prepare rest of stack in ustack.
  for(argc = 0; argv[argc]; argc++) {
    if(argc >= MAXARG)
      goto bad;
    sp = (sp - (strlen(argv[argc]) + 1)) & ~3;
    if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
      goto bad;
    ustack[3+argc] = sp;
  }
  ……

 bad:
  if(pgdir)
    freevm(pgdir);
  if(ip)
    iunlockput(ip);
  return -1;
}

Exercise2 帶着問題閱讀

1.了解 UNIX 文件系統的主要組成部分:超級塊(superblock),i節點(inode),數據塊(datablock),目錄塊(directoryblock),間接塊(indirectionblock)。分別解釋它們的作用。

boot super block dinode free bitmap blocks data blocks log blocks
第0塊 第1塊 superblock.ninodes塊 位圖管理空閑區塊 superblock.nblocks塊 superblock.nlog塊
  • bootloader引導區(第0塊):用於存放引導程序,系統啟動從這里開始;
  • superblock超級塊(第1塊):記錄文件系統的元信息,如文件系統的總塊數,數據塊塊數,i節點數,日志的塊數;
  • i節點(inode):從第2塊開始存放 i 節點,每一塊能夠存放多個 i 節點;
  • bitmap空閑塊管理區:用於存放空閑塊位圖,因為系統需要知道文件系統的使用情況,哪些塊已經分配出去了,哪些塊還未被分配;
  • 數據塊 (datablock):數據塊存儲的是真真實實的文件內容;
  • 目錄塊(directoryblock):文件系統中除了文件外,還有目錄,目錄本身是一個文件目錄(由很多FCB組成),文件目錄也需要以文件的形式存儲到磁盤上,存儲到磁盤上的這個文件叫做目錄文件,目錄文件就是存儲到目錄塊中的;
  • 間接塊(indirectionblock):xv6這里應該是指log日志塊,這是文件系統執行磁盤IO操作的中間層,主要目的是維護文件系統的一致性。

2.閱讀文件ide.c。這是一個簡單的ide硬盤驅動程序,對其內容作大致了解。

  • xv6 的文件系統分6層實現,從底至頂如下:
System calls File descriptors
Pathnames Recursive lookup
Directories Directory inodes
Files Inodes and block allocator
Transactions Logging
Blocks Buffer cache
  • 底層通過塊緩沖Buffer cache讀寫IDE 硬盤,它同步了對磁盤的訪問,保證同時只有一個內核進程可以修改磁盤塊;
  • 第二層Loggins向上層提供服務,該層實現了文件系統的一致性,使得更高層的接口可以將對磁盤的更新按會話打包,通過會話的方式來保證這些操作是原子操作(要么都被應用,要么都不被應用);
  • 第三層提供無名文件,每一個這樣的文件由一個 i 節點和一連串的數據塊組成;
  • 第四層將目錄實現為一種特殊的 i 節點,它的內容是一連串的目錄項,每一個目錄項包含一個文件名和對應的 i 節點;
  • 第五層提供了層次路經名(如/usr/rtm/xv6/fs.c這樣的),這一層通過遞歸的方式來查詢路徑對應的文件;
  • 最后一層將許多 UNIX 的資源(如管道,設備,文件等)抽象為文件系統的接口,極大地簡化了程序員的工作。

3.閱讀文件buf.h,bio.c。了解 XV6 文件系統中buffer cache層的內容和實現。描述buffer雙鏈表數據結構及其初始化過程。了解 buffer的狀態。了解對buffer的各種操作。

  • 數據結構bcache維護了一個由struct buf組成的雙向鏈表,同時bcache.lock用戶互斥訪問;
  • 首先系統調用binit()初始化緩存,隨即調用initlock初始化bcache.lock,然后循環遍歷buf數組,采用頭插法逐個鏈接到bcache.head后;
  • 上層文件系統讀磁盤時,調用bread(),隨即調用bget()檢查請求的磁盤塊是否在緩存中,如果命中,返回緩存命中結果。如果未命中,轉到底層的iderw()函數先將此磁盤塊從磁盤加載進緩存中,再返回此磁盤塊;
  • 上層文件系統寫磁盤時,調用bwrite()直接將緩存中的數據寫入磁盤。Buffer Cache層不會嘗試執行任何延遲寫入的操作,何時調用bwrite()寫入磁盤是由上層的文件系統控制的;
  • 上層文件系統可通過調用brelse()釋放一塊不再使用的緩沖區。
// buf.h
struct buf {
  int flags;
  uint dev;
  uint sector;
  struct buf *prev; // LRU cache list
  struct buf *next;
  struct buf *qnext; // disk queue
  uchar data[512];
};

// bio.c
struct {
  struct spinlock lock;
  struct buf buf[NBUF];

  // Linked list of all buffers, through prev/next.
  // head.next is most recently used.
  struct buf head;
} bcache;

void binit(void)
{
  struct buf *b;

  initlock(&bcache.lock, "bcache");

  //PAGEBREAK!  頭插法,每次都是插入到bcache.head的后面
  // Create linked list of buffers
  bcache.head.prev = &bcache.head;
  bcache.head.next = &bcache.head;
  for(b = bcache.buf; b < bcache.buf+NBUF; b++){
    b->next = bcache.head.next;
    b->prev = &bcache.head;
    b->dev = -1;
    bcache.head.next->prev = b;
    bcache.head.next = b;
  }
}

// Return a B_BUSY buf with the contents of the indicated disk sector.
struct buf* bread(uint dev, uint sector)
{
  struct buf *b;
  // 優先查找緩存
  b = bget(dev, sector);
  if(!(b->flags & B_VALID))
    iderw(b);  // 命中失敗時調用下一次接口真真實實讀磁盤
  return b;
}

// Write b's contents to disk.  Must be B_BUSY.
void bwrite(struct buf *b)
{
  if((b->flags & B_BUSY) == 0)
    panic("bwrite");
  b->flags |= B_DIRTY;
  iderw(b); // 立即寫, 未延遲寫
}

4.閱讀文件log.c,了解XV6文件系統中的logging和transaction機制;
日志存在於磁盤末端已知的固定區域。它包含了一個起始塊,緊接着一連串的數據塊。起始塊包含了一個扇區號的數組,每一個對應於日志中的數據塊,起始塊還包含了日志數據塊的計數。xv6 在提交后修改日志的起始塊,而不是之前,並且在將日志中的數據塊都拷貝到文件系統之后將數據塊計數清0。提交之后,清0之前的崩潰就會導致一個非0的計數值。

5.閱讀文件fs.h/fs.c。了解XV6文件系統的硬盤布局。

// On-disk inode structure
struct dinode {
  short type;           // File type
  short major;          // Major device number (T_DEV only)
  short minor;          // Minor device number (T_DEV only)
  short nlink;          // Number of links to inode in file system
  uint size;            // Size of file (bytes)
  // NDIRECT = 12, 前12個為直接索引, 
  // 第13個為間接索引, 可容納128個直接索引
  uint addrs[NDIRECT+1];   // Data block addresses  
};

6.閱讀文件file.h/file.c。了解XV6的“文件”有哪些,以及文件,i節點,設備相關的數據結構。了解XV6對文件的基本操作有哪些。XV6最多支持多少個文件? 每個進程最多能打開多少個文件?

  • xv6文件分為管道文件,設備文件和普通文件;
  • XV6最多支持同時打開100個文件,也就是分配100個文件句柄;
  • 單個進程最多能打開16個文件。
// param.h
#define NOFILE       16  // open files per process
#define NFILE       100  // open files per system

7.閱讀文件sysfile.c。了解與文件系統相關的系統調用,簡述各個系統調用的作用。
參見源代碼閱讀部分,已經做出了完整解答。

參考文獻

[1] xv6中文文檔
[2] xv6文件系統博客園
[3] xv6文件系統CSDN
[4] xv6文件系統CSDN
[5] 操作系統-文件系統課件


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM