第7课:文件系统 下载源代码
声明:转载请保留:
译者:http://www.cppblog.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn
目标
这一课中,将创建一个磁盘分区,并在她上面建立文件系统。文章看起来比较长,但是过程比较简单。
大家都知道,硬盘最小的管理单位是扇区,每个扇区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与线形地址的关系。不同点在于:
- 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}; // 定义一个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好了。