自己动手写文件系统

这一课中,将创建一个磁盘分区,并在她上面建立文件系统。文章看起来比较长,但是过程比较简单。




大家都知道,硬盘最小的管理单位是扇区,每个扇区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与线形地址的关系。不同点在于:

  1. CHS是三维的,seg:offset是二维的
  2. 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 *)&sect[0x1be + 0x08] = 1;
    *(unsigned long *)&sect[0x1be + 0x0c] = 85*1024*2; /* 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);    // 写入磁盘

    // 写到扇区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 = &sect[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好了。

展开阅读全文

没有更多推荐了,返回首页