我们的目标

本节中,我们准备建立硬盘分区和文件系统,看起来很长,但很简单。

下载代码


存取硬盘

我们都知道,磁盘由扇区组成,通常情况下一个扇区有512字节长。

有两种常用的方式找到磁盘扇区,一个是CHS表示柱面、磁头和扇区,它反应了磁盘的物理结构。扇区是最小的磁盘可直接获取的数据单位。柱面也被叫做轨道,包含了一定数量的扇区(1.44软盘是18个扇区,硬盘的这个值通常是64),就想它名字一样,它是个同心圆。磁头表示了盘片的哪一面,因为磁盘包含了许多盘片,每一个盘片有两个面。这是一个硬盘盘片,

HD plate

显然,在CHS模式下,磁盘容量可以表示为:柱面*磁头每柱面*扇区每磁头*512字节。还必须注意,柱面和扇区,它们从0计数,扇区从1开始。

LBA(线性块寻址),它可以用逻辑地址方式访问磁盘。就像线性物理地址那样,LBA也用线性地址来表示磁盘扇区,柱面和磁头就不再使用了。用户用起来也容易,但是磁盘只能识别CHS模式,所以我们必须找到从CHS到LBA转换的方式,我们设定线性扇区地址从0开始。

反过来,

你可以从VMWARE的BIOS信息中得到CHS值。

hd information from bios

可以看到我使用的硬盘有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 4HD0(=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);

insloutsl像我们用过的inboutb一样,除了它们读写一定长度的数据而不是1个字节。

    while ((inb(HD_PORT_STATUS)&0x80))

        ;

确保工作完成。

实际上,它只给了你一个大体的存储硬盘的办法,它没有做多少错误检查,请注意这一点。然而,Skelix也不打算作任何复杂的操作,所以还好:)


硬盘分区

现在我们有了存取任何扇区的能力,现在让我们把所有的扇区组织在一起。

硬盘的第一个扇区必须包含硬盘分区的所有信息。硬盘分区表(DPT)开始于第一个扇区的0x1BE,64个字节包含了4个分区的信息。无须说明,接下来三个分区项开始于0x1CE、0x1DE和0x1EE,第四个项结束于0x1FC,0x1FE应该包含字0xAA55。

这个表展示了每项的格式,

字节0是否可启动
字节1-3CHS信息
字节4分区类型
字节5-7CHS信息
字节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 *)&sect[0x1be + 0x08] = 1;

    *(unsigned long *)&sect[0x1be + 0x0c] = 85*1024*2; /* 85MB */

第一个分区是一个可启动的正常文件系统,这个分区的类型定义成FST_FS,分区容量是85MB。

    sect[0x1ce + 0x04] = FST_SW;

    *(unsigned long *)&sect[0x1ce + 0x08] = 85*1024*2+1;

    *(unsigned long *)&sect[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 int *q = (unsigned int *)(HD0_ADDR);

引导后,分区的起始地址和长度保存在HD0_ADDR中。

    hd_rw(0, HD_READ, 1, sect);

    if ((sect[0x1fe]==0x55) && (sect[0x1ff]==0xaa)) {

检查是否是一个有效的扇区。

        unsigned char *p = &sect[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_runtask2_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.csti()之前。

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()之后,任务切换被许可,新任务可能会在硬盘设置好之前存取它。

最终,

making process of tutorial07 01 writes DPT

请注意,因为我们在虚拟机上设置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,所以不算小了。

three level sectors

例如,上面是个文件的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值,我们就可以找到这个文件。头两个文件就是“.”和“..”,表示当前和上一级目录,如果我们向下走,我们可以找到本目录下的每个文件,并且如果往上走,可以找到它的父目录。最顶级的目录是/,它没有有效的父目录。

file tree

例如,如果我们想在路径/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_FSFST_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数组占用了多少块

};

这个结构用来管理每个分区上的超级块信息。

总而言之,我们应该有一个像这样的磁盘映像,

partions and file system

每个分区的第一个分区都是启动扇区,但是我不准备用它,第二个扇区包含了超级块,接着是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 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;

 

    /* 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, 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);

 

        /* 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, 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);

有一个不同,在启动时,我们清除了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.oKERNEL_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

making process of tutorial07 02 get file system information

根目录

本节中剩下的最后一件事是建立根目录,/是所有文件的根,所以为了搜索其它文件的需要,我们必须在启动时找到它。

/总是用inode值0,所以Skelix知道哪里去找到它,从而读取/的文件内容,也就是一个DIR_ENTRY数组, 接着Skelix可以找到/下的所有文件。重复这个操作,就可以遍历整个文件树。

在我们看生成/的代码前,我们必须看一下操作文件系统上块和inode的几个函数。

07/root/fs.c

static struct INODE iroot = {FT_DIR, 2*sizeof(struct DIR_ENTRY), {0,}};

 

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_inodefree_inode工作起来和alloc_blkfree_blk一样,除了它们处理inode。

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);

}

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_NMLFT_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 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));

}

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();

making process of tutorial07 03 new file ssytem

如果你用一个新分配的虚拟盘,你应该看到这个,

full system information

正好一屏,嘿嘿



你可以自由使用我的代码,如有疑问请联系我