自己动手写文件系统
這一課中,將創建一個磁盤分區,并在她上面建立文件系統。文章看起來比較長,但是過程比較簡單。
大家都知道,硬盤最小的管理單位是扇區,每個扇區512字節。有兩種方式定位訪問磁盤。一種是CHS模式(Cylinder, Head,?Sector),她反映了磁盤的物理結構。扇區(Sector)是磁盤可以直接訪問的最小數據單位(即一個塊)。柱面(Cylinder)也成為磁軌,通常一個1.44M軟盤每柱面有18扇區,而硬盤每柱面含有64扇區。磁頭(Head)表示有多少個盤面(通常每個盤面使用兩個磁頭,正反盤面各一個)。關于CHS相關的資料網絡上可以搜索出很多,這里不贅述。
例如我建立了一個100M字節的磁盤映象文件,在bocsh可以這樣定義它的結構:
ata0-master: type=disk, path="minix.img", mode=flat, cylinders=200, heads=16, spt=63
計算:16 * 64 * 200 * 512 =?100 M(512是每扇區字節數)
很明顯,在CHS模式下,磁盤容量最大可表示為??柱面數?*?每柱面扇區數?*?磁頭數?* 512.?注意:柱面和磁頭從0開始計數,而扇區數則從1開始。不要問我為什么。
另一種硬盤訪問模式叫LBA(Linear Block Addressing:線性塊尋址模式),它是物理硬盤的邏輯尋址方式。和線性內存尋址一樣,LBA用線性地址來定位扇區(這樣,柱面和磁頭就不會用到了)。這種方式非常簡單,但是驅動使用的是CHS模式,所以需要進行轉換(LBA也是從0開始計數的)。
LBA =(C-Cs)*PH*PS+(H-Hs)*PS+(S-Ss)
磁盤大小LBA值?= C *?H *?S - 1
方向計算(由LBA獲取CHS:下面的公式好像有問題,讀者最好在網上找到正確的資料):
C = LBA / (heads per cylinder * sectors per track)
temp = LBA % (heads per cylinder * sectors per track)
H = temp / sectors per track
S = temp % sectors per track + 1
CHS跟LBA之間,其實相當于seg:offset與線形地址的關系。不同點在于:
如果你使用vmware建立一個虛擬磁盤,可以在啟動時進入bios看到該虛擬磁盤的?CHS?和?LBA?信息。
下面三個demo對應的源程序分別為dpt(分區表disk partition table),fs和root目錄。
我們可以定義一個數據結構:
07/dpt/hd.c
struct HD_PARAM {
??? unsigned int cyl;
??? unsigned int head;
??? unsigned int sect;
} HD0 = {208, 16, 63};????????//?定義一個104M磁盤
?
由于skelix使用LBA尋址,所以需要把LBA地址轉換成CHS地址:
??? unsigned int cyl = lba / (HD0.head * HD0.sect);
??? unsigned int head = (lba % (HD0.head * HD0.sect)) / HD0.sect;
??? unsigned int sect = (lba % (HD0.head * HD0.sect)) % HD0.sect + 1;
現在已經得到chs地址了,我們將通過?0x1F0-0x1F7端口來訪問硬盤。
(這些繁文縟節一點也不好玩,但是我又不得不講)
07/include/hd.h
#define HD_PORT_DATA??? ???? 0x1f0
#define HD_PORT_ERROR??? ??? 0x1f1
#define HD_PORT_SECT_COUNT?? 0x1f2
#define HD_PORT_SECT_NUM???? 0x1f3
#define HD_PORT_CYL_LOW??? ? 0x1f4
#define HD_PORT_CYL_HIGH???? 0x1f5
#define HD_PORT_DRV_HEAD???? 0x1f6
#define HD_PORT_STATUS??? ?? 0x1f7
#define HD_PORT_COMMAND??? ? 0x1f7
?
另人恐慌,不過可以很清楚的來個分組。我們從0x1f0端口讀取數據,
如果發生了錯誤就從0x1f1讀取錯誤碼,
0x1f2和0x1f3端口設置扇區號,
0x1f4和0x1f5端口設置柱面號,
0x1f6端口設置磁頭號。
端口0x1f7用于讀硬盤狀態或者寫操作命令。
寫0x1f7端口常用的命令字如下:
#define HD_READ??? ??? ??? ? 0x20????????//?讀硬盤
#define HD_WRITE??? ??? ???? 0x30????????//?寫硬盤
通過上面定義,我們可以粗略的認為通過以下幾個步驟訪問硬盤,
(為了簡化步驟,暫時不做一些錯誤檢測)
下面這個函數hd_rw就是用來操作硬盤的接口。
07/hd.c
??? while ((inb(HD_PORT_STATUS)&0xc0)!=0x40)
??? //?等待硬盤狀態,直到可以寫或讀為止,狀態字節說明如下:
| Bit 7 | 控制器忙 |
| Bit 6 | 驅動器準備就緒 |
| Bit 5 | 寫出錯 |
| Bit 4 | 尋道結束 |
| Bit 3 | 數據請求復位 |
| Bit 2 | ECC效驗錯誤 |
| Bit 1 | 硬盤已收到索引 |
| Bit 0 | 命令執行錯誤 |
??? outb(sects_to_access, HD_PORT_SECT_COUNT); //?準備讀或寫多少個扇區
?
??? outb(sect, HD_PORT_SECT_NUM);????????????? //?發送chs地址
??? outb(cyl, HD_PORT_CYL_LOW);
??? outb(cyl>>8, HD_PORT_CYL_HIGH);
??? outb(0xa0|head, HD_PORT_DRV_HEAD);???????? // a0是第一塊硬盤
| Bits 7-5 | 必須是?101b |
| Bit 4 | HD0(=0第一塊硬盤), HD1(=1第二塊硬盤) |
| Bits 3-0 | 磁頭號 |
?
??? outb(com, HD_PORT_COMMAND);????????????????//?硬盤操作命令
| HD_READ=0x20 | 如果不成功會反復讀 |
| HD_WRITE=0x30 | 如果不成功會反復寫 |
??? if (com == HD_READ)
??? ??? insl(HD_PORT_DATA, buf, sects_to_access<<7);
??? else if (com == HD_WRITE)
??? ??? outsl(buf, sects_to_access<<7, HD_PORT_DATA);
????//?說明:insl和outsl是從io端口讀寫一串數據的宏匯編指令,
????//?這里使用的是pio方式,mdma和udma不作討論
??? while ((inb(HD_PORT_STATUS)&0x80));????????//?等待完成
事實上,這只是最簡單的處理流程,連錯誤檢測都沒有。雖然是pio方式,
仍然可以使用中斷,以避免上面的while循環等待,而減少內核浪費的時間。
不過skelix不準備做這么復雜。
?
磁盤分區表(disk partitiontable,以下簡稱dpt)
現在我們有能力訪問硬盤的扇區了,需要把這些扇區管理起來。硬盤的第一個扇區必須包含硬盤分區表。這個分區表從第一個扇區的0x1be偏移開始,長度是64字節。最多可以包含4個分區(本文不考慮邏輯分區,都使用主分區)。這4個分區的相對偏移分別為0x1be, 0x1ce, 0x1de, 0x1fe,第一個扇區的最后兩個字節必須是0xaa55。
每個分區項的格式如下:
| Byte? 0 | 引導標識 |
| Bytes 1-3 | CHS?信息 |
| Byte? 4 | 分區類型 |
| Bytes 5-7 | CHS?信息 |
| Bytes 8-11 | 該分區的起始扇區號 |
| Bytes 12-16 | 扇區數量 |
?
第0個字節是引導標識,如果值為0x80標識可引導。對于一塊硬盤來說,只有一個分區是可以引導的。第4個字節定義分區類型,例如FAT32, NTFS, ext2等。有一篇文章http://www.osdever.net/documents/partitiontypes.php?the_id=35,里面定義了常見的分區類型。
從上面的表可以看到dpt項有兩種方式定位扇區,一種是通過字節1~3和字節5~7中的CHS信息,另一種是字節8~16的LBA信息。隨便用哪一種都是一樣的,在本文中使用LBA方式,所以我不準備解釋字節1~3和字節5~7的具體格式了。
現在我們來建立分區表:
07/dpt/hd.c
?
static void
setup_DPT(void) {
??? unsigned char sect[512] = {0};
??? sect[0x1be] = 0x80;?????????????//?第一個分區可引導
??? sect[0x1be + 0x04] = FST_FS;????//?自定義的數據分區類型
??? *(unsigned long *)§[0x1be + 0x08] = 1;
??? *(unsigned long *)§[0x1be + 0x0c] = 85*1024*2; /* 85MB */
??? sect[0x1ce + 0x04] = FST_SW;????//?自定義的交換分區類型,后續課程使用
??? *(unsigned long *)§[0x1ce + 0x08] = 85*1024*2+1;
??? *(unsigned long *)§[0x1ce + 0x0c] = 16*1024*2; /* 16MB */
??? sect[0x1fe] = 0x55;
??? sect[0x1ff] = 0xaa;
??? hd_rw(0, HD_WRITE, 1, sect);????//?寫入磁盤
????//?寫到扇區0,扇區數為1,sect是寫入緩沖
}
現在,我們在啟動的過程中把分區表信息打印出來:
07/dpt/hd.c
void
verify_DPT(void) {
??? unsigned char sect[512];
??? unsigned i = 0;
??? unsigned int *q = (unsigned int *)(HD0_ADDR);
????//?變量q存放讀出的分區表(起始扇區號和扇區數量)數組
??? hd_rw(0, HD_READ, 1, sect);
??? if ((sect[0x1fe]==0x55) && (sect[0x1ff]==0xaa)) {
??????? unsigned char *p = §[0x1be];
??????? char *s;
??? ??? kprintf(KPL_DUMP, "?? | Bootable | Type????? | Start Sector | Capacity \n");
??????? for (i=0; i<4; ++i) {
??? ??? ??? kprintf(KPL_DUMP, " %d ", i);
??????????? if (p[0x04] == 0x00) {
??????????????? kprintf(KPL_DUMP, "| Empty\n");
??? ??? ??? ??? p += 16;
??? ??? ??? ??? q += 2;
??? ??? ??? ??? continue;
??? ??? ??? }
??? ??? ??? if (p[0x00] == 0x80)
??? ??? ??? ??? s = "| Yes????? ";
??? ??? ??? else
??? ??? ??? ??? s = "| No?????? ";
??????????? kprintf(KPL_DUMP, s);
??? ??? ??? /* system indicator at offset 0x04 */
??? ??? ??? if (p[0x04] == FST_FS) {
??? ??? ??? ??? kprintf(KPL_DUMP, "| Skelix FS ");
??? ??? ??? } else if (p[0x04] == FST_SW) {
??? ??? ??? ??? kprintf(KPL_DUMP, "| Skelix SW ");
??? ??? ??? } else
??? ??? ??? ??? kprintf(KPL_DUMP, "| Unknown?? ", *p);
??????????? /* starting sector number */
??? ??? ??? *q++ = *(unsigned long *)&p[0x08];
??? ??? ??? kprintf(KPL_DUMP, "| 0x%x?? ", *(unsigned long*)&p[0x08]);
??? ??? ??? /* capacity */
??? ??? ??? *q++ = *(unsigned long*)&p[0x0c];
??? ??? ??? kprintf(KPL_DUMP, "| %dM\n", (*(unsigned long*)&p[0x0c]*512)>>20);
??????????? //?保存到內存中,32字節偏移,32字節長度
??????????? p += 16;
??? ??? }
??? } else {
??? ??? kprintf(KPL_DUMP, "No bootable DPT found on HD0\n");
??? ??? kprintf(KPL_DUMP, "Creating DPT on HD0 automaticly\n");
??? ??? kprintf(KPL_DUMP, "Creating file system whatever you want it or not!!\n");
??? ??? setup_DPT();
??? ??? verify_DPT();
??? }
}
在我們編譯觀察結果之前,還需要修改任務函數task1_run?和?task2_run,因為它們會滾動屏幕把我們想要的結果覆蓋掉。
07/init.c
void
do_task1(void) {
??? __asm__ ("incb 0xb8000+160*24+2");
}
void
do_task2(void) {
??? __asm__ ("incb 0xb8000+160*24+4");
}
按例,還得改改Makefile,加入?hd.o?到?KERNEL_OBJS,?并在sti()?之前就調用?verify_DPT()函數:
07/dpt/Makefile
KERNEL_OBJS= load.o init.o isr.o timer.o libcc.o scr.o kb.o task.o kprintf.o hd.o exceptions.o
?
07/dpt/init.c
??? __asm__ ("ltrw??? %%ax\n\t"::"a"(TSS_SEL));
??? __asm__ ("lldt??? %%ax\n\t"::"a"(LDT_SEL));
??? kprintf(KPL_DUMP, "Verifing disk partition table....\n");
??? verify_DPT();?? ?? /* <<<<< Here it is */
??? sti();???????????? //?任務調度可以進行了
?
編譯運行一把,OK!(最好使用一個未分區的磁盤映象來測試)
文件系統
分區已經建立,下一步就是組織各個分區上的文件系統。雖然我們可以做到訪問扇區了,但是對于訪問文件卻是不方便的。需要做一些結構化的工作,為此定義了一個表示文件的數據結構:
07/fs/include/fs.h
?
#define FT_NML??? 1???????????? /* normal file */
#define FT_DIR??? 2
struct INODE {????????????????? /*?存放在硬盤里面,在inode區?*/
??? unsigned int i_mode;??? ??? /* file mode */
??? unsigned int i_size;??? ??? /* size in bytes */
??? unsigned int i_block[8];
};
*nix?用戶可能對inode比較敏感。現在我來一一解釋這個數據結構中的域,i_mode定義文件類型。FT_NML?表示這是一個普通文件,FT_DIR?表示是一個目錄。i_size?是文件大小,對于文件夾則是另外意思,后面將會講到。i_block的前6個整形表示文件的前6個扇區號,第七個表示二級指針扇區(即它指向一個扇區,這個扇區里面存放文件后續部分使用扇區號),由?512 / 4 = 128?扇區,表示文件接下來使用的128個扇區。128 * 512 = 64K。i_block數組的最后一個表示三級指針,最大可以表示?(512 / 4) * (512 / 4) * 512 = 8MB字節。所以這個i_block數組最大可以表示?3k + 64K +?8M文件的大小,雖然比較小,但是對于我們的85M?分區來說已經足夠了。最重要的是,它比較簡單。舉例來說把,這個文件節點使用了如下扇區序列:?13, 7, 9, 16, 257, 57, 3, ....., 35, 33, ....., 55, ......., 128, ....., 81.
對于目錄(也是一種文件)來說,它以類似數組的形式組織:?{{file_name, file_inode}, {file_name, file_inode}, {file_name, file_inode}, },定義如下:
07/fs/include/fs.h
#define MAX_NAME_LEN 11
struct DIR_ENTRY {????????????/?存放在硬盤里面,在data區?*/
??? char de_name[MAX_NAME_LEN];
??? int de_inode;
};
操作系統中的所有文件都有一個獨一無二的節點編號,如果有了這個節點號,就可以找到對應的文件。最開始的兩個文件永遠是"."?和?"..",表示當前目錄和上級目錄,如果我們切換到下級目錄,可以通過".."來回到上一級。"/"表示最上級目錄,它沒有父節點。
舉例來說,我們需要定位到?/usr/doc/fvwm/TODO?文件,首先我們找到"/"文件,然后搜索這個文件項下面的doc文件,因為"/"是個目錄,所以先得到"/"目錄的節點編號,然后搜索指向的節點表。然后再搜索到fvwm目錄,并且在這個目錄的節點表中搜索到"TODO"這個文件,并通過"TODO"的節點編號來定位節點這個文件的節點數據結構。最后就可以訪問i_block數組了,也就是可以訪問這個文件了。怎么自己看的都昏菜了,s**t!
還有兩個問題,一個是需要指定從哪里搜索節點號,我們在磁盤中組織所有節點為數組,并由節點號來索引節點數組。另一個問題是,"/"沒有父節點,需要知道"/"存放在什么地方,這個也好辦,就放在節點數組的第一項好了。
文件名聲明成12字節,這樣每個節點將占用16字節(另4字節是節點編號),這樣方便磁盤IO之后的一些操作。當磁盤使用一段時間后,有的節點使用了,有的節點沒有使用,那怎么知道呢?一種方法是建立一個位圖表,每個位表示inode數組中的一項。
07/fs/include/fs.h
struct SUPER_BLOCK {
??? unsigned char sb_magic;????/*?分區類型?FST_FS?或?FST_SW *'
????
??? unsigned int sb_start;???? /* DPT 0x08:?起始扇區?*/
??? unsigned int sb_blocks;??? /* DPT 0x0c:?扇區數量?*/
??? unsigned int sb_dmap_blks;
??? unsigned int sb_imap_blks;
??? unsigned int sb_inode_blks;
};
?
這個超級塊的數據結構用來管理各個分區。例如,下面是一個磁盤分區:
?________________________________________________________
|??|?\\\\?| ?|?\\\\?|??|????? ?data????????? |?
?--------------------------------------------------------
每個分區的第一個扇區(藍色)是boot secotr,我不打算使用它,一個扇區大小。
第二個扇區(綠色)是超級塊(super block,以下簡稱sb),一個扇區大小。
黑色是dmap,336個扇區大小。
紅色是imap,一個扇區大小。
灰色是inodes,將占有342個block,即342 * 8?個扇區大小。
為了管理這個85M分區,我們額外花了?1.5M?的空間。
?
在verify_fs()函數中定義了超級塊(局部變量)sb,為了方便訪問定義了一些宏,獲取相對整個硬盤的絕對地址(LBA):
07/fs/incude/fs.h
#define ABS_BOOT_BLK(sb)??? ??? ((sb).sb_start)
#define ABS_SUPER_BLK(sb)??? ??? ((ABS_BOOT_BLK(sb))+1)
#define ABS_DMAP_BLK(sb)??? ??? ((ABS_SUPER_BLK(sb))+1)
#define ABS_IMAP_BLK(sb)??? ??? ((ABS_DMAP_BLK(sb))+(sb).sb_dmap_blks)
#define ABS_INODE_BLK(sb)??? ??? ((ABS_IMAP_BLK(sb))+(sb).sb_imap_blks)
#define ABS_DATA_BLK(sb)??? ??? ((ABS_INODE_BLK(sb))+INODE_BLKS)
?
說明:dmap(data map)存放的是扇區使用位圖
????? imap(inode map)存放inode使用位圖。
????? inodes存放節點表。
為了方便,一些位的操作函數如下:
07/fs/fs.c
void
setb(void *s, unsigned int i) {
??? unsigned char *v = s;
??? v += i>>3;????????????// i的單位由位轉換成字節
??? *v |= 1<<(7-(i%8));
}
void
clrb(void *s, unsigned int i) {
??? unsigned char *v = s;
??? v += i>>3;
??? *v &= ~(1<<(7-(i%8)));
}
int
testb(void *s, unsigned int i) {
??? unsigned char *v = s;
??? v += i>>3;
??? return (*v&(1<<(7-(i%8)))) !=0;
}
例如,設置緩沖區sect的1796位,可以使用setb(sect, 1796)
init.c在調用verify_DPT();創建分區表后,緊接著調用verify_fs();創建文件系統:
07/fs/fs.c
void
verify_fs(void) {
??? unsigned int *q = (unsigned int *)(HD0_ADDR);
??? unsigned char sect[512] = {0};
??? struct SUPER_BLOCK sb;
??? unsigned int i = 0, j = 0, m = 0, n = 0;
??? /*?讀取超級塊?*/
??? sb.sb_start = q[0];
??? hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, &sb);
????//?很難想象這段代碼不越界,正好越界到sect上了?昏菜!
??? /*?判斷超級塊是否正確,不是就創建文件系統?*/
??? if (sb.sb_magic != FST_FS) {
??????? kprintf(KPL_DUMP, "Partition 1 does not have a valid file system\n");
??? ??? kprintf(KPL_DUMP, "Creating file system\t\t\t\t\t\t\t? ");
??? ??? sb.sb_magic = FST_FS;
??? ??? sb.sb_start = q[0];
??? ??? sb.sb_blocks = q[1];
??? ??? sb.sb_dmap_blks = (sb.sb_blocks+0xfff)>>12;
??? ??? sb.sb_imap_blks = INODE_BIT_BLKS;
??? ??? sb.sb_inode_blks = INODE_BLKS;
??? ??? hd_rw(ABS_SUPER_BLK(sb), HD_WRITE, 1, &sb);
?
????????// dmap位圖中,每個位表示1個扇區,也就是說dmap中每個扇區可以標識512 * 8扇區。
????????//?另外,我們把inode位圖大小固定,即使用1個扇區。
??????? /*?初始化dmap位圖?*/
??? ??? n = ABS_DMAP_BLK(sb);
??? ??? j = sb.sb_dmap_blks+sb.sb_imap_blks+sb.sb_inode_blks+2;
??? ??? memset(sect, 0xff, sizeof sect/sizeof sect[0]);
??? ??? for (i=j/(512*8); i>0; --i) {
??? ??? ??? hd_rw(n++, HD_WRITE, 1, sect);
??? ??? ??? m += 4096;
??? ??? }
??? ??? m += 4096;
??? ??? for (i=j%(512*8); i<512*8; ++i) {
??? ??? ??? clrb(sect, i);
??? ??? ??? --m;
??? ??? }
??? ??? hd_rw(n++, HD_WRITE, 1, sect);
??? ??? memset(sect, 0, sizeof sect/sizeof sect[0]);
??? ??? for (i=sb.sb_imap_blks-(n-ABS_DMAP_BLK(sb)); i>0; --i)
??? ??? ??? hd_rw(n++, HD_WRITE, 1, sect);
??? ??? /*?初始化inode?位圖?*/
??? ??? for (i=sb.sb_imap_blks; i>0; --i)
??? ??? ??? hd_rw(ABS_IMAP_BLK(sb)+i-1, HD_WRITE, 1, sect);
??? ??? /*?初始化inode?數組?*/
??? ??? for (i=sb.sb_inode_blks; i>0; --i)
??? ??? ??? hd_rw(ABS_INODE_BLK(sb)+i-1, HD_WRITE, 1, sect);
??? ??? kprintf(KPL_DUMP, "[DONE]");
??? }
??? q += 2;
??? kprintf(KPL_DUMP, "0: Type: FST_FS ");
??? kprintf(KPL_DUMP, "start at: %d ", sb.sb_start);
??? kprintf(KPL_DUMP, "blocks: %d ", sb.sb_blocks);
??? kprintf(KPL_DUMP, "dmap: %d ", sb.sb_dmap_blks);
??? kprintf(KPL_DUMP, "imap: %d ", sb.sb_imap_blks);
??? kprintf(KPL_DUMP, "inodes: %d\n", sb.sb_inode_blks);
??? /*?初始化交互分區?*/
??? sb.sb_start = q[0];
??? hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, &sb);
??? if (sb.sb_magic != FST_SW) {
????????//?注意,和數據分區不同(每個塊占有1個扇區),
????????//?交互分區每個塊占有8個扇區,即4096字節,和內存頁對齊
??????? kprintf(KPL_DUMP, "\nPartition 2 does not have a valid file system\n");
??? ??? kprintf(KPL_DUMP, "Creating file system\t\t\t\t\t\t\t? ");
??? ??? sb.sb_magic = FST_SW;
??? ??? sb.sb_start = q[0];
??? ??? sb.sb_blocks = q[1];
??? ??? sb.sb_dmap_blks = (sb.sb_blocks)>>15;??? /* 1 bits == 4K page */
??? ??? hd_rw(ABS_SUPER_BLK(sb), HD_WRITE, 1, &sb);
??? ??? kprintf(KPL_DUMP, "[DONE]");????
??? }
??? /*?初始化數據位圖?*/
??? n = ABS_DMAP_BLK(sb);
??? j = sb.sb_dmap_blks+2;
??? memset(sect, 0xff, sizeof sect/sizeof sect[0]);
??? for (i=j/(512*8); i>0; --i) {
??? ??? hd_rw(n++, HD_WRITE, 1, sect);
??? ??? m += 4096;
??? }
??? m += 4096;
??? for (i=j%(512*8); i<512*8; ++i) {
??? ??? clrb(sect, i);
??? ??? --m;
??? }
??? hd_rw(n++, HD_WRITE, 1, sect);
??? kprintf(KPL_DUMP, "1: Type: FST_SW ");
??? kprintf(KPL_DUMP, "start at: %d ", sb.sb_start);
??? kprintf(KPL_DUMP, "blocks: %d ", sb.sb_blocks);
??? kprintf(KPL_DUMP, "dmap: %d, presents %d 4k-page\n",
??? ??? ??? sb.sb_dmap_blks, sb.sb_blocks>>3);
}
最后修改Makefile,然后make clean && make dep && make
07/fs/Makefile
KERNEL_OBJS= load.o init.o isr.o timer.o libcc.o scr.o kb.o task.o kprintf.o hd.o exceptions.o fs.o
編譯,運行。
?
root?根目錄
最后一件事情建立根目錄。"/"是所有文件的根目錄,所以我們一開始就必須設置好它。"/"文件永遠使用inode 0,這樣skelix內核才知道怎樣找到它。然后再讀取"/"文件的內容,也就是DIR_ENTRY結構數組。為了更方便的操作,我們先完成一些基礎函數,用來操作blocks和inodes。
07/root/fs.c
static struct INODE iroot = {FT_DIR, 2*sizeof(struct DIR_ENTRY), {0,}};
unsigned int
alloc_blk(struct SUPER_BLOCK *sb) {
????//?根據dmap位圖查找空閑的扇區,返回LBA地址(從1開始)
??? unsigned int i = 0, j = 0, n = 0, m = 0;
??? unsigned char sect[512] = {0};
??? n = ABS_DMAP_BLK(*sb);
??? for (; i<sb->sb_dmap_blks; ++i) {
??? ??? hd_rw(n, HD_READ, 1, sect);
??? ??? for (j=0; j<512*8; ++j) {
??? ??? ??? if (testb(sect, j)) {
??? ??? ??? ??? ++m;
??? ??? ??? } else {??? ??? ??? /* gotcha */
??? ??? ??? ??? setb(sect, j);
??? ??? ??? ??? if (m >= sb->sb_blocks)
??? ??? ??? ??? ??? return 0;
??? ??? ??? ??? else {
??? ??? ??? ??? ??? hd_rw(n, HD_WRITE, 1, sect);
??? ??? ??? ??? ??? return ABS_BOOT_BLK(*sb) + m;
??? ??? ??? ??? }
??? ??? ??? }
??? ??? }
??? ??? ++n;
??? }
??? return 0;
}
void
free_blk(struct SUPER_BLOCK *sb, unsigned int n) {
????//?釋放一個扇區:設置dmap位圖中對應的位即可
??? unsigned char sect[512] = {0};
??? unsigned int t = (n-ABS_BOOT_BLK(*sb))/(512*8)+ABS_DMAP_BLK(*sb);
??? hd_rw(t, HD_READ, 1, sect);
??? clrb(sect, (n-ABS_BOOT_BLK(*sb))%(512*8));
??? hd_rw(t, HD_WRITE, 1, sect);
}
static int
alloc_inode(struct SUPER_BLOCK *sb) {
????//?在imap表中中找一個空閑的項
??? unsigned char sect[512] = {0};
??? int i = 0;
??? hd_rw(ABS_IMAP_BLK(*sb), HD_READ, 1, sect);
??? for (; i<512; ++i) {
??? ??? if (! testb(sect, i)) {
??? ??? ??? setb(sect, i);
??? ??? ??? hd_rw(ABS_IMAP_BLK(*sb), HD_WRITE, 1, sect);
??? ??? ??? break;
??? ??? }
??? }
??? return (i==512)?-1:i;
}
static void
free_inode(struct SUPER_BLOCK *sb, int n) {
????//?釋放inode項
??? unsigned char sect[512] = {0};
??? hd_rw(ABS_IMAP_BLK(*sb), HD_READ, 1, sect);
??? clrb(sect, n);
??? hd_rw(ABS_IMAP_BLK(*sb), HD_WRITE, 1, sect);
}
//?上面4個函數就是針對dmap和imap的操作(申請,釋放)
static struct INODE *
iget(struct SUPER_BLOCK *sb, struct INODE *inode, int n) {
??? unsigned char sect[512] = {0};
??? int i = n / INODES_PER_BLK;
??? int j = n % INODES_PER_BLK;
??? hd_rw(ABS_INODE_BLK(*sb)+i, HD_READ, 1, sect);
??? memcpy(inode, sect+j*sizeof(struct INODE), sizeof(struct INODE));
??? return inode;
}
static void
iput(struct SUPER_BLOCK *sb, struct INODE *inode, int n) {
??? unsigned char sect[512] = {0};
??? int i = n/INODES_PER_BLK;
??? int j = n%INODES_PER_BLK;
??? hd_rw(ABS_INODE_BLK(*sb)+i, HD_READ, 1, sect);
??? memcpy(sect+j*sizeof(struct INODE), inode, sizeof(struct INODE));
??? hd_rw(ABS_INODE_BLK(*sb)+i, HD_WRITE, 1, sect);
}
//?上面兩個函數分別完成讀/寫磁盤指定下標號對應的inode節點到內存中。
//?需要注意的是,這些函數對競態條件做處理,因為skelix僅內核讀寫硬盤。
//?本文中暫時沒有用戶態的多任務。
主流程如下:
07/root/fs.c
static void
check_root(void) {
??? struct SUPER_BLOCK sb;
??? unsigned char sect[512] = {0};
??? struct DIR_ENTRY *de = NULL;
??? sb.sb_start = *(unsigned int *)(HD0_ADDR);
??? hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect);
??? memcpy(&sb, sect, sizeof(struct SUPER_BLOCK));
??? hd_rw(ABS_IMAP_BLK(sb), HD_READ, 1, sect);
????//?加載imap扇區,判斷"/"目錄有沒有創建
??? if (! testb(sect, 0)) {????????????// "/"目錄必須使用inode 0,否則halt
??????? kprintf(KPL_DUMP, "/ has not been created, creating....\t\t\t\t\t? ");
??? ??? if (alloc_inode(&sb) != 0) {?? //?分配節點號:即imap位圖中的一位
??????????? kprintf(KPL_PANIC, "\n/ must be inode 0!!!\n");
??? ??? ??? halt();
??? ??? }
??????? iroot.i_block[0] = alloc_blk(&sb);????//?節點分配一個塊(一個扇區)
??????? iput(&sb, &iroot, 0);???????????????? //?寫入節點
?
??????? de = (struct DIR_ENTRY *)sect;
??? ??? strcpy(de->de_name, ".");
??? ??? de->de_inode = 0;???????????????????? //?節點號為0
??????? ++de;
??? ??? strcpy(de->de_name, "..");
??? ??? de->de_inode = -1;????????????????????//?節點號為-1,這樣我們就知道是最上層目錄了
??????? hd_rw(iroot.i_block[0], HD_WRITE, 1, sect);????//?寫入"."?和?".."文件夾
??????? kprintf(KPL_DUMP, "[DONE]");
??? }
??? iget(&sb, &iroot, 0);
??? hd_rw(iroot.i_block[0], HD_READ, 1, sect);
??? de = (struct DIR_ENTRY *)sect;
??? if ((strcmp(de[0].de_name, ".")) || (de[0].de_inode) ||
??? ??? (strcmp(de[1].de_name, "..")) || (de[1].de_inode) != -1) {
??? ??? kprintf(KPL_PANIC, "File system is corrupted!!!\n");
??? ??? halt();
??? }
}
?
//?再來一個函數打印文件的相關信息
static void
stat(struct INODE *inode) {
??? unsigned int i = 0;
??? char sect[512] = {0};
??? struct DIR_ENTRY *de;
??? kprintf(KPL_DUMP, "======== stat / ========\n");
??? switch (inode->i_mode) {
??? case FT_NML:
??? ??? kprintf(KPL_DUMP, "File, ");
??? ??? break;
??? case FT_DIR:
??? ??? kprintf(KPL_DUMP, "Dir,? ");
??? ??? break;
??? default:
??? ??? kprintf(KPL_PANIC, "UNKNOWN FILE TYPE!!");
??? ??? halt();
??? }
??? kprintf(KPL_DUMP, "Size: %d, ", inode->i_size);
??? kprintf(KPL_DUMP, "Blocks: ");
??? for (; i<8; ++i)????????//?打印inode標識使用的扇區
??????? kprintf(KPL_DUMP, "%d, ", inode->i_block[i]);
??? hd_rw(inode->i_block[0], HD_READ, 1, sect);
??? switch (inode->i_mode) {
??? case FT_DIR:
??? ??? kprintf(KPL_DUMP, "\nName\tINode\n");
??? ??? de = (struct DIR_ENTRY *)sect;????//?打印子目錄(只一個扇區)
??????? for (i=0; i<inode->i_size/sizeof(struct DIR_ENTRY); ++i) {
??? ??? ??? kprintf(KPL_DUMP, "%s\t%x\n", de[i].de_name, de[i].de_inode);
??? ??? }
??????? break;
??? default:
??? ??? break;
??? }
}
現在,我們把上面的函數整理到程序中
void
verify_dir(void) {
??? unsigned char sect[512] = {0};
??? unsigned int *q = (unsigned int *)(HD0_ADDR);
??? struct INODE inode;
??? struct SUPER_BLOCK sb;
??? sb.sb_start = q[0];
??? hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect);
??? check_root();
??? memcpy(&sb, sect, sizeof(struct SUPER_BLOCK));
??? stat(iget(&sb, &inode, 0));
}
07/root/init.c
void?
init(void) {
????……
??? kprintf(KPL_DUMP, "Verifing disk partition table....\n");
??? verify_DPT();
??? kprintf(KPL_DUMP, "Verifing file systes....\n");
??? verify_fs();
??? kprintf(KPL_DUMP, "Checking / directory....\n");
????verify_dir();
????……
}
不需要再編輯Makefile了,直接make && run好了。
總結
- 上一篇: java实践源码--哈弗曼树
- 下一篇: 自己动手写mvc