这一课中,将创建一个磁盘分区,并在她上面建立文件系统。文章看起来比较长,但是过程比较简单。
大家都知道,硬盘最小的管理单位是扇区,每个扇区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
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
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好了。