我们都知道,磁盘由扇区组成,通常情况下一个扇区有512字节长。
有两种常用的方式找到磁盘扇区,一个是CHS表示柱面、磁头和扇区,它反应了磁盘的物理结构。扇区是最小的磁盘可直接获取的数据单位。柱面也被叫做轨道,包含了一定数量的扇区(1.44软盘是18个扇区,硬盘的这个值通常是64),就想它名字一样,它是个同心圆。磁头表示了盘片的哪一面,因为磁盘包含了许多盘片,每一个盘片有两个面。这是一个硬盘盘片,
显然,在CHS模式下,磁盘容量可以表示为:柱面*磁头每柱面*扇区每磁头*512字节。还必须注意,柱面和扇区,它们从0计数,扇区从1开始。
LBA(线性块寻址),它可以用逻辑地址方式访问磁盘。就像线性物理地址那样,LBA也用线性地址来表示磁盘扇区,柱面和磁头就不再使用了。用户用起来也容易,但是磁盘只能识别CHS模式,所以我们必须找到从CHS到LBA转换的方式,我们设定线性扇区地址从0开始。
LBA = (柱面*磁头每柱面 + 磁头)*扇区每磁头 + 扇区 - 1
反过来,
柱面 = LBA / (磁头每柱面 * 扇区每柱面)
temp = LBA % (磁头每柱面 * 扇区每柱面)
H = temp / 扇区每柱面
S = temp % 扇区每柱面 + 1
你可以从VMWARE的BIOS信息中得到CHS值。
可以看到我使用的硬盘有208个柱面、16个磁头和64个扇区每柱面。把它们放到这个结构里去,
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地址,我们准备通过端口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 | 磁盘数据被正确读取 |
Bit 1 | 索引 - 每转一次都设为1 |
Bit 0 | 上一个命令有错误 |
继续
outb(sects_to_access, HD_PORT_SECT_COUNT);
outb(sect, HD_PORT_SECT_NUM);
outb(cyl, HD_PORT_CYL_LOW);
outb(cyl>>8, HD_PORT_CYL_HIGH);
outb(0xa0|head, HD_PORT_DRV_HEAD);
磁头信息所用的端口也被用于选择硬盘,
Bits 7-5 | 必须是101b |
Bit 4 | HD0(=0),HD1(=1) |
Bits 3-0 | 磁头数 |
继续
outb(com, HD_PORT_COMMAND);
com
定义了命令,它们的宏如下,
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
像我们用过的inb
和outb
一样,除了它们读写一定长度的数据而不是1个字节。
while
((inb(HD_PORT_STATUS)&0x80))
;
确保工作完成。
实际上,它只给了你一个大体的存储硬盘的办法,它没有做多少错误检查,请注意这一点。然而,Skelix也不打算作任何复杂的操作,所以还好:)
现在我们有了存取任何扇区的能力,现在让我们把所有的扇区组织在一起。
硬盘的第一个扇区必须包含硬盘分区的所有信息。硬盘分区表(DPT)开始于第一个扇区的0x1BE,64个字节包含了4个分区的信息。无须说明,接下来三个分区项开始于0x1CE、0x1DE和0x1EE,第四个项结束于0x1FC,0x1FE应该包含字0xAA55。
这个表展示了每项的格式,
字节0 | 是否可启动 |
字节1-3 | CHS信息 |
字节4 | 分区类型 |
字节5-7 | CHS信息 |
字节8-11 | 开始扇区的偏移 |
字节12-16 | 分区中的扇区数 |
字节0定义了这个分区是否是启动分区,启动分区的值应为0x80。通常来说一块硬盘只有一个启动分区。字节4定义了建立这个分区的文件系统的类型,像FAT32、NTFS、ReiserFS之类,你可以看这里了解各种分区类型。
实际上我们可以看到(字节1-3、字节5-7)和(字节8-11、字节12-16)提供了两种可能的寻找硬盘扇区的方式,(字节1-3,字节5-7)提供了所有的关于CHS的信息,但是不打算解释它们因为我不打算用它们。字节8-11,这4字节长的整数提供了这个分区的第一个扇区到磁盘开始处的偏移,字节12-16指出了本分区中共有多少扇区,简单的把它们相加就可以得到LBA地址。简而言之,DPT结构实际上提供了两种寻找扇区的方式,因此实际上你可以任选其一来管理你的磁盘。我们准备用第二种方式。
建立DPT,
06/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 */
第一个分区是一个可启动的正常文件系统,这个分区的类型定义成FST_FS
,分区容量是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);
}
不要忘记把它写入第一个扇区。hd_rw
用扇区数作为第一个参数,第二个参数非常明显,第三个参数指出了我们准备存取多少个扇区,这里只有一个扇区,最后一个参数是扇区缓冲。
先看另一个函数,它要在启动时输出分区信息。
void
verify_DPT(void
) {
unsigned
char
sect[512];
unsigned
i = 0;
unsigned
*q = (int
unsigned
*)(HD0_ADDR);int
引导后,分区的起始地址和长度保存在HD0_ADDR
中。
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);
/* system indicator at offset 0x04 */
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);
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
的代码,因为他们不停的上滚屏幕,让我们没法看verify_DPT
输出的信息,所以我们必须把它们的代码改成,
void
do_task1(void
) {
__asm__
("incb
0xb8000+160*24+2");
}
void
do_task2(void
) {
__asm__
("incb
0xb8000+160*24+4");
}
我们让task1_run
翻动最底行的第二个字符,task2_run
翻动最底行的第三个字符。
现在我们有了一个可以输出的屏幕。修改Makefile,把hd.o
加入到KERNEL_OBJS
中去,把verify_DPT
加入到07/dpt/init.c
,sti()
之前。
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();
因为在sti()
之后,任务切换被许可,新任务可能会在硬盘设置好之前存取它。
最终,
请注意,因为我们在虚拟机上设置Skelix,虚拟硬盘上没有其他的系统,所以我们我无须关心其它系统分区,为简化流程需要,Skelix假定第一个分区是普通文件分区,第二个分区是交换分区。
我们现在把分区弄好了,我们要在每个分区上建立文件系统。我们可以存取分区的任意扇区,但是从管理文件的角度是很不方便的,我们需要在让它再结构化一些,所以我们必须引入新的结构去表示文件。
07/fs/include/fs.h
#define FT_NML 1
#define FT_DIR 2
struct
INODE {
unsigned
int
i_mode; /* file mode */
unsigned
int
i_size; /* size in bytes */
unsigned
int
i_block[8];
};
类*nix用户肯定熟悉inode这个名字,现在我要解释这个结构中的每个域。i_mode
定义了文件类型,Skelix通过INODE
把所有文件一视同仁,FT_NML
表示文件是普通文件,FT_DIR
指出文件是目录,i_size
很简单,是文件长度,目录文件的长度一会儿再解释。开始的6个无符号整数表示了文件的头6个扇区,也就是3KB大小,第7个无符号整数指向了一个扇区,这个扇区包含了512/4 = 128个扇区号,它们是文件接下来的128个扇区,现在又128*512 = 64KB,最后一个无符号整数指向一个有 512/4 = 128 个项,每个项都指向像第7个无符号整数指向的扇区那样的扇区,它可以表示128*128*512 = 8MB。糊涂了吧,一会儿看图就明白了。使用这三级磁盘“指针”,我们可以有最大为3+64+8*1024 = 8259KB的文件。确实不大,不过请注意我们的分区也只有85MB,所以不算小了。
例如,上面是个文件的inode,那么它的数据保存在第13、7、9、16、257、57、3、…….、35、33、…….、55、……...、128、…….、81的扇区中
对目录文件来说,它的结构像一个包含了这个结构{{file_name, file_inode}, {file_name, file_inode}, {file_name, file_inode}, }
的数组。
#define MAX_NAME_LEN 11
struct
DIR_ENTRY {
char
de_name[MAX_NAME_LEN];
int
de_inode;
};
系统中所有的文件都有一个独特的inode值,所有如果我们有了这个inode值,我们就可以找到这个文件。头两个文件就是“.”和“..”,表示当前和上一级目录,如果我们向下走,我们可以找到本目录下的每个文件,并且如果往上走,可以找到它的父目录。最顶级的目录是/
,它没有有效的父目录。
例如,如果我们想在路径/usr/doc/fvwm/TODO
中寻找TODO
,那么我们要先找到/
,然后在它的项中寻找doc
,它是个目录,所以我们从/
中获得它的inode值,然后在不管哪里搜索inode表获得它的内容,然后再在里面搜索下一个路径段fvwm
,寻找它的inode值,再搜索fvwm
的文件内容,直到我们找到文件TODO
和它的inode值,然后再一次根据它的inode值搜索不管在哪里的inode表找到它的存储扇区,即i_block
数组,也就意味着我们可以存取这个文件了。
如你所见,这里有两个问题,首先我们需要有一个inode表好对应inode值和文件扇区,我们把系统中所有的inode组织成一个数组并存到磁盘中去,并把inode值当成这个数组的索引,另一个问题是/
没有父目录,那么我们怎么才能找到它呢。很简单,我们把它作为inode数组的第一个元素,它的inode值总是0。实际上,这里有一个很小但是很烦人的问题,我们必须定义最长的文件名长度,因为inode数值被当成整形定义,也就是4字节长,因此我们把最长的文件名长度设定为12字节,所以我们能把目录文件中的项长度定为16字节,可以很方便的用于磁盘I/O。
一些文件建立和删除操作以后,会有一些inode被占用或被释放,但是我们怎样才能知道呢?我们准备做一个inode数组的位图,每一位代表了一个inode数组中元素的占用情况。同样的问题也存在于管理扇区的占用情况,我们也用一个位图来表示哪个扇区被使用了哪个未用。
07/fs/include/fs.h
struct
SUPER_BLOCK {
unsigned
char
sb_magic;
分区类型FST_FS
或FST_SW
/* DPT 0x08 */
unsigned
int
sb_start;
起始扇区号
/* DPT 0x0c */
unsigned
int
sb_blocks;
分区容量,用扇区数表示
unsigned
int
sb_dmap_blks;
块位图占用了多少块(Skelix中一块就是一个扇区)
unsigned
int
sb_imap_blks;
inode数组位图占用了多少块
unsigned
int
sb_inode_blks;
inode数组占用了多少块
};
这个结构用来管理每个分区上的超级块信息。
总而言之,我们应该有一个像这样的磁盘映像,
每个分区的第一个分区都是启动扇区,但是我不准备用它,第二个扇区包含了超级块,接着是dmap、imap和inode,用户数据又在它们之后。
方便起见,定义一些宏。sb
是分区的SUPER_BLOCK
结构,我们用这些宏去获得指定扇区的LBA地址,以方便hd_rw
使用。
#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)
#define INODE_BIT_BLKS 1 /* 512*8 = 4096 inodes */
#define INODES_PER_BLK (512/sizeof
(struct
INODE))
#define INODE_BLKS ((INODE_BIT_BLKS*512*8+INODES_PER_BLK-1)/(INODES_PER_BLK))
这些块被Skelix保留。现在我们有了一个分区的大体印象,现在开始看代码。位图也让这些位操作容易些,
07/fs/fs.c
void
setb(void
*s, unsigned
int
i) {
unsigned
char
*v = s;
v += i>>3;
*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;
}
例如,通过调用setb(sect, 1796)
可以设置位图中的第1796位,偏移开始于0,clrb
用来清除一位,testb
用来测试一位是否被设置。
void
verify_fs(void
) {
unsigned
*q = (int
unsigned
*)(HD0_ADDR);int
unsigned
char
sect[512] = {0};
struct
SUPER_BLOCK sb;
unsigned
int
i = 0, j = 0, m = 0, n = 0;
/* get the fs sb */
sb.sb_start = q[0];
hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, &sb);
从第一个分区中读取超级块,它应该是普通文件系统。
/* the first partition must be FST_FS type */
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);
填充SUPER_BLOCK
结构并把它写入磁盘。请注意,在dmap块中每一位表示了一个扇区,所以一个dmap扇区可以表示512*8 = 2^12个扇区,我们仅仅做了一个位操作。简化起见,我们定义每个分区有固定数目的inode,我们定义每个分区用一个扇区作为imap,所以它可以表示512*8个扇区,也意味着INODE_BIT_BLKS=1
,并且一个扇区可以包含512/sizeof(struct INODE)
个inode,最后我们需要INODE_BLKS=((INODE_BIT_BLKS+INODES_PER_BLK-1)/(INODES_PER_BLK))
个扇区给inode数组用。
/* initial data bitmap */
n = ABS_DMAP_BLK(sb);
j = sb.sb_dmap_blks+sb.sb_imap_blks+sb.sb_inode_blks+2;
memset(sect, 0xff,
sect/sizeof
sect[0]);sizeof
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,
sect/sizeof
sect[0]);sizeof
for
(i=sb.sb_imap_blks-(n-ABS_DMAP_BLK(sb)); i>0; --i)
hd_rw(n++, HD_WRITE, 1, sect);
/* initail inode bitmap */
for
(i=sb.sb_imap_blks; i>0; --i)
hd_rw(ABS_IMAP_BLK(sb)+i-1, HD_WRITE, 1, sect);
/* initial inode blocks */
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]");
位操作,填充dmap和imap,将所有被启动扇区、超级块、imap、dmap和inode占用的扇区在dmap中设为1。
}
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);
/* initialize swap partition */
sb.sb_start = q[0];
hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, &sb);
if
(sb.sb_magic != FST_SW) {
第二个分区是交换扇区,虚拟内存会使用它,后面的教程会解释。当前我们只需知道每4KB磁盘空间被组织成一个单元,这个分区不需要imap和inode,所以很简单,
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]");
}
/* initial data bitmap */
n = ABS_DMAP_BLK(sb);
j = sb.sb_dmap_blks+2;
memset(sect, 0xff,
sect/sizeof
sect[0]);sizeof
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);
有一个不同,在启动时,我们清除了bmap中的所有位,所以我们不保留磁盘中的任何信息,原因在下一节中解释。
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,增加fs.o
到KERNEL_OBJS
中,接着运行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
本节中剩下的最后一件事是建立根目录,/
是所有文件的根,所以为了搜索其它文件的需要,我们必须在启动时找到它。
/
总是用inode值0,所以Skelix知道哪里去找到它,从而读取/
的文件内容,也就是一个DIR_ENTRY
数组,
接着Skelix可以找到/
下的所有文件。重复这个操作,就可以遍历整个文件树。
在我们看生成/
的代码前,我们必须看一下操作文件系统上块和inode的几个函数。
07/root/fs.c
static
INODE iroot = {FT_DIR, 2*struct
sizeof
(
DIR_ENTRY), {0,}};struct
unsigned
int
alloc_blk(struct
SUPER_BLOCK *sb) {
这个函数找到第一个找到的未用块,返回它的相对于整个盘的LBA地址,实际上在内核中我们也会这么用,所有的磁盘地址都是相对这个硬盘的LBA地址,而不是相对于分区的。
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) {
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) {
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) {
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);
}
alloc_inode
、free_inode
工作起来和alloc_blk
、free_blk
一样,除了它们处理inode。
static
struct
INODE *
iget(
SUPER_BLOCK *sb, struct
INODE *inode, struct
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
INODE), struct
(sizeof
INODE));struct
return
inode;
}
static
void
iput(
SUPER_BLOCK *sb, struct
INODE *inode, struct
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
INODE), inode, struct
(sizeof
INODE));struct
hd_rw(ABS_INODE_BLK(*sb)+i, HD_WRITE, 1, sect);
}
iget
把指定inode值的inode内容从磁盘读入内存,iput
把inode写入磁盘。这六个函数的用法非常清晰,它们的实现却不:(。注意这些函数不处理竞争条件,因为他们只会被内核用来设置系统,用户任务应该用系统调用,系统调用会在后面的教程中介绍。
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块载入内存,如果/
已经被建立,那么imap的第一位被设置。
if
(! testb(sect, 0)) {
kprintf(KPL_DUMP, "/ has not been created, creating....\t\t\t\t\t ");
if
(alloc_inode(&sb) != 0) {
kprintf(KPL_PANIC, "\n/ must be inode 0!!!\n");
halt();
}
/
必须使用inode值0,如果这个inode已经被分配,那么就是哪里搞错了。
iroot.i_block[0] = alloc_blk(&sb);
iput(&sb, &iroot, 0);
把/
的inode写入硬盘,它被设置成目录类型,长度是两个DIR_ENTRY
,因为一个是.
,另一个是..
。接着我们分配一个磁盘上未用的块把它设置成/
占用的第一个块。
de = (struct
DIR_ENTRY *)sect;
strcpy(de->de_name, ".");
de->de_inode = 0;
++de;
strcpy(de->de_name, "..");
de->de_inode = -1;
hd_rw(iroot.i_block[0], HD_WRITE, 1, sect);
我们把第一个块填入格式为{{".", 0}, {"..", -1}}
的信息,我们让根的父目录的inode值为无效的-1,所以当我们到达文件树顶端的时候,我们知道不能再往上了。
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();
}
}
stat
用来检查文件信息。
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();
}
输出文件类型,必须是FT_NML
或FT_DIR
之一。
kprintf(KPL_DUMP, "Size: %d, ", inode->i_size);
kprintf(KPL_DUMP, "Blocks: ");
for
(; i<8; ++i)
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
*q = (int
unsigned
*)(HD0_ADDR);int
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));
}
把verify_dir
放入07/root/init.c
文件,在verify_fs()
之后。
07/root/init.c
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();
如果你用一个新分配的虚拟盘,你应该看到这个,
正好一屏,嘿嘿
你可以自由使用我的代码,如有疑问请联系我。