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

news2024/11/20 8:39:08

上一篇文章记录了文件系统,我们将硬盘映像进行了分区,并且清楚了如何获取分区信息。但是硬盘驱动程序目前只能处理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

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

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

欢迎关注我的公众号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1375367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++力扣题目110--平衡二叉树

给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;t…

YOLOv8 + openVINO 多线程数据读写顺序处理

多线程数据读写顺序处理 一个典型的生产者-消费者模型&#xff0c;在这个模型中&#xff0c;多个工作线程并行处理从共享队列中获取的数据&#xff0c;并将处理结果以保持原始顺序的方式放入另一个队列。 多线程处理模型&#xff0c;具体细节如下&#xff1a; 1.数据:数据里必…

第三站:C/C++基础-二维数组

二维数组的概念 一维数组本身是多个大小相同的内存块,从0开始逐渐递增所组成的在横向上的有序"组合", 二维数组就是很多个一维数组在纵向上的组合,每一个一维数组就是二维数组在纵向上的从0开始的逐渐递增的一个单位,(所以一维数组在二维数组的基础上,每一个内存块…

AcWing1210-连号区间

文章目录 题目输入格式输出格式数据范围样例输入样例1输出样例1输入样例2输出样例2样例解释 思路代码 题目 输入格式 输出格式 数据范围 样例 输入样例1 4 3 2 4 1 输出样例1 7 输入样例2 5 3 4 2 5 1 输出样例2 9 样例解释 思路 固定L&#xff0c;遍历R在[L,R]区域中找到最大…

Java并发之互斥一:管程

1、简单聊聊什么是管程模型 &#xff08;共享资源&#xff09;&#xff1a;定义一个共享变量&#xff0c;可以理解锁&#xff0c;令牌这类的东西&#xff08;互斥访问共享资源&#xff09;&#xff1a;获取这个锁、令牌的时候是排好队的&#xff0c;只允许单线程访问&#xff…

《射雕三部曲》人物关系可视化及问答系统

背景&#xff1a; 该项目旨在构建一个基于图数据库和知识图谱的《射雕三部曲》人物关系可视化及问答系统。通过分析小说中的人物关系&#xff0c;将其构建成图数据库&#xff0c;并结合问答系统和数据分析技术&#xff0c;提供用户可视化的人物关系展示和相关问题的回答。 介绍…

快速排序-排序算法

算法思想 快速排序采用的仍然是分治的思想。 Step1.每次在无序的序列中选取一个基准数。 Step2.然后将大于和小于基准数的元素分别放置于基准数两边。&#xff08;前面部分的元素均小于或等于基准数&#xff0c;后面部分均大于或等于基准数&#xff09; Step3.然后采用分治法&…

mySQL 汇总

登录MySQL winR 打开查询命令 输入 cmd 输入net start MySQL 打开mysql 报错:系统错误&#xff0c;拒绝访问 &#xff08;没权限&#xff01;&#xff09; 解决办法&#xff1a;搜索栏查询‘cmd’ 使用管理员身份运行 &#xff08;或鼠标右键‘开始’&#xff0c;windows po…

YOLOv5 损失函数改进 | 引入 Shape-IoU 考虑边框形状与尺度的度量

🗝️改进YOLOv8注意力系列一:结合ACmix、Biformer、BAM注意力机制 论文讲解加入代码本文提供了改进 YOLOv8注意力系列包含不同的注意力机制以及多种加入方式,在本文中具有完整的代码和包含多种更有效加入YOLOv8中的yaml结构,读者可以获取到注意力加入的代码和使用经验,总…

学生评教,问卷调查表评价教师统计,python+pandas处理数据

先上一个结果表格 几个关键步骤 1、问卷网站上设置相关题目,条目,最好用评分题目(点击文本选项,但是保存下来的是分值),如图 2、pandas清洗数据,包括unstack,其目的是把所有学生得分细分展开,因为班级选科不同,问卷上针对的教师不同,比如有些学生需要评价物理教师…

关于‘ Mybatis中的动态SQL语句 ‘解析

1、什么是动态SQL MyBatis中的动态SQL是一种可以根据不同条件生成不同SQL语句的技术。它允许我们在映射文件中编写灵活的SQL语句&#xff0c;以便根据参数的不同情况来动态生成SQL语句。这种灵活性使得我们能够根据应用程序的需求来构建动态的查询语句。 2、动态SQL的作用 动…

如何在Github上快速下载代码

由于网络环境问题&#xff0c;有时候比较难从Github上下载代码&#xff0c;我归纳了以下三种从Github上下载代码的方法&#xff0c;如何选择使用&#xff0c;可根据你的实际情况&#xff1a; 目录 方法一&#xff1a;使用 “Download ZIP” 按钮 方法二&#xff1a;使用 Git…

使用AUTOSAR来开发汽车基础软件的优点

1、高质量。以前我们采用手写代码的方式&#xff0c;是几个工程师在战斗。现在我们采用平台&#xff0c;BSW代码都是供应商提供的&#xff0c;我们相当于后面还有一个团队陪着我们在战斗。 2、低成本。大家都说采用AUTOSAR平台好贵&#xff0c;但是从长远来看是值得的&#xff…

服务器感染了.pings勒索病毒,如何确保数据文件完整恢复?

导言&#xff1a; 随着科技的不断进步&#xff0c;网络犯罪也在不断演变。其中之一的.pings勒索病毒是一种危险的恶意软件&#xff0c;它能够加密用户的数据文件&#xff0c;并要求支付赎金以解密这些文件。在本文中&#xff0c;91数据恢复将介绍.pings勒索病毒&#xff0c;以…

回归预测 | Matlab基于SMA+WOA+SFO-LSSVM多输入单输出回归预测

回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测 目录 回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 SMAWOASFO-LSSVM回归预测 基于黏菌算法鲸鱼算法向日葵算法优化LSSVM回归预测 其中包含三种改进…

常用机床类型的用途和介绍

随着市场对机加工需求的提升&#xff0c;机械加工的技术精度也随之提高&#xff0c;机床的种类也就越来越多。 根据加工方法和使用的工具进行分类&#xff0c;国家将机床编制为11类&#xff1a;车床、钻床、镗床、磨床、齿轮加工机床、螺纹加工机床、铣床、刨床、拔床、锯床等…

Frps服务端一键配置脚本搭建记录

安装&#xff0c;一般修改80&#xff0c;443端口&#xff0c;其他默认回车 Gitee wget https://gitee.com/mvscode/frps-onekey/raw/master/install-frps.sh -O ./install-frps.sh chmod 700 ./install-frps.sh ./install-frps.sh installGithub wget https://raw.githubuse…

2024美赛数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&…

5个不买后悔的云服务器推荐(2024年更新)

作为多年站长使市面上大多数的云厂商的云服务器都使用过&#xff0c;很多特价云服务器都是新用户专享的&#xff0c;本文有老用户特价云服务器&#xff0c;阿腾云atengyun.com有多个网站、小程序等&#xff0c;国内头部云厂商阿里云、腾讯云、华为云、UCloud、京东云都有用过&a…

Linxu每日智囊

每日分享三个Linux命令,悄悄培养读者的Linux技能。 欢迎关注公众号(NLP Research) apt 作用 包管理器 语法 apt [选项] 软件包 参数: -h:帮助-y:当安装过程提示选择全部为"yes"-q:不显示安装的过程案例 列出所有可更新的软件清单命令sudo apt update升级软…