第7課:文件系統 下載源代碼
聲明:轉載請保留:
譯者:http://www.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn
目標
這一課中,將創建一個磁盤分區,并在她上面建立文件系統。文章看起來比較長,但是過程比較簡單。
大家都知道,硬盤最小的管理單位是扇區,每個扇區512字節。有兩種方式定位訪問磁盤。一種是CHS模式(Cylinder, Head, Sector),她反映了磁盤的物理結構。扇區(Sector)是磁盤可以直接訪問的最小數據單位(即一個塊)。柱面(Cylinder)也成為磁軌,通常一個
例如我建立了一個
ata0-master: type=disk, path="minix.img",
mode=flat, cylinders=200, heads=16, spt=63
計算:16 * 64 * 200 * 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與線形地址的關系。不同點在于:
- CHS是三維的,seg:offset是二維的
- LBA的空間更大2^32,CHS空間很小2^24(不考慮sector不能取0的問題)
如果你使用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}; // 定義一個
由于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地址了,我們將通過 0x
(這些繁文縟節一點也不好玩,但是我又不得不講)
07/include/hd.h
#define HD_PORT_DATA
0x
#define HD_PORT_ERROR 0x
#define HD_PORT_SECT_COUNT 0x
#define HD_PORT_SECT_NUM 0x
#define HD_PORT_CYL_LOW 0x
#define HD_PORT_CYL_HIGH 0x
#define HD_PORT_DRV_HEAD 0x
#define HD_PORT_STATUS 0x
#define HD_PORT_COMMAND 0x
另人恐慌,不過可以很清楚的來個分組。我們從0x
如果發生了錯誤就從0x
0x
0x
0x
端口0x
寫0x
#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 + 0x
sect[0x1ce + 0x04] = FST_SW; // 自定義的交換分區類型,后續課程使用
*(unsigned long *)§[0x1ce + 0x08] = 85*1024*2+1;
*(unsigned long *)§[0x1ce + 0x
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[0x
kprintf(KPL_DUMP,
"| %dM\n", (*(unsigned long*)&p[0x
// 保存到內存中,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比較敏感?,F在我來一一解釋這個數據結構中的域,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 +
對于目錄(也是一種文件)來說,它以類似數組的形式組織: {{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 0x
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 個扇區大小。
為了管理這個
在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好了。