前言
這個(gè)應(yīng)該是這個(gè)系列的尾聲了,一個(gè)完整的操作系統(tǒng)可能最主要的也就是分成這幾大模塊:進(jìn)程管理、內(nèi)存管理和文件系統(tǒng)。計(jì)算機(jī)以進(jìn)程為基本單位進(jìn)行資源的調(diào)度和分配;而與用戶的交互,基本單位則是文件
生磁盤
文件正是對(duì)生磁盤的抽象
磁盤的組成
一個(gè)磁盤由多個(gè)盤面串聯(lián)而成,而一個(gè)盤面又被分為磁道,磁道又由扇區(qū)組成。
磁盤的訪問單元就是扇區(qū),一個(gè)扇區(qū)為512字節(jié)
磁盤的使用
* CPU向磁盤的控制器發(fā)出一個(gè)指令
* 控制器開始進(jìn)行尋道、旋轉(zhuǎn)和傳輸
* 最后完成后向CPU發(fā)送一個(gè)中斷
也就是向控制器發(fā)送柱面磁頭扇區(qū)等信息,然后等待回應(yīng)
盤塊
盤塊是對(duì)扇區(qū)的抽象
程序負(fù)責(zé)提高盤塊block,而磁盤驅(qū)動(dòng)負(fù)責(zé)從block計(jì)算出cyl,head,sec(CHS),最后傳遞到磁盤控制器上
磁盤訪問時(shí)間 = 寫入控制器時(shí)間 + 尋道時(shí)間 + 旋轉(zhuǎn)時(shí)間 + 傳輸時(shí)間
其中主要的時(shí)間都在尋道時(shí)間和旋轉(zhuǎn)時(shí)間,所以對(duì)扇區(qū)抽象成盤塊就可以一次訪問多個(gè)扇區(qū)來節(jié)省磁盤的訪問時(shí)間
磁盤調(diào)度
既然有多進(jìn)程交替執(zhí)行,就有可能多個(gè)進(jìn)程同時(shí)訪問相同磁盤的情況,就需要一個(gè)請(qǐng)求隊(duì)列來處理所有請(qǐng)求,就會(huì)涉及到調(diào)度算法了
FCFS調(diào)度
FCFS是最公平最直觀的算法,也就是按照隊(duì)列順序來訪問磁盤,但是效率也很低下,磁頭會(huì)在不規(guī)律的磁道長(zhǎng)途奔襲
SSTF調(diào)度
SSTF算法就類似短作業(yè)優(yōu)先算法,先尋找更近距離的磁道,但是SSTF算法可能會(huì)產(chǎn)生饑餓問題,過長(zhǎng)距離的磁道可能一直得不到處理
SCAN調(diào)度
SCAN算法就是SSTF算法的改良版,也就是進(jìn)行SSTF,但是在中途不會(huì)折返去尋找更短距離的磁道,這樣就避免了饑餓問題
C-SCAN調(diào)度(電梯算法)
把掃描限定在一個(gè)方向,當(dāng)訪問到某個(gè)方向的最后一個(gè)磁道時(shí),磁道返回磁盤相反方向磁道的末端,并再次開始掃描。
文件和文件系統(tǒng)
文件是對(duì)磁盤的第三層抽象,扇區(qū)和盤塊分別是前兩層抽象。之所以有文件這層抽象是為了方便用戶的使用,在用戶的眼里,磁盤上的信息都可以看作是字符流。所以文件的抽象九可以看作是字符流到盤塊集合的映射關(guān)系
文件的邏輯結(jié)構(gòu)
從文件到盤塊的映射來看,一般有這幾種組織方式
*
順序文件
記錄是定長(zhǎng)的且按關(guān)鍵字順序排列??梢皂樞虼鎯?chǔ)或以鏈表形勢(shì)存儲(chǔ),在訪問時(shí)需要順序搜索文件。順序文件有以下兩種結(jié)構(gòu):
*
第一種是串結(jié)構(gòu),各記錄之間的順序與關(guān)鍵字無關(guān)。通常的辦法是由時(shí)間來決定,即按存入時(shí)間的先后排列,最先存入的記錄作為第一個(gè)記錄,其次存入的為第二個(gè)記錄,以此類推。
*
第二種是順序結(jié)構(gòu),指文件中所有記錄按關(guān)鍵字順序排列。
在對(duì)記錄進(jìn)行批量操作時(shí),即每次要讀或?qū)懸淮笈涗?,?duì)順序文件的效率是所有邏輯文件中最高的;此外,也只有順序文件才能存儲(chǔ)在磁帶上,并能有效的工作。但順序文件對(duì)查找、修改、增加或刪除單個(gè)記錄的操作比較困難。
*
索引文件
對(duì)于可變長(zhǎng)記錄的文件只能順序查找,系統(tǒng)開銷較大,為此可以建立一張索引表以加快檢索速度,索引表本身是順序文件。在記錄很多或是訪問要求高的文件中,需要引入索引以提供有效的訪問,實(shí)際中,通過索引可以成百上千倍的提高訪問速度。
*
索引順序表
索引順序表是順序和索引兩種組織形勢(shì)的結(jié)合。索引順序文件將順序文件中所有記錄分為若干個(gè)組,為順序文件建立一張索引表,在索引表中為每組中的第一個(gè)記錄建立一個(gè)索引項(xiàng),其中含有該記錄的關(guān)鍵字值和指向該記錄的指針。
在實(shí)際的操作系統(tǒng)實(shí)現(xiàn)中,一般是采用多級(jí)索引
目錄和文件系統(tǒng)
文件系統(tǒng)或者說目錄是對(duì)磁盤的第四個(gè)抽象,也就是抽象了整個(gè)磁盤
操作系統(tǒng)為了實(shí)現(xiàn)文件目錄,引入了稱為文件控制塊的數(shù)據(jù)結(jié)構(gòu)。
文件控制塊。
文件控制塊(FCB)是用來存放控制文件需要的各種信息的數(shù)據(jù)結(jié)構(gòu),以實(shí)現(xiàn)“按名存取”。FCB的有序集合稱為文件目錄,一個(gè)FCB就是一個(gè)文件目錄項(xiàng)。為了創(chuàng)建一個(gè)新文件,系統(tǒng)將分配一個(gè)FCB并存放在文件目錄中,稱為目錄項(xiàng)。
FCB主要包含以下信息:
* 基本信息,如文件名、文件的物理位置、文件的邏輯結(jié)構(gòu)、文件的物理結(jié)構(gòu)等。
* 存取控制信息,如文件的存取權(quán)限等。
* 使用信息,如文件建立時(shí)間、修改時(shí)間等。
文件目錄樹
在多級(jí)目錄下一般對(duì)磁盤就可以抽象為
*
FCB數(shù)組
FCB數(shù)組就是將所有盤塊的FCB信息都集中到一個(gè)數(shù)組中
*
數(shù)據(jù)盤塊集合
在每個(gè)數(shù)據(jù)盤塊里都包含一些目錄項(xiàng)用來找到子目錄,目錄項(xiàng)也就是文件名+對(duì)應(yīng)的FCB的“地址”,也就是去之前的FCB數(shù)組中找到相應(yīng)的FCB
在磁盤進(jìn)行格式化的時(shí)候,會(huì)存放一些信息用來知道一些磁盤信息和找到根目錄
*
inode位圖: 哪些inode空閑,哪些被占用
*
超級(jí)塊:記錄兩個(gè)位圖有多大等信息
*
盤塊位圖: 哪些盤塊是空閑的,硬盤大小不同這個(gè)位圖的大小也不同
文件的實(shí)現(xiàn)
在我之前實(shí)現(xiàn)的FragileOS <https://github.com/dejavudwh/FragileOS>
里文件系統(tǒng)非常簡(jiǎn)陋,基本沒有什么好說的。這其實(shí)也是為什么之前把這個(gè)系列改了一個(gè)方向來結(jié)合的看Linux0.11的代碼。所以來看一下Linux0.11里是怎么使用和實(shí)現(xiàn)文件系統(tǒng)的,
讀取文件
* 這是讀取文件的系統(tǒng)調(diào)用
* 函數(shù)首先對(duì)參數(shù)有效性進(jìn)行判斷
* 之后對(duì)文件的類型進(jìn)行判斷
* 如果是目錄文件或者是常規(guī)文件就執(zhí)行讀取操作 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; }
* 根據(jù)i節(jié)點(diǎn)和文件結(jié)構(gòu),讀取文件中數(shù)據(jù)。
* 首先判斷參數(shù)的有效性
* 之后循環(huán)的調(diào)用bread來讀取數(shù)據(jù)
* 之后復(fù)制chars字節(jié)到用戶緩沖區(qū)buf中
* 最后是修改該i節(jié)點(diǎn)的訪問時(shí)間為當(dāng)前時(shí)間和返回讀取的字節(jié)數(shù) 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; }
文件寫入
* 根據(jù)i節(jié)點(diǎn)和文件結(jié)構(gòu)信息,將用戶數(shù)據(jù)寫入文件中
* 首先確定數(shù)據(jù)寫入文件的位置
* 然后算出對(duì)應(yīng)的盤塊
* 然后用戶緩沖區(qū)buf中復(fù)制c個(gè)字節(jié)到告訴緩沖塊中p指向的開始位置處
* 最后是修改該i節(jié)點(diǎn)的訪問時(shí)間為當(dāng)前時(shí)間和返回讀取的字節(jié)數(shù) 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); }
文件目錄的實(shí)現(xiàn)
打開創(chuàng)建文件
* 首先對(duì)參數(shù)進(jìn)行處理,然后搜索進(jìn)程結(jié)構(gòu)中文件結(jié)構(gòu)指針數(shù)組一個(gè)空閑的文件句柄
* 接著為打開文件在文件表中尋找一個(gè)空閑結(jié)構(gòu)項(xiàng)
* 然后調(diào)用函數(shù)open_namei()執(zhí)行打開操作,若返回值小于0,則說明出錯(cuò),就釋放剛申請(qǐng)到的文件結(jié)構(gòu)
* 然后為不同的文件類型做一些特殊的處理
* 最后初始化打開文件的文件結(jié)構(gòu),然后返回文件句柄 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); }
解析目錄
* 先根據(jù)當(dāng)前路徑的第一個(gè)字符來判斷當(dāng)前路徑是絕對(duì)路徑還是相對(duì)路徑
* 然后進(jìn)循環(huán)處理過程,分割每個(gè)目錄名
* 得到這個(gè)目錄名后,調(diào)用查找目錄項(xiàng)函數(shù)find_entry()在當(dāng)前處理的目錄中尋找指定名稱的目錄
* 如果找到這個(gè)目錄,就設(shè)置一些信息,然后繼續(xù)以該目錄項(xiàng)為當(dāng)前目錄繼續(xù)循環(huán)處理路徑名中的下一目錄名部分(或文件名) 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; } }
熱門工具 換一換