我是如何學習寫一個操作系統(九):文件系統


前言

這個應該是這個系列的尾聲了,一個完整的操作系統可能最主要的也就是分成這幾大模塊:進程管理、內存管理和文件系統。計算機以進程為基本單位進行資源的調度和分配;而與用戶的交互,基本單位則是文件

生磁盤

文件正是對生磁盤的抽象

磁盤的組成

一個磁盤由多個盤面串聯而成,而一個盤面又被分為磁道,磁道又由扇區組成。

磁盤的訪問單元就是扇區,一個扇區為512字節

磁盤的使用

  • CPU向磁盤的控制器發出一個指令
  • 控制器開始進行尋道、旋轉和傳輸
  • 最后完成后向CPU發送一個中斷

也就是向控制器發送柱面磁頭扇區等信息,然后等待回應

盤塊

盤塊是對扇區的抽象

程序負責提高盤塊block,而磁盤驅動負責從block計算出cyl,head,sec(CHS),最后傳遞到磁盤控制器上

磁盤訪問時間 = 寫入控制器時間 + 尋道時間 + 旋轉時間 + 傳輸時間

其中主要的時間都在尋道時間和旋轉時間,所以對扇區抽象成盤塊就可以一次訪問多個扇區來節省磁盤的訪問時間

磁盤調度

既然有多進程交替執行,就有可能多個進程同時訪問相同磁盤的情況,就需要一個請求隊列來處理所有請求,就會涉及到調度算法了

FCFS調度

FCFS是最公平最直觀的算法,也就是按照隊列順序來訪問磁盤,但是效率也很低下,磁頭會在不規律的磁道長途奔襲

SSTF調度

SSTF算法就類似短作業優先算法,先尋找更近距離的磁道,但是SSTF算法可能會產生飢餓問題,過長距離的磁道可能一直得不到處理

SCAN調度

SCAN算法就是SSTF算法的改良版,也就是進行SSTF,但是在中途不會折返去尋找更短距離的磁道,這樣就避免了飢餓問題

C-SCAN調度(電梯算法)

把掃描限定在一個方向,當訪問到某個方向的最后一個磁道時,磁道返回磁盤相反方向磁道的末端,並再次開始掃描。

文件和文件系統

文件是對磁盤的第三層抽象,扇區和盤塊分別是前兩層抽象。之所以有文件這層抽象是為了方便用戶的使用,在用戶的眼里,磁盤上的信息都可以看作是字符流。所以文件的抽象九可以看作是字符流到盤塊集合的映射關系

文件的邏輯結構

從文件到盤塊的映射來看,一般有這幾種組織方式

  • 順序文件

    記錄是定長的且按關鍵字順序排列。可以順序存儲或以鏈表形勢存儲,在訪問時需要順序搜索文件。順序文件有以下兩種結構:

    1. 第一種是串結構,各記錄之間的順序與關鍵字無關。通常的辦法是由時間來決定,即按存入時間的先后排列,最先存入的記錄作為第一個記錄,其次存入的為第二個記錄,以此類推。

    2. 第二種是順序結構,指文件中所有記錄按關鍵字順序排列。
      在對記錄進行批量操作時,即每次要讀或寫一大批記錄,對順序文件的效率是所有邏輯文件中最高的;此外,也只有順序文件才能存儲在磁帶上,並能有效的工作。但順序文件對查找、修改、增加或刪除單個記錄的操作比較困難。

  • 索引文件

    對於可變長記錄的文件只能順序查找,系統開銷較大,為此可以建立一張索引表以加快檢索速度,索引表本身是順序文件。在記錄很多或是訪問要求高的文件中,需要引入索引以提供有效的訪問,實際中,通過索引可以成百上千倍的提高訪問速度。

  • 索引順序表

    索引順序表是順序和索引兩種組織形勢的結合。索引順序文件將順序文件中所有記錄分為若干個組,為順序文件建立一張索引表,在索引表中為每組中的第一個記錄建立一個索引項,其中含有該記錄的關鍵字值和指向該記錄的指針。

在實際的操作系統實現中,一般是采用多級索引

目錄和文件系統

文件系統或者說目錄是對磁盤的第四個抽象,也就是抽象了整個磁盤

操作系統為了實現文件目錄,引入了稱為文件控制塊的數據結構。

文件控制塊。

文件控制塊(FCB)是用來存放控制文件需要的各種信息的數據結構,以實現“按名存取”。FCB的有序集合稱為文件目錄,一個FCB就是一個文件目錄項。為了創建一個新文件,系統將分配一個FCB並存放在文件目錄中,稱為目錄項。

FCB主要包含以下信息:

  1. 基本信息,如文件名、文件的物理位置、文件的邏輯結構、文件的物理結構等。
  2. 存取控制信息,如文件的存取權限等。
  3. 使用信息,如文件建立時間、修改時間等。

文件目錄樹

在多級目錄下一般對磁盤就可以抽象為

  • FCB數組

    FCB數組就是將所有盤塊的FCB信息都集中到一個數組中

  • 數據盤塊集合

    在每個數據盤塊里都包含一些目錄項用來找到子目錄,目錄項也就是文件名+對應的FCB的“地址”,也就是去之前的FCB數組中找到相應的FCB

在磁盤進行格式化的時候,會存放一些信息用來知道一些磁盤信息和找到根目錄

fx.png

  • inode位圖: 哪些inode空閑,哪些被占用

  • 超級塊:記錄兩個位圖有多大等信息

  • 盤塊位圖: 哪些盤塊是空閑的,硬盤大小不同這個位圖的大小也不同

文件的實現

在我之前實現的FragileOS里文件系統非常簡陋,基本沒有什么好說的。這其實也是為什么之前把這個系列改了一個方向來結合的看Linux0.11的代碼。所以來看一下Linux0.11里是怎么使用和實現文件系統的,

讀取文件

  • 這是讀取文件的系統調用
  • 函數首先對參數有效性進行判斷
  • 之后對文件的類型進行判斷
  • 如果是目錄文件或者是常規文件就執行讀取操作
int sys_read(unsigned int fd,char * buf,int count)
{
	struct file * file;
	struct m_inode * inode;

	if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))
		return -EINVAL;
	if (!count)
		return 0;
	verify_area(buf,count);
	inode = file->f_inode;
	if (inode->i_pipe)
		return (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;
	if (S_ISCHR(inode->i_mode))
		return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);
	if (S_ISBLK(inode->i_mode))
		return block_read(inode->i_zone[0],&file->f_pos,buf,count);
	if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) {
		if (count+file->f_pos > inode->i_size)
			count = inode->i_size - file->f_pos;
		if (count<=0)
			return 0;
		return file_read(inode,file,buf,count);
	}
	printk("(Read)inode->i_mode=%06o\n\r",inode->i_mode);
	return -EINVAL;
}
  • 根據i節點和文件結構,讀取文件中數據。
  • 首先判斷參數的有效性
  • 之后循環的調用bread來讀取數據
  • 之后復制chars字節到用戶緩沖區buf中
  • 最后是修改該i節點的訪問時間為當前時間和返回讀取的字節數
int file_read(struct m_inode * inode, struct file * filp, char * buf, int count)
{
	int left,chars,nr;
	struct buffer_head * bh;

	if ((left=count)<=0)
		return 0;
	while (left) {
		if ((nr = bmap(inode,(filp->f_pos)/BLOCK_SIZE))) {
			if (!(bh=bread(inode->i_dev,nr)))
				break;
		} else
			bh = NULL;
		nr = filp->f_pos % BLOCK_SIZE;
		chars = MIN( BLOCK_SIZE-nr , left );
		filp->f_pos += chars;
		left -= chars;
		if (bh) {
			char * p = nr + bh->b_data;
			while (chars-->0)
				put_fs_byte(*(p++),buf++);
			brelse(bh);
		} else {
			while (chars-->0)
				put_fs_byte(0,buf++);
		}
	}
	inode->i_atime = CURRENT_TIME;
	return (count-left)?(count-left):-ERROR;
}

文件寫入

  • 根據i節點和文件結構信息,將用戶數據寫入文件中
  • 首先確定數據寫入文件的位置
  • 然后算出對應的盤塊
  • 然后用戶緩沖區buf中復制c個字節到告訴緩沖塊中p指向的開始位置處
  • 最后是修改該i節點的訪問時間為當前時間和返回讀取的字節數
int file_write(struct m_inode * inode, struct file * filp, char * buf, int count)
{
	off_t pos;
	int block,c;
	struct buffer_head * bh;
	char * p;
	int i=0;

/*
 * ok, append may not work when many processes are writing at the same time
 * but so what. That way leads to madness anyway.
 */
	if (filp->f_flags & O_APPEND)
		pos = inode->i_size;
	else
		pos = filp->f_pos;
	while (i<count) {
		if (!(block = create_block(inode,pos/BLOCK_SIZE)))
			break;
		if (!(bh=bread(inode->i_dev,block)))
			break;
		c = pos % BLOCK_SIZE;
		p = c + bh->b_data;
		bh->b_dirt = 1;
		c = BLOCK_SIZE-c;
		if (c > count-i) c = count-i;
		pos += c;
		if (pos > inode->i_size) {
			inode->i_size = pos;
			inode->i_dirt = 1;
		}
		i += c;
		while (c-->0)
			*(p++) = get_fs_byte(buf++);
		brelse(bh);
	}
	inode->i_mtime = CURRENT_TIME;
	if (!(filp->f_flags & O_APPEND)) {
		filp->f_pos = pos;
		inode->i_ctime = CURRENT_TIME;
	}
	return (i?i:-1);
}

文件目錄的實現

打開創建文件

  • 首先對參數進行處理,然后搜索進程結構中文件結構指針數組一個空閑的文件句柄
  • 接着為打開文件在文件表中尋找一個空閑結構項
  • 然后調用函數open_namei()執行打開操作,若返回值小於0,則說明出錯,就釋放剛申請到的文件結構
  • 然后為不同的文件類型做一些特殊的處理
  • 最后初始化打開文件的文件結構,然后返回文件句柄
int sys_open(const char * filename,int flag,int mode)
{
	struct m_inode * inode;
	struct file * f;
	int i,fd;

	mode &= 0777 & ~current->umask;
	for(fd=0 ; fd<NR_OPEN ; fd++)
		if (!current->filp[fd])
			break;
	if (fd>=NR_OPEN)
		return -EINVAL;
	current->close_on_exec &= ~(1<<fd);
	f=0+file_table;
	for (i=0 ; i<NR_FILE ; i++,f++)
		if (!f->f_count) break;
	if (i>=NR_FILE)
		return -EINVAL;
	(current->filp[fd]=f)->f_count++;
	if ((i=open_namei(filename,flag,mode,&inode))<0) {
		current->filp[fd]=NULL;
		f->f_count=0;
		return i;
	}
/* ttys are somewhat special (ttyxx major==4, tty major==5) */
	if (S_ISCHR(inode->i_mode)) {
		if (MAJOR(inode->i_zone[0])==4) {
			if (current->leader && current->tty<0) {
				current->tty = MINOR(inode->i_zone[0]);
				tty_table[current->tty].pgrp = current->pgrp;
			}
		} else if (MAJOR(inode->i_zone[0])==5)
			if (current->tty<0) {
				iput(inode);
				current->filp[fd]=NULL;
				f->f_count=0;
				return -EPERM;
			}
	}
/* Likewise with block-devices: check for floppy_change */
	if (S_ISBLK(inode->i_mode))
		check_disk_change(inode->i_zone[0]);
	f->f_mode = inode->i_mode;
	f->f_flags = flag;
	f->f_count = 1;
	f->f_inode = inode;
	f->f_pos = 0;
	return (fd);
}

解析目錄

  • 先根據當前路徑的第一個字符來判斷當前路徑是絕對路徑還是相對路徑
  • 然后進循環處理過程,分割每個目錄名
  • 得到這個目錄名后,調用查找目錄項函數find_entry()在當前處理的目錄中尋找指定名稱的目錄
  • 如果找到這個目錄,就設置一些信息,然后繼續以該目錄項為當前目錄繼續循環處理路徑名中的下一目錄名部分(或文件名)
static struct m_inode * get_dir(const char * pathname)
{
	char c;
	const char * thisname;
	struct m_inode * inode;
	struct buffer_head * bh;
	int namelen,inr,idev;
	struct dir_entry * de;

	if (!current->root || !current->root->i_count)
		panic("No root inode");
	if (!current->pwd || !current->pwd->i_count)
		panic("No cwd inode");
	if ((c=get_fs_byte(pathname))=='/') {
		inode = current->root;
		pathname++;
	} else if (c)
		inode = current->pwd;
	else
		return NULL;	/* empty name is bad */
	inode->i_count++;
	while (1) {
		thisname = pathname;
		if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) {
			iput(inode);
			return NULL;
		}
		for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)
			/* nothing */ ;
		if (!c)
			return inode;
		if (!(bh = find_entry(&inode,thisname,namelen,&de))) {
			iput(inode);
			return NULL;
		}
		inr = de->inode;
		idev = inode->i_dev;
		brelse(bh);
		iput(inode);
		if (!(inode = iget(idev,inr)))
			return NULL;
	}
}


免責聲明!

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



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