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] 操作系統-文件系統課件