《ORANGE’S:一个操作系统的实现》读书笔记(二十八)文件系统(三)

发布时间:2024年01月11日

上一篇文章记录了文件系统,我们将硬盘映像进行了分区,并且清楚了如何获取分区信息。但是硬盘驱动程序目前只能处理DEV_OPEN消息,这显然是不够的,这篇文章记录对硬盘驱动的完善,让它能够处理更多的消息,并且记录在硬盘上制作一个文件系统。

完善硬盘驱动程序

目前驱动程序只能处理DEV_OPEN消息,这显然是不够的,起码也要有DEV_READ和DEV_WRITE吧,要不然只是“打开”也没有意义,用不起来啊。我们现在就来记录添加读写硬盘的代码。

代码 kernel/hd.c,让驱动程序处理更多消息。

/* task_hd */
/* Main loop of HD driver */
PUBLIC void task_hd()
{
...
        switch (msg.type) {
            case DEV_OPEN:
                hd_open(msg.DEVICE);
                break;
            case DEV_CLOSE:
                hd_close(msg.DEVICE);
                break;
            case DEV_READ:
            case DEV_WRITE:
                hd_rdwt(&msg);
                break;
            case DEV_IOCTL:
                hd_ioctl(&msg);
                break;
...
        }
...
}
...
/**
 * <Ring 1> This routine handles DEV_CLOSE message.
 * 
 * @param device The device to be opened.
 */
PRIVATE void hd_close(int device)
{
    int drive = DRV_OF_DEV(device);
    assert(drive == 0); /* only one drive */

    hd_info[drive].open_cnt--;
}

/**
 * <Ring 1> This routine handles DEV_READ and DEV_WRITE message.
 * 
 * @param p Message ptr.
 */
PRIVATE void hd_rdwt(MESSAGE * p)
{
    int drive = DRV_OF_DEV(p->DEVICE);

    u64 pos = p->POSITION;
    assert((pos >> SECTOR_SIZE_SHIFT) < (1 << 31));

    /* We only allow to R/W from a SECTOR boundary: */
    assert((pos & 0x1FF) == 0);

    u32 sect_nr = (u32)(pos >> SECTOR_SIZE_SHIFT); /* pos / SECTOR_SIZE */
    int logidx = (p->DEVICE - MINOR_hd1a) % NR_SUB_PER_DRIVE;
    sect_nr += p->DEVICE < MAX_PRIM ?
            hd_info[drive].primary[p->DEVICE].base :
            hd_info[drive].logical[logidx].base;
    
    struct hd_cmd cmd;
    cmd.features = 0;
    cmd.count = (p->CNT + SECTOR_SIZE - 1) / SECTOR_SIZE;
    cmd.lba_low = sect_nr & 0xFF;
    cmd.lba_mid = (sect_nr >> 8) & 0xFF;
    cmd.lba_high = (sect_nr >> 16) & 0xFF;
    cmd.device = MAKE_DEVICE_REG(1, drive, (sect_nr >> 24) & 0xF);
    cmd.command = (p->type == DEV_READ) ? ATA_READ : ATA_WRITE;
    hd_cmd_out(&cmd);

    int bytes_left = p->CNT;
    void * la = (void*)va2la(p->PROC_NR, p->BUF);

    while (bytes_left) {
        int bytes = min(SECTOR_SIZE, bytes_left);
        if (p->type == DEV_READ) {
            interrupt_wait();
            port_read(REG_DATA, hdbuf, SECTOR_SIZE);
            phys_copy(la, (void*)va2la(TASK_HD, hdbuf), bytes);
        } else {
            if (!waitfor(STATUS_DRQ, STATUS_DRQ, HD_TIMEOUT)) {
                panic("hd writing error.");
            }
            port_write(REG_DATA, la, bytes);
            interrupt_wait();
        }
        bytes_left -= SECTOR_SIZE;
        la += SECTOR_SIZE;
    }
}

/**
 * <Ring 1> This routine handles the DEV_IOCTL message
 * 
 * @param p Ptr to the MESSAGE.
 */
PRIVATE void hd_ioctl(MESSAGE * p)
{
    int device = p->DEVICE;
    int drive = DRV_OF_DEV(device);

    struct hd_info * hdi = &hd_info[drive];

    if (p->REQUEST == DIOCTL_GET_GEO) {
        void * dst = va2la(p->PROC_NR, p->BUF);
        void * src = va2la(TASK_HD,
                           device < MAX_PRIM ?
                           &hdi->primary[device] :
                           &hdi->logical[(device - MINOR_hd1a) % NR_SUB_PER_DRIVE]);
        
        phys_copy(dst, src, sizeof(struct part_info));
    } else {
        assert(0);
    }
}

新增加的代码中增加了对四种消息的处理,所以到目前为止我们的硬盘驱动总共支持五种消息:

  • DEV_OPEN
  • DEV_CLOSE
  • DEV_READ
  • DEV_WRITE
  • DEV_IOCTL

其中DEV_READ和DEV_WRITE用同一个函数hd_rdwt()来处理,所以增加的函数共有三个,hd_close()非常简单。hd_ioctl()也容易理解,目前只支持一种消息类型——DIOCTL_GET_GEO,所做工作只是把请求的设备的起始扇区和扇区数目返回给调用者而已。剩下一个hd_rdwt(),书上这里没有任何的优化措施,没有缓冲区,只是让干什么就干什么,虽然效率不高,但是贵在简单。

新添加的消息类型和宏定义在 const.h 中,具体如下所示:

enum msgtype {
    /* 
     * when hard interrupt occurs, a msg (with type==HARD_INT) will
     * be sent to some tasks
     */
    HARD_INT = 1,

    /* SYS task */
    GET_TICKS,

    /* message type for drivers */
    DEV_OPEN = 1001,
    DEV_CLOSE,
    DEV_READ,
    DEV_WRITE,
    DEV_IOCTL
};

#define DIOCTL_GET_GEO  1

在硬盘上制作一个文件系统

硬盘驱动已经完成,接下来就要开始编写文件系统了。在开始之前,我们需要了解,文件系统通常有两个含义:

  • 用于存储和组织计算机文件数据的一套方法
  • 存在于某介质上的具备某种格式的数据

当我们说要“编写一个文件系统”时,我们指的是前者。因为我们需要考虑如何利用空间,如何对文件进行添加、删除以及修改,还要考虑不同类型的文件并存于同一个文件系统,比如在 *nix 世界中,设备通常也是文件。所以我们考虑的其实是一套机制,包括静态的存储方法和动态的管理方法。

当我们说某个硬盘分区上是“某某文件系统”时,说的是后者。它是静态的,其格式表明这个分区是由某种策略和机制来管理。或者说,它是一种含义的管理对象。

在上一篇文章中讨论的其实是第二种含义,这是我们对文件系统最直观的认识。接下来,我们仍然从这个直观认识入手,先在硬盘上划定格式,等有一个框架之后,再考虑文件的增删改等诸多事宜。有了上篇文章文件系统的设计图以及我们完成的硬盘驱动程序,这一工作就已经不那么复杂了。

文件系统涉及的数据结构

首先,我们先把上篇文章中文件系统提到的要素具体化成各种数据结构,代码如下所示。

代码 include/fs.h,super_block和inode。

/**
 * @def MAGIC_V1
 * @brief Magic number of FS v1.0
 */
#define MAGIC_V1    0x111

/**
 * @struct super_block fs.h "include/fs.h"
 * @brief The 2nd sector of the FS
 * 
 * Remeber to change SUPER_BLOCK_SIZE if the members are changed.
 */
struct super_block {
    u32 magic;              /*< Magic number */
    u32 nr_inodes;          /*< How many inodes */
    u32 nr_sects;           /*< How many sectors */
    u32 nr_imap_sects;      /*< How many inode-map sectors */
    u32 nr_smap_sects;      /*< How many sector-map sectors */
    u32 n_1st_sect;         /*< Number of the 1st data sector */
    u32 nr_inode_sects; /*< How many inode sectors */
    u32 root_inode;         /*< Inode nr of root directory */
    u32 inode_size;         /*< INODE_SIZE */
    u32 inode_isize_off;    /*< Offset of 'struct inode::i_size' */
    u32 inode_start_off;    /*< Offset of 'struct inode::ii_start_sect' */
    u32 dir_ent_size;       /*< DIR_ENTRY_SIZE */
    u32 dir_ent_inode_off;  /*< Offset of 'struct dir_entry::inode_nr' */
    u32 dir_ent_fname_off;  /*< Offset of 'struct dir_entry::name' */

    /* the following item(s) are only present in memory */
    int sb_dev;             /*< the super block's home device */
};

/**
 * @def SUPER_BLOCK_SIZE
 * @brief The size of super block \b in \b the \b device.
 * 
 * Note that this is the size of the struct in the device, \b NOT in memory.
 * The size in memory is larger because of some more members.
 */
#define SUPER_BLOCK_SIZE    56

/**
 * @struct inode
 * @brief i-node
 * 
 * The \c start_sect and\c nr_sects locate the file in the device,
 * and the size show how many bytes is used.
 * If <tt> size < (nr_sects * SECTOR_SIZE) </tt>, the rest bytes 
 * are wasted and reserved for later writing.
 * 
 * \b NOTE: Remeber to change INODE_SIZE if the members are changed
 */
struct inode {
    u32 i_mode;         /*< Access mode */
    u32 i_size;         /*< File size */
    u32 i_start_sect;   /*< The first sector of the data */
    u32 i_nr_sects;     /*< How many sectors the file occupies */
    u8 _unused[16];     /*< Stuff for alignment*/

    /* the following items are only present in memory */
    int i_dev;
    int i_cnt;          /*< How many procs share this inode */
    int i_num;          /*< inode nr. */
};

/**
 * @def INODE_SIZE
 * @brief The size of i-node stored \b in \b the \b device.
 * 
 * Note that this is the size of the struct in the device, \b NOT in memory.
 * The size in memory is larger because of some more members.
 */
#define INODE_SIZE  32

/**
 * @def MAX_FILENAME_LEN
 * @brief Max len of a filename
 * @see dir_entry
 */
#define MAX_FILENAME_LEN    12

/**
 * @struct dir_entry
 * @brief Directory Entry
 */
struct dir_entry {
    int inode_nr;                   /*< inode nr. */
    char name[MAX_FILENAME_LEN];    /*< Filename */
};

/**
 * @def DIR_ENTRY_SIZE
 * @brief The size of directory entry in the device.
 * 
 * It is as same as the size in memory.
 */
#define DIR_ENTRY_SIZE  sizeof(struct dir_entry)

上述代码定义了三个结构体,分别代表超级块、i-node和目录项。

超级块主要关注以下内容:

  • 文件系统的标识。这里用一个魔数(Magic Number)表明本文件系统是Orange’S FS v1.0。
  • 文件系统最多允许有多少个i-node。
  • inode-array占用多少扇区。
  • 文件系统总共扇区数是多少。
  • inode-map占用多少扇区。
  • sector-map占用多少扇区。
  • 第一个数据扇区的扇区号是多少。
  • 根目录区的i-node号是多少。

此外,inode和dir_entry两个结构体的一些信息也放在了这里。超级块有512个字节,通常是用不完的,所以哪怕有些项可放可不放,为了编码的方便,我们也可以尽管放进来。

请注意super_block这个结构体有个特殊的成员sb_dev,它在硬盘上是不存在的,这也是我们将SUPER_BLOCK_SIZE定义为56的原因。sb_dev存在的理由是这样的,我们打开一个设备后,会将超级块读入内存,这时我们要记录这个超级块是从哪里来的,于是我们把这个超级块所在设备的设备号记录在sb_dev中,这样我们随时都能知道这个超级块是属于哪个设备的,同时我们也可以通过辨识设备号来随时得到该设备的超级块而不必重新进行读取。

存在这样的特殊成员的缺点也很明显,那就是必须保持super_block这个结构体和SUPER_BLOCK_SIZE的一致,改变结构体时需要同时改变代表其大小的宏,在编码时需要注意。

我们的inode结构体目前很简单,其中i_start_sect代表文件的起始扇区,i_nr_sects代表总扇区数,i_size代表文件大小。在这里我们使用一个很笨拙的方法来存储文件,那就是事先为文件预留出一定的扇区数(i_nr_sects),以便让文件能够追加数据,i_nr_sects一旦确定就不再更改。这样做的优点很明显,那就是分配扇区的工作在建立文件时一次完成,从此再也不用额外分配或释放扇区。缺点也很明显,那就是文件大小范围在文件建立之后就无法改变了,i_size满足:i_size∈[0,i_nr_sects×512]。

书上最终决定使用这种有明显缺陷的i-node,原因还是因为简单,以至于可以大大简化我们的代码,而且,它还是能用的。等到我们有很多文件,需要灵活性时,再进行修改,推出一个v2.0,那时需要改进的怕是不仅仅是一个i-node了,所以在做第一步时,就怎么简单怎么来。

成员i_mode主要被用来区分文件类型。前面提到过,文件不仅仅可以是磁盘上的一块数据,也可以是一个设备。一个普通文件和一个特殊文件(比如设备文件)将首先从i_mode上区分开来。

inode结构体里也有几个只在内存中存在的成员,理由和super_block类似。

最后一个结构体是dir_entry,它是存在于根目录文件中的数据结构,用来索引一个文件。它只有两个成员:i-node和文件名。将来我们的根目录将会是一个dir_entry数组,用以索引文件系统中所有的文件。

编码建立文件系统

既然文件系统的结构和所需要的数据结构都已经备齐,下面就可以开始写硬盘了。

代码 fs/main.c,初始化文件系统。

/**
 * <Ring 1> The main loop of TASK FS.
 */
PUBLIC void task_fs()
{
    printl("Task FS begins.\n");
    init_fs();
    spin("FS");
}

/**
 * <Ring 1> Do some preparation.
 */
PRIVATE void init_fs()
{
    /* open the device: hard disk */
    MESSAGE driver_msg;
    driver_msg.type = DEV_OPEN;
    driver_msg.DEVICE = MINOR(ROOT_DEV);
    assert(dd_map[MAJOR(ROOT_DEV)].driver_nr != INVALID_DRIVER);
    send_recv(BOTH, dd_map[MAJOR(ROOT_DEV)].driver_nr, &driver_msg);

    mkfs();
}

/**
 * <Ring 1> Make a available Orange'S FS in the disk. it will
 *              - Write a super block to sector 1.
 *              - Create three special files: dev_tty0, dev_tty1, dev_tty2
 *              - Create the inode map
 *              - Create the sector map
 *              - Create the inodes of the files
 *              - Create '/' the root directory
 */
PRIVATE void mkfs()
{
    MESSAGE driver_msg;
    int i, j;

    int bits_per_sect = SECTOR_SIZE * 8; /* 8 bits per byte */

    /* get the geometry of ROOTDEV */
    struct part_info geo;
    driver_msg.type = DEV_IOCTL;
    driver_msg.DEVICE = MINOR(ROOT_DEV);
    driver_msg.REQUEST = DIOCTL_GET_GEO;
    driver_msg.BUF = &geo;
    driver_msg.PROC_NR = TASK_FS;
    assert(dd_map[MAJOR(ROOT_DEV)].driver_nr != INVALID_DRIVER);
    send_recv(BOTH, dd_map[MAJOR(ROOT_DEV)].driver_nr, &driver_msg);

    printl("dev size: 0x%x sectors\n", geo.size);

    /* super block */
    struct super_block sb;
    sb.magic = MAGIC_V1;
    sb.nr_inodes = bits_per_sect;
    sb.nr_inode_sects = sb.nr_inodes * INODE_SIZE / SECTOR_SIZE;
    sb.nr_sects = geo.size; /* partition size in sector */
    sb.nr_imap_sects = 1;
    sb.nr_smap_sects = sb.nr_sects / bits_per_sect + 1;
    sb.n_1st_sect = 1 + 1 + /* boot sector & super block */
                    sb.nr_imap_sects + sb.nr_smap_sects + sb.nr_inode_sects;
    sb.root_inode = ROOT_INODE;
    sb.inode_size = INODE_SIZE;
    struct inode x;
    sb.inode_isize_off = (int)&x.i_size - (int)&x;
    sb.inode_start_off = (int)&x.i_start_sect - (int)&x;
    sb.dir_ent_size = DIR_ENTRY_SIZE;
    struct dir_entry de;
    sb.dir_ent_inode_off = (int)&de.inode_nr - (int)&de;
    sb.dir_ent_fname_off = (int)&de.name - (int)&de;

    memset(fsbuf, 0x90, SECTOR_SIZE);
    memcpy(fsbuf, &sb, SUPER_BLOCK_SIZE);

    /* write the super block */
    WR_SECT(ROOT_DEV, 1);

    printl("devbase:0x%x00, sb:0x%x00, imap:0x%x00, smap:0x%x00\n",
           geo.base * 2,
           (geo.base + 1) * 2,
           (geo.base + 1 + 1) * 2,
           (geo.base + 1 + 1 + sb.nr_imap_sects) * 2);
    printl("        inodes:0x%x00, 1st_sector:0x%x00\n",
           (geo.base + 1 + 1 + sb.nr_imap_sects + sb.nr_smap_sects) * 2,
           (geo.base + sb.n_1st_sect) * 2);

    /* inode map */
    memset(fsbuf, 0, SECTOR_SIZE);
    for (i = 0; i < (NR_CONSOLES + 2); i++) {
        fsbuf[0] |= 1 << i;
    }

    assert(fsbuf[0] == 0x1F); /* 0001 1111 : 
                               *    | ||||
                               *    | |||`--- bit 0 : reserved
                               *    | ||`---- bit 1 : the first inode,
                               *    | ||              which indicates `/'
                               *    | |`----- bit 2 : /dev_tty0
                               *    | `------ bit 3 : /dev_tty1
                               *    `-------- bit 4 : /dev_tty2
                               */
    WR_SECT(ROOT_DEV, 2);

    /* sector map */
    memset(fsbuf, 0, SECTOR_SIZE);
    int nr_sects = NR_DEFAULT_FILE_SECTS + 1;
    /*             ~~~~~~~~~~~~~~~~~~~|~   |
     *                                |    `--- bit 0 is reserved
     *                                `-------- for `/'
     */
    for (i = 0; i < nr_sects / 8; i++) {
        fsbuf[i] = 0xFF;
    }

    for (j = 0; j < nr_sects % 8; j++) {
        fsbuf[i] |= (1 << j);
    }

    WR_SECT(ROOT_DEV, 2 + sb.nr_imap_sects);

    /* zeromemory the rest sector-map */
    memset(fsbuf, 0, SECTOR_SIZE);
    for (i = 1; i < sb.nr_smap_sects; i++) {
        WR_SECT(ROOT_DEV, 2 + sb.nr_imap_sects + i);
    }

    /* inodes */
    /* inode of '/' */
    memset(fsbuf, 0, SECTOR_SIZE);
    struct inode * pi = (struct inode*)fsbuf;
    pi->i_mode = I_DIRECTORY;
    pi->i_size = DIR_ENTRY_SIZE * 4; /* 4 files:
                                      * '.'
                                      * 'dev_tty0', 'dev_tty1', 'dev_tty2'
                                      */
    pi->i_start_sect = sb.n_1st_sect;
    pi->i_nr_sects = NR_DEFAULT_FILE_SECTS;
    /* inode of '/dev_tty0~2' */
    for (i = 0; i < NR_CONSOLES; i++) {
        pi = (struct inode*)(fsbuf + (INODE_SIZE * (i + 1)));
        pi->i_mode = I_CHAR_SPECIAL;
        pi->i_size = 0;
        pi->i_start_sect = MAKE_DEV(DEV_CHAR_TTY, i);
        pi->i_nr_sects = 0;
    }
    WR_SECT(ROOT_DEV, 2 + sb.nr_imap_sects + sb.nr_smap_sects);

    /* '/' */
    memset(fsbuf, 0, SECTOR_SIZE);
    struct dir_entry * pde = (struct dir_entry *)fsbuf;

    pde->inode_nr = 1;
    strcpy(pde->name, ".");

    /* dir entries of '/dev_tty0~2' */
    for (i = 0; i < NR_CONSOLES; i++) {
        pde++;
        pde->inode_nr = i + 2; /* dev_tty0's inode_nr is 2 */
        sprintf(pde->name, "dev_tty%d", i);
    }
    WR_SECT(ROOT_DEV, sb.n_1st_sect);
}

/**
 * <Ring 1> R/W a sector via messaging with the corresponding driver.
 * 
 * @param io_type   DEV_READ or DEV_WRITE
 * @param dev       device nr
 * @param pos       Byte offset from/to where to r/w.
 * @param bytes     r/w count in bytes.
 * @param proc_nr   To whom the buffer belongs.
 * @param buf       r/w buffer.
 * 
 * @return Zero if success.
 */
PUBLIC int rw_sector(int io_type, int dev, u64 pos, int bytes, int proc_nr, void* buf)
{
    MESSAGE driver_msg;

    driver_msg.type = io_type;
    driver_msg.DEVICE = MINOR(dev);
    driver_msg.POSITION = pos;
    driver_msg.BUF = buf;
    driver_msg.CNT = bytes;
    driver_msg.PROC_NR = proc_nr;
    assert(dd_map[MAJOR(dev)].driver_nr != INVALID_DRIVER);
    send_recv(BOTH, dd_map[MAJOR(dev)].driver_nr, &driver_msg);

    return 0;
}

代码中我们修改了task_fs(),让它调用函数init_fs(),而init_fs()在打开ROOT_DEV之后调用了mkfs(),这便是建立文件系统的函数了。

mkfs()有一点长,它分为这么几个部分:

  • 向硬盘驱动程序索取ROOT_DEV的起始扇区和大小;
  • 建立超级块(Super Block);
  • 建立 inode-map;
  • 建立 sector-map;
  • 写入 inode_array;
  • 建立根目录文件。

我们先来看根目录文件。它很类似于FAT12中的根目录区,但跟那里不同,在这里本质上它也是个文件。它有自己的i-node,于是在sector-map中理应占有一个位置。同时它跟一个普通文件一样占有一些连续的扇区,以便存放所有文件的列表。我们为它预留了NR_DEFAULT_FILE_SECTS个扇区,当然这些扇区应该能容纳足够多个dir_entry,至少不少于系统内i-node数目最大值。根据惯例,我们也称根目录为ROOT,写作“/”。由于我们的文件系统是扁平的,没有子目录,于是所有文件都能写成“/XXXX”的形式。

开头索取ROOT_DEV起始扇区和大小的工作是由向硬盘驱动程序发送DEV_IOCTL消息来完成的。

接下来是建立超级块,这时需要做一个决定,那就是文件系统内i-node数目的上限是多少。我们决定最多允许有4096个i-node,这样只需要一个扇区来做inode-map就够了(4096为一个扇区内的bit数)。这个决定同时意味着我们的文件系统最多容纳4096个文件。

另外还有两项是我们定义的宏,一个是MAGIC_V1,一个是ROOT_INODE。魔数是个标识而已,可以根据个人喜好指定一个,只要自己的操作系统认识并且不会跟其它文件系统冲突就行。ROOT_INODE是根目录文件的i-node,这里我们指定为1(定义在include/const.h中),第0号i-node我们保留起来,用以表示“不正确的i-node”,或者“没有inode”。

超级块占有一个扇区,除我们使用的各项之外,其余各字节我们初始化成了0x90,这样可以顺便测试一下写扇区功能。

再接下来是建立inode-map这个位图,它的每一位都映射到inode-array中的一个i-node。如果一个i-node被使用了,inode-map中相应的位就应该是1,反之则为0。第0个i-node被保留,这里置为了1,第一个是ROOT_INODE,也置为1,很快我们就要填充具体的i-node。另外,我们还添加了三个文件,分别是dev_tty0、dev_tty1和dev_tty2,它们三个是特殊文件,我们将来会用到,这里先占个位置,也置为1。所以整个的inode-map目前只使用了第一个字节,其值为0x1F。

再下面轮到sector-map了,它的每一位表示一个扇区的使用情况。如果第i个扇区被使用了,那么sector-map中的第i位应为1,这跟inode-map是类似的。sector-map中的第0位也被保留,由于第一个能用做数据区的扇区是第sb.n_1st_sect块,所以sector-map的第1位理应对应sb.n_1st_sect,这在以后的编码过程中需要注意。由于根目录占用NR_DEFAULT_FILE_SECTS个扇区,加上一个保留位,我们总共需要设置(NR_DEFAULT_FILE_SECTS+1)个1。

下面到了写具体的i-node了——我们一直称这块数据为inode_array,因为它其实也是个大数组(array)。注意写i-node的顺序一定要跟inode-map中一致,先是ROOT_INODE,然后是dev_tty[0,1,2]。

ROOT_INODE的i_mode为I_DIRECTORY,因为它是目录文件。目录中目前存在四个条目,分别为“.”、“dev_tty0”、“dev_tty1”和“dev_tty2”,所以它的i_size是DIR_ENTRY_SIZE×4。根目录文件是数据区的第一个文件,所以它的i_start_sect值为sb.n_1st_sect,i_nr_sects如前所述被赋值为NR_DEFAULT_FILE_SECTS。

dev_tty[0,1,2]三个文件的i_mode为I_CHAR_SPECIAL,我们可把它们称为字符设备特殊文件。关于什么是字符设备,我们以后再说,这里需要了解的是,i_start_sect一项因为它们“特殊文件”的身份而代表了与普通文件完全不同的意义。在普通文件中,它表示文件开始于哪个扇区,而在这里,它表示文件代表设备的设备号。与此同时,i_size和i_nr_sects都赋值为0,因为在磁盘上它们都不占有任何空间。

根目录文件的具体内容就比较好理解了,只是要注意,每个文件都要与相应的i-node对应起来。

在mk_fs()中,所有写入磁盘的内容都是先放进fsbuf这个缓冲区的。与通常做法不同,我们这次没有定义为一个数组,而是定义了一个指针,让它指向0x600000:

代码 kernel/global.c,FS缓冲区。

/* 6MB~7MB: buffer for FS */
PUBLIC u8 * fsbuf = (u8*)0x600000;
PUBLIC const int FSBUF_SIZE = 0x100000;

也就是说,我们指定内存地址6MB~7MB为文件系统的缓冲区,一定程度上,这大概也算是一种低级形态的“内存管理”吧。

在整个建立FS的过程中,写扇区的函数都是由WR_SECT这个宏来完成的,它的定义如下。

代码 include/fs.h,读写扇区的宏。

/**
 * Since all invocations of 'rw_sector()' in FS look similar (most of the 
 * params are the same), we use this macro to make code more readable.
 */
#define RD_SECT(dev, sect_nr) rw_sector(DEV_READ, \
                                        dev, \
                                        (sect_nr) * SECTOR_SIZE, \
                                        SECTOR_SIZE, /* read one sector */ \
                                        TASK_FS, \
                                        fsbuf)
#define WR_SECT(dev, sect_nr) rw_sector(DEV_WRITE, \
                                        dev, \
                                        (sect_nr) * SECTOR_SIZE, \
                                        SECTOR_SIZE, /* read one sector */ \
                                        TASK_FS, \
                                        fsbuf)

为了通用性着想,rw_sector()这个函数参数很多,使用一个宏可以让代码整洁一些。

代码中使用到了port_write()函数,该函数的作用是向指定的端口写入数据,代码如下所示。

代码 lib/kliba.asm,port_write。

global port_write
...
; void port_write(u16 port, void* buf, int n);
port_write:
    mov edx, [esp + 4] ; port
    mov esi, [esp + 4 + 4] ; buf
    mov ecx, [esp + 4 + 4 + 4] ; n
    shr ecx, 1
    cld
    rep outsw
    ret

好了,mkfs()已经写好了,现在我们来执行一下,结果如下图所示。

根据运行的输出可知:

  • 超级块开始于0xC00200(字节偏移,下同)
  • inode-map开始于0xC00400
  • sector-map开始于0xC00600
  • inode_array开始于0xC01200
  • 根目录文件开始于0xC21200

下面我们就来看一看磁盘中的实际内容:

一下子,我们刚才写入磁盘的结果就呈现在眼前了。

欢迎关注我的公众号

文章来源:https://blog.csdn.net/zorro_z/article/details/135537974
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。