mit6.828 - lab5筆記(上)

来源:https://www.cnblogs.com/toso/Undeclared/17955804
-Advertisement-
Play Games

文件系統結構 unix的文件系統相關知識 unix將可用的磁碟空間劃分為兩種主要類型的區域:inode區域和數據區域。 unix為每個文件分配一個inode,其中保存文件的關鍵元數據,如文件的stat屬性和指向文件數據塊的指針。 數據區域中的空間會被分成大小相同的數據塊(就像記憶體管理中的分頁)。數據 ...


文件系統結構

unix的文件系統相關知識

  • unix將可用的磁碟空間劃分為兩種主要類型的區域:inode區域數據區域
  • unix為每個文件分配一個inode,其中保存文件的關鍵元數據,如文件的stat屬性和指向文件數據塊的指針。
  • 數據區域中的空間會被分成大小相同的數據塊(就像記憶體管理中的分頁)。數據塊中存儲文件數據目錄元數據
  • 目錄條目包含文件名和指向inode的指針

jos的文件系統
jos對文件系統進行了簡化——不使用inode。文件的所有元數據存儲在目錄條目中。
jos中的數據區域也會被劃分為“塊”


磁碟上的數據結構

磁碟不可能以位元組為單位進行讀寫,而是以扇區為單位進行讀寫。jos 中扇區為512位元組。
硬體層面,磁碟驅動以扇區為單位進行數據讀寫。
軟體層面,操作系統以“塊”為單位進行數據分配。
jos 中塊的大小為4096位元組。

超級塊 superblocks

如上文所述,jos 的數據區域被劃分為塊。
jos 還會有一個超級塊,位於磁碟的第1個塊(第0個塊中存放 引導載入器和分區表),由 inc/fs.h 中的 struct super 定義。內容如下:

struct Super {
	uint32_t s_magic;		// Magic number: FS_MAGIC
	uint32_t s_nblocks;		// Total number of blocks on disk
	struct File s_root;		// Root directory node
};

其中包含磁碟中存放的塊的總量和文件系統的根目錄
超級塊可能一個塊存不下,所以會占據多個塊。

文件元數據 File Meta data

文件元數據定義在 inc/fs.h 中的 struct File , 內容如下:

struct File {
	char f_name[MAXNAMELEN];	// filename
	off_t f_size;			// file size in bytes
	uint32_t f_type;		// file type

	// Block pointers.
	// A block is allocated iff its value is != 0.
	uint32_t f_direct[NDIRECT];	// direct blocks
	uint32_t f_indirect;		// indirect block

	// Pad out to 256 bytes; must do arithmetic in case we're compiling
	// fsformat on a 64-bit machine.
	uint8_t f_pad[256 - MAXNAMELEN - 8 - 4*NDIRECT - 4];
} __attribute__((packed));	// required only on some 64-bit machines

其中包含文件名、大小、類型、數據塊指針,以及pad(該結構保持256位元組大小)。
一個 File struct 對應的數據塊分為兩種:直接塊、間接塊。

  • 直接塊有10個,可以存儲40kb的文件,存儲在 File struct 的數據塊指針數組 f_direct 中。
  • 間接塊最多可以有1024個,如果直接塊不夠用, File struct 會被分配一個數據塊(地址存儲在 f_indirect ),裡面全都用於保存數據塊指針。(一個數據塊大小為4096位元組,一個數據塊指針占據4位元組,故間接塊可以有4096/4=1024個)
    因此 jos 的文件最多可以 1034個塊, 可以存儲 $1034 \times 4096kb$ 的數據。
    File的結構體的情況可以用下麵的圖片來理解:


目錄文件和普通文件

jos 的 File struct 既可以用來表示普通文件,也可以代表目錄文件,由 File struct 的 f_type 欄位來區分。
文件系統管理普通文件和目錄文件的方式完全相同,具體來說,文件系統不解析普通文件內的數據,但解析目錄文件內的數據,因為其中是該目錄下的文件的數據。
超級塊中包含一個 File struct , 其中包含了文件系統根目錄的元數據。


文件系統


磁碟訪問

在之前的 lab 中,我們通過彙編的 inout 指令,向磁碟設備發送讀寫信號。但是這樣果然還是好麻煩, jos 將 IDE 磁碟驅動程式作為用戶級進程來實現。(傳統的策略是將磁碟驅動加入至內核,然後以系統調用的形式供進程調用)。
為了能夠讓用戶級進程在不使用磁碟中斷的前提下,擁有執行特殊設備I/O指令的許可權,需要使用 EFLAG 寄存器中的 IOPL 位。
因此,操作系統在創建我們的 用戶級文件系統 進程時,應該對其 env struct 中的 eflag 成員置位。因此,我們需要對 lab3 編寫的 env_create 進行修改。由於我們只允許 用戶及文件系統進程 進行磁碟IO,所以需要將其他進程和 用戶級文件系統進程 進行區分, jos 的方案是專為 用戶及文件系統進程 設立一個 ENV_TYPE_FS 。具體任務見練習1.


塊緩存(練習2@fs/bc.c)

在之前的 lab 中,我們訪問磁碟是通過分區號來訪問的。但是這樣果然好麻煩,要是能像記憶體一樣用線性地址訪問就好了。

塊緩存的編址
為了實現上面的目標, jos 將 用戶級文件系統進程的虛擬地址空間中 0x1000_0000 (DISKMAP) 到 0xD000_0000 (DISKMAP+DISKMAX) 部分用於和磁碟的存儲空間進行映射。

具體來說,首先將塊號和記憶體地址進行映射,當我們訪問塊緩存中的地址時,先將地址轉化為塊號,然後再進行讀寫。然後將塊緩存的地址通過頁表、頁目錄進行管理。

順帶一提,DISKMAX 大小為 0xC000_0000B = 3GB , 因此 jos 僅支持 3GB 大小的磁碟存儲空間。

塊緩存的同步方案
為了同步記憶體中的磁碟數據,和磁碟中實際存儲的數據,我們利用 PTE 的 PTE_D 位追蹤數據是否被改動。(當記憶體地址addr所指頁被改動時,MMU會將其對應的PTE中的 PTE_D 置位)。

將整個磁碟都讀入記憶體很浪費時間,我們可以參考“寫時複製”實現一種“讀時載入”的頁錯誤處理程式。當程式讀取的塊緩存地址對應的數據尚未被載入時,觸發頁錯誤,然後將對應的數據從磁碟中載入到其塊緩存地址。

塊緩存的是同步的實現,即是練習2的內容。

塊緩存的實現
jos 對塊緩存的實現,主要位於 fs/bc.c 中。其中包含如下函數:

void * diskaddr(uint32_t blockno)

將塊號轉化為對應的塊緩存地址

void*
diskaddr(uint32_t blockno)
{
	if (blockno == 0 || (super && blockno >= super->s_nblocks))
		panic("bad block number %08x in diskaddr", blockno);
	return (char*) (DISKMAP + blockno * BLKSIZE);
}

bool va_is_mapped(void *va)

檢查塊緩存 va 是否已經被映射到頁目錄。
具體方法:檢查 va 對應的 PDE 和 PTE 的 PTE_P 是否置位。

bool
va_is_mapped(void *va)
{
	return (uvpd[PDX(va)] & PTE_P) && (uvpt[PGNUM(va)] & PTE_P);
}

bool va_is_dirty(void *va)

檢查塊緩存 VA 是否"臟"。臟指的是數據是否被改動。
具體方法:檢查 va 對應的 PTE 的 PTE_D 是否置位。

bool
va_is_dirty(void *va)
{
	return (uvpt[PGNUM(va)] & PTE_D) != 0;
}

static void bc_pgfault(struct UTrapframe *utf)

缺頁中斷,當塊緩存範圍內地址觸發頁錯誤時會被調用。

static void
bc_pgfault(struct UTrapframe *utf)
{
    void *addr = (void *) utf->utf_fault_va;                    //取出引發錯誤的地址
    uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE;    //轉化為塊號
	int r;

	// 檢查地址是否越界
	if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE))
		panic("page fault in FS: eip %08x, va %08x, err %04x",
		      utf->utf_eip, addr, utf->utf_err);

	// 檢查塊號是否越界
	if (super && blockno >= super->s_nblocks)
		panic("reading non-existent block %08x\n", blockno);

	// Allocate a page in the disk map region, read the contents
	// of the block from the disk into that page.
	// Hint: first round addr to page boundary. fs/ide.c has code to read
	// the disk.
	//
	// LAB 5: you code here:
	//申請一塊物理頁,將其映射到addr處
	addr  = ROUNDDOWN(addr, PGSIZE);		//對齊需要讀取的地址
	sys_page_alloc(0, addr, PTE_W|PTE_U|PTE_P);	//調用page_alloc的syscall,申請記憶體頁
	// 將磁碟中的數據讀入記憶體
	// 磁碟以扇區為單位讀取數據,一個扇區512位元組
	// 文件系統以塊為單位管理數據,一個塊4096位元組(一頁)
	if((r=ide_read(blockno * BLKSECTS, addr, BLKSECTS))<0)	//從硬碟讀取數據到記憶體頁
		panic("ide_read: %e" ,r);
	
	//由於,上面的操作對addr進行記憶體寫操作,PTE_D被置位
	//如果坐視不管,後面會被文件系統認為,需要寫回磁碟,這肯定是不合理的
	//因此通過sys_page_map的方式清空臟位(?為什麼sys_page_map可以清空臟位)
	if ((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0)
		panic("in bc_pgfault, sys_page_map: %e", r);

	// Check that the block we read was allocated. (exercise for
	// the reader: why do we do this *after* reading the block
	// in?)
	//(這裡我不是很明白)
	if (bitmap && block_is_free(blockno))
		panic("reading free block %08x\n", blockno);
}

void flush_block(void *addr)

刷新塊緩存地址 addr

void
flush_block(void *addr)
{
	//將addr轉換位塊號
	uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE;

	//檢查addr是否越界
	if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE))
		panic("flush_block of bad va %08x", addr);

	// LAB 5: Your code here.
	// panic("flush_block not implemented");
	//addr向下對齊
	addr = ROUNDDOWN(addr, PGSIZE);

	//檢查數據是否已經讀入,數據頁是否有臟位
	if(!va_is_mapped(addr) || !va_is_dirty(addr))
	{
		return ;
	}
	int r = 0;
	//將數據寫回至磁碟
	if((r = ide_write(blockno*BLKSECTS, addr, BLKSECTS))<0)
	{
		panic("in flush_block, ide_write() : %e",r);
	}
	//清空臟位
	if((r=sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL))<0)
		panic("in bc_pgfault, sys_page_map: %e", r);
}

boid bc_init(void)

初始化塊緩存

void
bc_init(void)
{
	struct Super super;
	//設置文件系統的缺頁處理函數
	set_pgfault_handler(bc_pgfault);
	check_bc();

	// cache the super block by reading it once
	// 對super塊的塊緩存地址讀取,並給super
	// 首先會觸發缺頁中斷,將block#1載入至diskaddr(1)處
	// 然後再進行memmove,將讀取到的數據填充super struct
	memmove(&super, diskaddr(1), sizeof super);
}

塊點陣圖

為什麼需要塊點陣圖?

塊緩存實現之後,我們擁有了從磁碟中讀寫、改寫數據塊的能力。接下來考慮一個問題,我們如何刪除某個塊的內容?將其數據用0覆蓋嗎?磁碟的磁頭聽到這種方案內心一定是麻的。一種簡單的方案是用一個點陣圖來表示所有塊的狀態,每個位代表一個塊,1代表空閑,0代表占用。這樣,當我們要"刪除"一個塊的數據時,只要在點陣圖中將對應位置0即可。

塊點陣圖本身也是占用磁碟數據空間的, jos 的塊點陣圖存儲在磁碟的第2個塊以及之後,塊點陣圖一個塊存不下。(手冊中只有一張示意圖提到這個點)

jos 的點陣圖是一個u32int型數組,一個u32int有32個位,對應32個塊。3GB磁碟空間共有 $$\frac{3\times 1024 \times 1024 \times 1024 B}{4\times 1024 B} = 768\times 1024 個$$一個塊共有 $4096\times 8bit = 32 \times 1024 bit$ 因此,bitmap應該占據 $\frac{768}{32} = 24$ 個塊
磁碟的存儲空間示意圖如下:

image.png

維護塊點陣圖 (練習3@fs/fs.c)

當我們想要使用 free 的塊時,需要將對應位置0。當我們想釋放 non-free 塊時,應將對應位置1。 練習3 讓我們實現 fs/fs.c 中的 free_blockalloc_block 來實現這些內容。值得註意的是,無論是 free_block 還是 alloc_block 我們一定都會影響 bitmap 占用的block,因此需要調用 flush_block 刷新 bitmap 占用的 block。

void free_block(uint32_t blockno)
void
free_block(uint32_t blockno)
{
	// Blockno zero is the null pointer of block numbers.
	if (blockno == 0)
		panic("attempt to free zero block");
	bitmap[blockno/32] |= 1<<(blockno%32);			//修改bitmap,標記為未使用
}

free_block 中最核心的就是 bitmap[blockno/32] |= 1<<(blockno%32);
blockno/32 得到 blockno 在bitmap 的哪個uint32_t,因為 bitmap 的定義如下:

//bitmap 是一個uint32_t類型的數組
uint32_t *bitmap;       // bitmap blocks mapped in memory

blockno%32 得到 blockno 位於這個uint32_t 的哪一位

int alloc_block(void)

allock_block 根據塊點陣圖尋找一個空閑塊,返回這個空閑塊的塊號,並更新塊點陣圖

int
alloc_block(void)
{
	uint32_t bitmap_start = 2;
	//一個block大小為4096B,bitmap的一項是uint32_t,大小為4B,故一個block夠裝下4096/4項
	uint32_t bitmap_item_num_in_block = 4096/4;

	for (uint32_t blockno = 0; blockno < super->s_nblocks; blockno++) {//遍歷整個bitmap,尋找空閑塊
		if(block_is_free(blockno)){
			bitmap[blockno / 32] &= ~(1 << (blockno % 32));	//將該塊置位"已使用"
			
			//blockno/32 得到該block在bitmap的下標位置
			uint32_t bitmap_item = blockno / 32;
			//blockno % 32 得到block在 bitmap_item 中的第幾個比特位
			uint32_t bitmap_item_bit = blockno % 32;
			//1 << bitmap_item_bit 得到 bitmap_item_bit 的掩碼
			bitmap[bitmap_item] &= ~(1 << bitmap_item_bit);	

			//bitmap_item / bitmap_item_num_in_block 得到 bitmap_item 位於bitmap後的第幾個block
			//然後調用flush_block把該項所在的bitmap塊寫回
			flush_block(diskaddr(bitmap_start + bitmap_item / bitmap_item_num_in_block));
			return blockno;
		}
	}
	//沒有找到空閑block, 則返回-E_NO_SIAK
	return -E_NO_DISK;
}

文件操作(練習4@fs/fs.c)

有了塊緩存和塊點陣圖機制,我們現在有了以塊為單位的讀寫硬碟數據的能力。但是塊中的數據是以文件的形式存儲。jos 在 fs/fs.c 中還有一些函數,用來實現對文件的基本操作。包括:

  • 從塊中解析文件結構
  • 掃描目錄中的文件條目
  • 從根目錄尋找指定文件
    練習4 讓我們實現 file_block_walkfile_get_block,並且通讀 fs/fs.c

file_block_walk : 從文件中的快便宜映射到結構文件中該塊的指針或間接塊
file_get_block : 映射到實際的磁碟塊

static int file_block_walk

根據文件 File f 內的塊號 filebno,尋找該塊對應的硬碟塊號 ppdiskbno
如果 File findirects 沒有初始化,且 alloc 置位,則初始化 indirects
如果輸入合法,這個函數保證一定完成 filebnodiskbno 的映射,但不保證 diskbno 所指的塊已經在塊點陣圖中申請。
申請工作由 file_get_block 負責

static int
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)
{
	uint32_t * indirects;
	if(filebno >= NDIRECT + NINDIRECT)		//檢查 filebno 是否超出 File結構體 能存儲的上限
		return -E_NO_DISK;

	if(filebno < NDIRECT)
	{
		*ppdiskbno = &(f->f_direct[filebno]);	//如果 filebno 位於 直接塊內,直接返回
	}
	else if(f->f_indirect)						// 如果 filebno 位於 非直接塊,且該File已經申請了直接塊
	{
		indirects = diskaddr(f->f_indirect);	//獲取非直接塊所在的地址
		*ppdiskbno = &(indirects[filebno - NDIRECT]);
	}
	else if(alloc)								// 如果 filebno 位於 非直接塊,且該File未申請非直接塊,且alloc置位
	{
		int bn;
		if ((bn = alloc_block()) < 0)
			return bn;		//如果沒有剩餘空閑塊,返回E_NO_DISK
		f->f_indirect = bn;	//初始化 indirect
		flush_block(diskaddr(bn));	//刷新 indirect塊 不太理解為什麼要刷新這個塊,這個塊應該是未修改的
		indirects = diskaddr(bn);
		*ppdiskbno = &(indirects[filebno - NDIRECT]);
	}
	else										// 如果 filebno 位於 非直接塊,且該File未申請非直接塊,且alloc未置位
	{
		return -E_NOT_FOUND;
	}
	return 0;
}

file_get_block

負責讀取文件 File f 的第 filebnoe 個塊,將數據存儲在 blk 所指記憶體地址。

int
file_get_block(struct File *f, uint32_t filebno, char **blk)
{
	uint32_t * pdiskbno;
	int r;
	//先通過 file_block_walk 獲取 filebno 對應的 diskbno
	if((r = file_block_walk(f, filebno, &pdiskbno, true)) < 0)
		return r;
	
	int bn;
	//如果 filebno 對應的塊尚未申請,則通過 alloc_block 進行申請
	if(*pdiskbno == 0)	
	{
		if((bn = alloc_block()) < 0)
			return bn;
		*pdiskbno = bn;
		flush_block(diskaddr(bn));
	}
	//將塊中的數據讀取到 blk 所指空間
	*blk = diskaddr(*pdiskbno);

	return 0;
}

static int dir_lookup

查找 dir 中文件名為 name 的文件,保存在 file 中。

static int
dir_lookup(struct File *dir, const char *name, struct File **file)
{
	int r;
	uint32_t i, j, nblock;
	char *blk;
	struct File *f;

	assert((dir->f_size % BLKSIZE) == 0);
	nblock = dir->f_size / BLKSIZE;			//dir中共有 nblock 個塊
	for (i = 0; i < nblock; i++) {
		if ((r = file_get_block(dir, i, &blk)) < 0)	//從dir中獲取第i個塊
			return r;
		f = (struct File*) blk;						
		for (j = 0; j < BLKFILES; j++)				//遍歷 當前blk中存放的所有 File結構體
			if (strcmp(f[j].f_name, name) == 0) {	//順找是否存在這個name
				*file = &f[j];
				return 0;
			}
	}
	return -E_NOT_FOUND;
}

static int dir_alloc_file

在目錄 dir 下申請一個文件 file

static int
dir_alloc_file(struct File *dir, struct File **file)
{
	int r;
	uint32_t nblock, i, j;
	char *blk;
	struct File *f;

	assert((dir->f_size % BLKSIZE) == 0);
	nblock = dir->f_size / BLKSIZE;
	//遍歷 dir 中所有的塊
	for (i = 0; i < nblock; i++) {
		if ((r = file_get_block(dir, i, &blk)) < 0)
			return r;
		f = (struct File*) blk;
		//遍歷塊中的所有 File
		for (j = 0; j < BLKFILES; j++)
			//如果存在空閑的 File ,則保存至 f 並返回
			if (f[j].f_name[0] == '\0') {	
				*file = &f[j];
				return 0;
			}
	}
	//如果 dir 中已申請的塊沒有空閑空間,則申請塊並更新 f_size
	dir->f_size += BLKSIZE;
	if ((r = file_get_block(dir, i, &blk)) < 0)
		return r;
	//返回新塊 blk 的第一個 File 的地址
	f = (struct File*) blk;
	*file = &f[0];
	return 0;
}

static int walk_path

從根目錄搜索 path 所指的文件,將最終搜索結果的最底層目錄存放在 pdir , 文件則存放在 pf

static int
walk_path(const char *path, struct File **pdir, struct File **pf, char *lastelem)
{
	const char *p;
	char name[MAXNAMELEN];
	struct File *dir, *f;
	int r;

	path = skip_slash(path);	//跳過一開始的 '/'
	f = &super->s_root;			//從 super 中讀取根目錄文件
	dir = 0;					//dir 指向上層目錄,dir應當是個目錄類型
	name[0] = 0;

	if (pdir)
		*pdir = 0;
	*pf = 0;
	while (*path != '\0') {
		dir = f;
		p = path;								//自頂向下地處理 path ,p 指向當前層次目錄名的開頭
		while (*path != '/' && *path != '\0')	//用path指向當前層次目錄名的尾後
			path++;
		if (path - p >= MAXNAMELEN)
			return -E_BAD_PATH;
		memmove(name, p, path - p);				//用p和path切下當前層次目錄名,copy到name
		name[path - p] = '\0';
		path = skip_slash(path);				//更新path,讓path指向下一個層次地目錄名開頭

		if (dir->f_type != FTYPE_DIR)			//檢查dir是否是目錄類型
			return -E_NOT_FOUND;

		if ((r = dir_lookup(dir, name, &f)) < 0) {		//在dir中尋找name
			if (r == -E_NOT_FOUND && *path == '\0') {	//如果沒找到,就將 pdir 設置為最後有效的目錄,
				if (pdir)								//lastelem 設置為最後遍歷到的條目
					*pdir = dir;
				if (lastelem)							
					strcpy(lastelem, name);
				*pf = 0;
			}
			return r;
		}
	}

	if (pdir)
		*pdir = dir;
	*pf = f;
	return 0;
}

int file_create

int file_create(const char *path, struct File **pf)
在 path 處創建文件 pf

int
file_create(const char *path, struct File **pf)
{
	char name[MAXNAMELEN];
	int r;
	struct File *dir, *f;

	if ((r = walk_path(path, &dir, &f, name)) == 0)		//嘗試獲取 path 所指文件的 File結構體
		return -E_FILE_EXISTS;							//如果那個位置已經存在則返回錯誤碼 E_FILE_EXISTS
	if (r != -E_NOT_FOUND || dir == 0)					//如果存在其他問題,則返回對應錯誤碼
		return r;
	if ((r = dir_alloc_file(dir, &f)) < 0)				//申請一個 File結構體,並將它記錄在 dir 之下
		return r;

	strcpy(f->f_name, name);							//file_create 只負責填充 File結構體的 name欄位
	*pf = f;
	file_flush(dir);									//file_create 只改動了磁碟中的 dir 所指文件的 File結構體
	return 0;											//因此只需要刷新該部分
}

file_open

打開 path 所在的文件,其實就是直接調用 walk_path

int
file_open(const char *path, struct File **pf)
{
	return walk_path(path, 0, pf, 0);
}

file_read

將 File 的數據中第 offset 個位元組後 count 個位元組存儲到 buf 中

ssize_t
file_read(struct File *f, void *buf, size_t count, off_t offset)
{
	int r, bn;
	off_t pos;
	char *blk;

	if (offset >= f->f_size)
		return 0;

	count = MIN(count, f->f_size - offset);

	for (pos = offset; pos < offset + count; ) {
		// 獲取 pos 所在的塊
		// pos/BLKSIZE 可以得到需要讀的數據在第幾個塊
		if ((r = file_get_block(f, pos / BLKSIZE, &blk)) < 0)		
			return r;
		// bn為當前塊剩餘全部位元組,或讀取count還未讀完的位元組
		bn = MIN(BLKSIZE - pos % BLKSIZE, offset + count - pos);	
		memmove(buf, blk + pos % BLKSIZE, bn);
		pos += bn;
		buf += bn;
	}

	return count;
}

file_write

int
file_write(struct File *f, const void *buf, size_t count, off_t offset)
{
	int r, bn;
	off_t pos;
	char *blk;

	// Extend file if necessary
	if (offset + count > f->f_size)						//如果文件不夠大,擴展文件大小
		if ((r = file_set_size(f, offset + count)) < 0)
			return r;

	for (pos = offset; pos < offset + count; ) {
		if ((r = file_get_block(f, pos / BLKSIZE, &blk)) < 0)
			return r;
		bn = MIN(BLKSIZE - pos % BLKSIZE, offset + count - pos);
		memmove(blk + pos % BLKSIZE, buf, bn);
		pos += bn;
		buf += bn;
	}

	return count;
}

file_free_block

static int
file_free_block(struct File *f, uint32_t filebno)
{
	int r;
	uint32_t *ptr;

	if ((r = file_block_walk(f, filebno, &ptr, 0)) < 0)		//獲取目標塊所在的位置
		return r;
	if (*ptr) {
		free_block(*ptr);									//釋放掉塊
		*ptr = 0;
	}
	return 0;
}

static void file_truncate_blocks

將 File 的塊數量調整到 newsize 大小

static void
file_truncate_blocks(struct File *f, off_t newsize)
{
	int r;
	uint32_t bno, old_nblocks, new_nblocks;

	old_nblocks = (f->f_size + BLKSIZE - 1) / BLKSIZE;
	new_nblocks = (newsize + BLKSIZE - 1) / BLKSIZE;
	for (bno = new_nblocks; bno < old_nblocks; bno++)
		if ((r = file_free_block(f, bno)) < 0)
			cprintf("warning: file_free_block: %e", r);

	if (new_nblocks <= NDIRECT && f->f_indirect) {
		free_block(f->f_indirect);
		f->f_indirect = 0;
	}
}

int file_set_size

設置 File f 的大小為 newsize

int
file_set_size(struct File *f, off_t newsize)
{
	if (f->f_size > newsize)
		file_truncate_blocks(f, newsize);
	f->f_size = newsize;
	flush_block(f);
	return 0;
}

void file_flush

刷新 f 中的所有塊

void
file_flush(struct File *f)
{
	int i;
	uint32_t *pdiskbno;

	for (i = 0; i < (f->f_size + BLKSIZE - 1) / BLKSIZE; i++) {
		if (file_block_walk(f, i, &pdiskbno, 0) < 0 ||
		    pdiskbno == NULL || *pdiskbno == 0)
			continue;
		flush_block(diskaddr(*pdiskbno));
	}
	flush_block(f);			//刷新f所在的塊
	if (f->f_indirect)
		flush_block(diskaddr(f->f_indirect));	//刷新f_indirect所在的塊
}

void fs_sync(void)

刷新整個根目錄

void
fs_sync(void)
{
	int i;
	for (i = 1; i < super->s_nblocks; i++)
		flush_block(diskaddr(i));
}

文件系統介面

jos 的文件系統是一個用戶級進程,以 RPC 的形式,開放給客戶端調用。

文件系統架構和文件客戶端

文件系統提供 readwritestat 等介面。這些介面可以對所有類型的文件進行操作,這些函數組成了文件操作的客戶端。
如下圖:

image.png

read 會根據文件的具體類型,調用不同的處理函數,其工作內容只是簡單地將讀取任務分發給 與文件描述符類型 對應的設備讀取函數。(file 類型的設備讀取函數是 devfile_readpipe 類型的設備讀取函數是 devpipe_read )如下圖:

image.png

這些設備處理函數的操作方式基本相同:

  1. 將上層介面函數( read 、write 、 stat )的形參,填寫到請求專用的結構體中,
  2. 然後將這個結構體作為參數調用 fsipc ,
  3. fsipc 以 ipc 的形式,將 IPC 請求發送給 fs_server
  4. fsipc 接收 fs_server 的處理結果,返回給設備處理函數。
    文件系統的相關調用的結構如下圖所示:
      Regular env           FS env
   +---------------+   +---------------+
   |      read     |   |   file_read   |
   |   (lib/fd.c)  |   |   (fs/fs.c)   |
...|.......|.......|...|.......^.......|...............
   |       v       |   |       |       | RPC mechanism
   |  devfile_read |   |  serve_read   |
   |  (lib/file.c) |   |  (fs/serv.c)  |
   |       |       |   |       ^       |
   |       v       |   |       |       |
   |     fsipc     |   |     serve     |
   |  (lib/file.c) |   |  (fs/serv.c)  |
   |       |       |   |       ^       |
   |       v       |   |       |       |
   |   ipc_send    |   |   ipc_recv    |
   |       |       |   |       ^       |
   +-------|-------+   +-------|-------+
           |                   |
           +-------------------+

客戶端函數 lab 直接提供了,沒要求編寫代碼,但是。相關文件如下:

path 備註
/lib/fd.c 對各種設備類型的抽象fd的實現,以及高層 file 介面(read)
/lib/file.c fsipc 的實現、file 類型設備的操作(devfile_read)
/lib/pipe.c pipe 的實現、pipe 類型設備的操作(devpipe_read)

文件系統服務(練習5@fs/serv.c fs/fs.c)

文件系統的維護三種數據結構: struct Filestruct Fd struct OpenFile

struct File 的結構我們已經很熟悉了,struct File 會被映射到記憶體中,用於映射磁碟。

struct Fd 位於 /inc/fd.h ,對於每一個 jos 中打開了的文件,文件系統都會為它維護一個 struct Fd,從 FILEVA 0xD000_0000 開始的記憶體被用來存儲 Fd ,定義如下:


struct FdFile {
	int id;
};

struct Fd {
	int fd_dev_id;    //設備類型
	off_t fd_offset;  //文件偏移
	int fd_omode;     //文件模式
	union {
		// File server files
		struct FdFile fd_file;
	};
};

struct OpenFilestruct Filestruct Fd 關聯起來。伺服器維護一個打開所有文件的數組,通過 file ID 索引起來。客戶端使用 file ID 來與伺服器通信。 openfile_lookup 可以將文件的 ID 傳遞給 struct OpenFile

OpenFile 以全局數組的形式定義於 serv.c ,如下:

// Max number of open files in the file system at once
#define MAXOPEN		1024
#define FILEVA		0xD0000000

// initialize to force into data section
struct OpenFile opentab[MAXOPEN] = {
	{ 0, 0, 1, 0 }
};

void serv_init

文件系統在 serve_init從 FILEVA 0xD000_0000 開始的記憶體被用來存儲為 Fd ,定義如下:

void
serve_init(void)
{
	int i;
	uintptr_t va = FILEVA;
	for (i = 0; i < MAXOPEN; i++) {
		opentab[i].o_fileid = i;
		opentab[i].o_fd = (struct Fd*) va;	//沒有使用 fd_alloc ,而是直接分配空間
		va += PGSIZE;						//一個fd占用一頁
	}
}

int openfile_alloc

serve_init 在初始化 opentab 的時候給每一個 struct OpenFileFd 成員分配地址,但這些地址可能還沒有被分配物理頁。
openfile_alloc 負責從 opentab 中申請一個空閑的 struct OpenFile , 同時將對應的 Fd 初始化:

  • 如果沒有映射物理頁,則先申請物理頁。
  • 將頁面內容置零。
int
openfile_alloc(struct OpenFile **o)
{
	int i, r;

	// Find an available open-file table entry
	for (i = 0; i < MAXOPEN; i++) {
		switch (pageref(opentab[i].o_fd)) {
		case 0://如果這個頁還沒有映射,說明該 struct OpenFile 是空閑的,先申請物理頁
			if ((r = sys_page_alloc(0, opentab[i].o_fd, PTE_P|PTE_U|PTE_W)) < 0)
				return r;
			/* fall through */
		case 1:
			opentab[i].o_fileid += MAXOPEN;
			*o = &opentab[i];
			memset(opentab[i].o_fd, 0, PGSIZE);		//整個記憶體頁清零
			return (*o)->o_fileid;
		}
	}
	return -E_MAX_OPEN;
}

int openfile_lookup

獲取 fileid 所指的 struct OpenFile

int
openfile_lookup(envid_t envid, uint32_t fileid, struct OpenFile **po)
{
	struct OpenFile *o;

	o = &opentab[fileid % MAXOPEN];
	if (pageref(o->o_fd) <= 1 || o->o_fileid != fileid)
		return -E_INVAL;
	*po = o;
	return 0;
}

int serve_read(練習 5)

根據 union Fsipc 的參數,讀取指定文件的數據

int
serve_read(envid_t envid, union Fsipc *ipc)
{
	struct Fsreq_read *req = &ipc->read;
	struct Fsret_read *ret = &ipc->readRet;
	int r;
	if (debug)
		cprintf("serve_read %08x %08x %08x\n", envid, req->req_fileid, req->req_n);

	// Lab 5: Your code here:
	int fileid = req->req_fileid;
	int count = req->req_n;

	struct OpenFile * o;
	if((r = openfile_lookup(envid, fileid, &o))<0)
		return r;
	
	if((r = file_read(o->o_file, ret->ret_buf, count, o->o_fd->fd_offset))<0)
		return r;
	count = r;
	o->o_fd->fd_offset += count;
	return count;
}

int serve_write

int
serve_write(envid_t envid, struct Fsreq_write *req)
{
	if (debug)
		cprintf("serve_write %08x %08x %08x\n", envid, req->req_fileid, req->req_n);

	// LAB 5: Your code here.
	// panic("serve_write not implemented");
	int r;
	struct OpenFile * o;
	if((r = openfile_lookup(envid, o->o_fileid, &o)) < 0)
		return r;

	//調用 file_write 將 req 中所指 buf 的數據寫入 File 的 offset 所指記憶體中
	if((r = file_write(o->o_file, req->req_buf, req->req_n, o->o_fd->fd_offset)) < 0)
		return r;

	o->o_fd->fd_offset += r;
	return r;
}

理解open的過程

open調用後,會從客戶端的 fd_table 中取一個空閑槽 fd_table[n],然後由文件系統的 serv_open 來分配物理頁,同時文件西痛的地址空間中也會映射這個 fd 頁面。
image.png



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 歷史考試選擇題:黃花崗起義第一槍誰開的? A宋教仁 B孫中山 C黃興 D徐錫麟,考生選C。 又看第二題:黃花崗起義第二槍誰開的? 考生傻了,就選了個B。 接著看第三題:黃花崗起義中,第三槍誰開的? 考生瘋了,胡亂選了A。 考試出來就去找出捲老師。老師拿出課本說:黃興連開三槍,揭開了黃花崗起義的 ...
  • 在日常開發中,當點擊某控制項時,經常看到一些彈出框,停靠在某系頁面元素的附近,但這些又不是真正的視窗,而是頁面的一部分,那這種功能是如何實現的呢?今天就以一個簡單的小例子,簡述如何在WPF開發中,通過Popup實現滑鼠點擊彈出浮動停靠視窗,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 在我們一些非標的用戶界面中,我們往往需要自定義用戶控制項界面,從而實現不同的內容展示和處理規則,本篇內容介紹使用Winform開發自定義用戶控制項,以及實現相關自定義事件的處理。 ...
  • AI是當今和未來非常重要的技術領域之一,它在各個行業都有廣泛的應用,如醫療保健、金融、教育、製造業等。學習AI可以讓你瞭解和掌握未來技術發展的核心,併為未來的職業發展做好準備。現在有很多開源的Model可以讓我們使用,使用這些開源Model在低成本下,我們也能完成自己的任務。 現在我的需求是給公眾號 ...
  • 在之前的項目中,我們已經使用 OpenVINOCSharp API 部署 PaddleOCR 全系列模型,但隨著PaddleOCRv4版本發佈以及OpenVINO CSharp API版本迭代,上一版本的項目已經不再適用。因此在推出的最新項目中,已經完成了對PaddleOCRv4的匹配,並且採用了最... ...
  • 庫函數和系統調用 庫函數調用 系統調用 在所有的ANSI C編譯器中,C庫函數都是相同的 各個操作系統的系統調用是不同的,這導致程式不可移植 它調用庫函數中的一段程式(或函數) 它調用系統內核的服務 與用戶程式相聯繫 在內核地址空間執行 它的運行時間屬於“用戶時間” 運行時間屬於“系統時間” 屬於過 ...
  • Nginx採用虛擬目錄的方式代理IIS站點 起因 背景 由於IIS出現了某種不可知的問題,H5APP的部署從IIS改為Nginx。 H5APP的Nginx的部署比較簡單,直接修改官方的實例即可 但是之前H5站點中有一個虛擬目錄用於客戶單點登錄認證,所以需要在Nginx中添加對應的虛擬目錄,但是單點認 ...
  • 使用STM32CubeMX軟體配置STM32F407開發板上串口USART1進行DMA傳輸數據,然後實現與實驗STM32CubeMX教程9 USART/UART 非同步通信相同的目標 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...