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

发布时间:2024年01月13日

上一篇文章完善了硬盘驱动程序,并且完成了一个文件系统。这篇文章开始记录对文件的操作,首先记录如何创建一个文件。

目录

创建文件

Linux 下的文件操作

文件描述符(file descriptor)

open()

创建文件所涉及的其它函数

strip_path()

search_file()

get_inode()和sync_inode()

init_fs()

read_super_block()和get_super_block()

关闭文件

查看已创建的文件


创建文件

我们现在已经有了一个文件系统存在于我们的虚拟磁盘上。但是这仅仅是个静态的系统,我们还无法对文件进行添加、删除、修改等操作,而且现在磁盘上还没有一个真正的“普通”文件。不过有了我们这个初级形态的文件系统,接下来只需要一步一步来做就好了。

Linux 下的文件操作

要进行修改和删除操作,首先要有文件才行,所以第一步我们先写代码来创建文件。不过我们同时要考虑文件系统的对外接口,因为文件系统并不“主动”对文件进行操作,它总是根据用户进程的请求而做相应的工作。我们不妨先看一下在Linux系统下用系统调用来进行文件操作的过程,代码如下所示。

代码 tmp/f.c,Linux下用系统调用读写文件。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>

int main()
{
    int fd;
    int n;
    const char filename[] = "blah";
    const char bufw[] = "abcde";
    const int rd_bytes = 3;
    char bufr[rd_bytes];

    assert(rd_bytes <= strlen(bufw));

    /* create & write */
    fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
    if (fd == -1) {
        printf("failed to open %s\n", filename);
        return 1;
    }

    n = write(fd, bufw, strlen(bufw));
    if (n != strlen(bufw)) {
        printf("failed to write to %s\n", filename);
        close(fd);
        return 2;
    }

    close(fd);

    /* open & read */
    fd = open(filename, O_RDWR);
    if (fd == -1) {
        printf("failed to open %s\n", filename);
        return 3;
    }

    n = read(fd, bufr, rd_bytes);
    if (n != rd_bytes) {
        printf("failed to read from %s\n", filename);
        close(fd);
        return 4;
    }
    bufr[n] = 0;
    printf("%d bytes read: %s\n", n, bufr);

    close(fd);

    return 0;
}

这是一段非常简单的对文件进行创建、读写以及关闭的代码,其中用到了open()、write()、read()、close()等几个系统调用。显然,如果我们的文件系统向用户进程提供服务的话,也要实现这些系统调用,所以我们最好模仿一下它们的行为。它的声明如下:

int open(const char *pathname, int flags, mode_t mode);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
int close(int fd);

接下来,我们要做的就是实现这些系统调用,只是在功能上,我们将做的简单一些。

文件描述符(file descriptor)

Linux系统文件操作都使用到了一个变量(或者返回值),那就是fd,即“file descriptor”,是它“代表”了一个文件,理所应当地充当了整个过程中最重要的角色。如果对它不是很了解的情况下,下图可以帮助建立一点初步的认识。

该图描述的是我们即将使用的文件操作方案。每个进程表中都将增加一个filp数组,其成员是指向file descriptor(下文简称fd)的指针。每一个使用中的fd都具有一个指针指向一个inode结构体,而由这个inode结构体可以找到具体的文件。

现在我们来修改进程表,在进程表中添加filp数组。

代码 include/proc.h,修改进程表。

typedef struct s_proc {
...
    struct file_desc * filp[NR_FILES];
}PROCESS;

我们的fd的定义代码如下所示。

代码 include/fs.h,file descriptor。

/**
 * @struct file_desc
 * @brief Pile Descriptor
 */
struct file_desc {
    int fd_mode;                /*< R or W */
    int fd_pos;                 /*< Current position for R/W */
    struct inode* fd_inode;     /*< Ptr to the i-node  */
};

这是一个简单的结构体,fd_mode用来记录这个fd是用来做什么操作的,比如读、写,或是既读又写。fd_pos用来记录读写到了文件的什么位置。fd_inode便是指向inode的指针了。

每当一个进程打开一个文件——无论是打开一个已存在的还是创建一个新的,该进程的进程表的filp数组中就会分配一个位置——假设是k,用于存放打开文件的fd指针,而这个k就是返回给用户进程的open()函数的返回值了。这便是我们在代码中看到的变量fd的真正含义——它其实是一个数组下标,循着这个下标,系统可以找到用以描述文件的数据结构。

这里可能会有疑问,既然三部分要连接起来,我们为何不将它们合并起来呢?这个问题可以从两方面考虑。一方面f_desc_table[]和inode_table[]不能合并,我们不能把fd_mode和fd_pos等值塞入i-node,因为不同的进程可以打开同一个文件,而且打开之后可能进行不同的操作——比如读取其不同的位置,所以合并后两个表是行不通的。另一方面,我们也不能将f_desc_table[]直接放入进程表。

open()

现在我们可以着手编写open()了。我们先在用户进程中装模作样地创建一个文件:

代码 kernel/main.c,创建一个文件。

void TestA()
{
    int fd = open("/blah", O_CREAT);
    printf("fd: %d\n", fd);
    close(fd);
    spin("TestA");
}

跟Linux中的open()系统调用不同,这里我们只用了两个参数,第三个参数我们省略了,访问权限等问题以后再考虑。

调用了open(),但我们压根儿还没有这个函数呢,现在就来创建一个,代码如下。

代码 lib/open.c,open(),这是新建的文件。

/**
 * open/create a file.
 * 
 * @param pathname  The full path of the file to be opened/created.
 * @param flags     O_CREAT, O_RDWR, etc.
 * 
 * @return File descriptor if successful, otherwise -1.
 */
PUBLIC int open(const char *pathname, int flags)
{
    MESSAGE msg;

    msg.type = OPEN;

    msg.PATHNAME = (void*)pathname;
    msg.FLAGS = flags;
    msg.NAME_LEN = strlen(pathname);

    send_recv(BOTH, TASK_FS, &msg);
    assert(msg.type == SYSCALL_RET);

    return msg.FD;
}

我们发送了一个OPEN消息给文件系统,所以文件系统需要处理它:

代码 fs/main.c,文件系统处理OPEN消息。

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

    while (1) {
        send_recv(RECEIVE, ANY, &fs_msg);

        int src = fs_msg.source;
        pcaller = &proc_table[src];

        switch (fs_msg.type) {
            case OPEN:
                fs_msg.FD = do_open();
                break;
            case CLOSE:
                fs_msg.RETVAL = do_close();
                break;
            default:
                dump_msg("FS::unknown message:", &fs_msg);
                assert(0);
                break;
        }

        /* reply */
        fs_msg.type = SYSCALL_RET;
        send_recv(SEND, src, &fs_msg);
    }
}

这里我们用一个专门的函数do_open()来处理OPEN消息,代码如下:

代码 fs/open.c,do_open,这是新建的文件。

/**
 * Open a file and return the file descriptor.
 * 
 * @return File descriptor if successful, otherwise a negative error code.
 */
PUBLIC int do_open()
{
    int fd = -1; /* return value */

    char pathname[MAX_PATH];

    /* get parameters from the message */
    int flags = fs_msg.FLAGS;       /* access mode */
    int name_len = fs_msg.NAME_LEN; /* length of filename */
    int src = fs_msg.source;        /* caller proc nr. */
    assert(name_len < MAX_PATH);
    phys_copy((void*)va2la(TASK_FS, pathname),
              (void*)va2la(src, fs_msg.PATHNAME),
              name_len);
    pathname[name_len] = 0;

    /* fint a free slot in PROCESS::flip[] */
    int i;
    for (i = 0; i < NR_FILES; i++) {
        if (pcaller->filp[i] == 0) {
            fd = i;
            break;
        }
    }
    if ((fd < 0) || (fd >= NR_FILES)) {
        panic("filp[] is full (PID:%d)", proc2pid(pcaller));
    }

    /* find a free slot in f_desc_table[] */
    for (i = 0; i < NR_FILE_DESC; i++) {
        if (f_desc_table[i].fd_inode == 0) {
            break;
        }
    }
    if (i >= NR_FILE_DESC) {
        panic("f_desc_table[] is full (PID:%d)", proc2pid(pcaller));
    }

    int inode_nr = search_file(pathname);

    struct inode * pin = 0;
    if (flags & O_CREAT) {
        if (inode_nr) {
            printl("file exists.\n");
            return -1;
        } else {
            pin = create_file(pathname, flags);
        }
    } else {
        assert(flags & O_RDWR);

        char filename[MAX_PATH];
        struct inode * dir_inode;
        if (strip_path(filename, pathname, &dir_inode) != 0) {
            return -1;
        }
        pin = get_inode(dir_inode->i_dev, inode_nr);
    }

    if (pin) {
        /* connects proc with file_descriptor */
        pcaller->filp[fd] = &f_desc_table[i];

        /* connects file_descriptor with inode */
        f_desc_table[i].fd_inode = pin;

        f_desc_table[i].fd_mode = flags;
        f_desc_table[i].fd_pos = 0;

        int imode = pin->i_mode & I_TYPE_MASK;

        if (imode == I_CHAR_SPECIAL) {
            MESSAGE driver_msg;

            driver_msg.type = DEV_OPEN;
            int dev = pin->i_start_sect;
            driver_msg.DEVICE = MINOR(dev);
            assert(MAJOR(dev) == 4);
            assert(dd_map[MAJOR(dev)].driver_nr != INVALID_DRIVER);

            send_recv(BOTH, dd_map[MAJOR(dev)].driver_nr, &driver_msg);
        } else if (imode == I_DIRECTORY) {
            assert(pin->i_num == ROOT_INODE);
        } else {
            assert(pin->i_mode == I_REGULAR);
        }
    } else {
        return -1;
    }

    return fd;
}

/**
 * Create a file and return it's inode ptr.
 * 
 * @param[in] path  The full path of the new file
 * @param[in] flags Attributes of the new file
 * 
 * @return Ptr to i-node of the new file if successful, otherwise 0.
 * 
 * @see open()
 * @see do_open()
 */
PRIVATE struct inode * create_file(char * path, int flags)
{
    char filename[MAX_PATH];
    struct inode * dir_inode;

    if (strip_path(filename, path, &dir_inode) != 0) {
        return 0;
    }

    int inode_nr = alloc_imap_bit(dir_inode->i_dev);

    int free_sect_nr = alloc_smap_bit(dir_inode->i_dev, NR_DEFAULT_FILE_SECTS);

    struct inode * newino = new_inode(dir_inode->i_dev, inode_nr, free_sect_nr);

    new_dir_entry(dir_inode, newino->i_num, filename);

    return newino;
}

do_open()首先是从消息内读出各项参数,其中需要格外注意是文件名的读取。由于跨越了两个特权级,所以得到文件名需要付出额外的两份努力。一是需要事先记下文件名的长度,二是需要用phys_copy()来复制一份。

前面说过,open()要返回的是进程表filp[]内的一个索引,所以一开始我们就在filp[]内寻找一个空项,用来存放即将打开的文件fd。由于filp[]内只保存指针,所以我们还要从f_desc_table[]中找一个空项。这两项工作做完之后,我们调用search_file()来看看要打开的文件是否已经存在。其中的具体细节暂时略过,只需要知道这个函数将返回零,因为我们要创建的文件肯定不存在。

接下来是调用create_file(),这是真正来创建文件的函数了。一个文件在文件系统中涉及的要素有五个:

  • 文件内容(数据)所占用扇区;
  • i-node;
  • i-node在inode-map中占用一位;
  • 数据扇区在sector-map中占用的一位或多位;
  • 文件在目录中占用的目录项(dir entry)。

相应地,我们创建一个文件,需要做以下几项工作:

  • 为文件内容(数据)分配扇区;
  • 在inode_array中分配一个i-node;
  • 在inode-map中分配一位;
  • 在sector-map中分配一位或多位;
  • 在相应目录中写入一个目录项(dir entry)。

在create_file()中,这几项工作分别是由四个函数来完成的:

  • alloc_imap_bit()?在inode-map中分配一位,这也意味着新文件的i-node有了确定的位置。
  • alloc_smap_bit()?在sector-map中分配多位,这也意味着为文件内容分配了扇区。
  • new_inode()?在inode_array中分配一个i-node,并写入内容。
  • new_dir_entry()?在相应目录中写入一个目录项(dir entry)。

这四个函数的定义如下代码所示。

代码 fs/open.c,创建文件需要的函数。

/**
 * Allocate a bit in inode-map.
 * 
 * @param dev   In which device the inode-map is located.
 * 
 * @return I-node nr.
 */
PRIVATE int alloc_imap_bit(int dev)
{
    int inode_nr = 0;
    int i, j, k;

    int imap_blk0_nr = 1 + 1; /* 1 boot sector & 1 super block */
    struct super_block * sb = get_super_block(dev);

    for (i = 0; i < sb->nr_imap_sects; i++) {
        RD_SECT(dev, imap_blk0_nr + i);

        for (j = 0; j < SECTOR_SIZE; j++) {
            /* skip '11111111' bytes */
            if (fsbuf[j] == 0xFF) {
                continue;
            }

            /* skip '1' bits */
            for (k = 0; ((fsbuf[j] >> k) & 1) != 0; k++) {}

            /* i: sector index; j: byte index; k: bit index */
            inode_nr = (i * SECTOR_SIZE + j) * 8 + k;
            fsbuf[j] |= (1 << k);

            /* write the bit to imap */
            WR_SECT(dev, imap_blk0_nr + i);
            break;
        }

        return inode_nr;
    }

    /* no free bit in imap */
    panic("inode-map is probably full.\n");

    return 0;
}

/**
 * Allocate a bit in sector-map.
 * 
 * @param dev               In which device the sector-map is located.
 * @param nr_sects_to_alloc How many sectors are allocated.
 * 
 * @return The 1st sector nr allocated.
 */
PRIVATE int alloc_smap_bit(int dev, int nr_sects_to_alloc)
{
    int i; /* sector index */
    int j; /* byte index */
    int k; /* bit index */

    struct super_block * sb = get_super_block(dev);

    int smap_blk0_nr = 1 + 1 + sb->nr_imap_sects;
    int free_sect_nr = 0;

    for (i = 0; i < sb->nr_smap_sects; i++) { /* smap_blk0_nr + 1: current sect nr. */
        RD_SECT(dev, smap_blk0_nr + 1);

        /* byte offset in current sect */
        for (j = 0; j < SECTOR_SIZE && nr_sects_to_alloc > 0; j++) {
            k = 0;
            if (!free_sect_nr) {
                /* loop until a free bit is found */
                if (fsbuf[j] == 0xFF) {
                    continue;
                }
                for (; ((fsbuf[j] >> k) & 1) != 0; k++) {}
                free_sect_nr = (i * SECTOR_SIZE + j) * 8 + k - 1 + sb->n_1st_sect;
            }

            for (; k < 8; k++) { /* repeat till enough bits are set */
                assert(((fsbuf[j] >> k) & 1) == 0);
                fsbuf[j] |= (1 << k);
                if (--nr_sects_to_alloc == 0) {
                    break;
                }
            }
        }

        if (free_sect_nr) { /* free bit found, write the bits to smap */
            WR_SECT(dev, smap_blk0_nr + i);
        }

        if (nr_sects_to_alloc == 0) {
            break;
        }
    }

    assert(nr_sects_to_alloc == 0);

    return free_sect_nr;
}

/**
 * Generate a new i-node and write it to disk.
 * 
 * @param dev           Home device of the i-node.
 * @param inode_nr      I-node nr.
 * @param start_sect    Start sector of the file pointed by the new i-node.
 * 
 * @return Ptr of the new i-node.
 */
PRIVATE struct inode * new_inode(int dev, int inode_nr, int start_sect)
{
    struct inode * new_inode = get_inode(dev, inode_nr);

    new_inode->i_mode = I_REGULAR;
    new_inode->i_size = 0;
    new_inode->i_start_sect = start_sect;
    new_inode->i_nr_sects = NR_DEFAULT_FILE_SECTS;

    new_inode->i_dev = dev;
    new_inode->i_cnt = 1;
    new_inode->i_num = inode_nr;

    /* write to the inode array */
    sync_inode(new_inode);

    return new_inode;
}

/**
 * Write a new entry into the directory.
 * 
 * @param dir_inode     I-node of the directory.
 * @param inode_nr      I-node nr of the new file.
 * @param filename      Filename of the new file.
 */
PRIVATE void new_dir_entry(struct inode * dir_inode, int inode_nr, char * filename)
{
    /* write the dir_entry */
    int dir_blk0_nr = dir_inode->i_start_sect;
    int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE) / SECTOR_SIZE;
    int nr_dir_entries = dir_inode->i_size / DIR_ENTRY_SIZE; /* including unused slots (the file has been deleted but the slot is still there) */
    int m = 0;
    struct dir_entry * pde;
    struct dir_entry * new_de = 0;

    int i, j;
    for (i = 0; i < nr_dir_blks; i++) {
        RD_SECT(dir_inode->i_dev, dir_blk0_nr + i);

        pde = (struct dir_entry *)fsbuf;
        for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++, pde++) {
            if (++m > nr_dir_entries) {
                break;
            }

            if (pde->inode_nr == 0) { /* it's a free slot */
                new_de = pde;
                break;
            }
        }
        if (m > nr_dir_entries || /* all entryies have been iterated or */
            new_de) {             /* free slot is found */
            break;
        }
    }
    if (!new_de) { /* reached the end of the dir */
        new_de = pde;
        dir_inode->i_size += DIR_ENTRY_SIZE;
    }
    new_de->inode_nr = inode_nr;
    strcpy(new_de->name, filename);

    /* write dir block -- ROOT dir block */
    WR_SECT(dir_inode->i_dev, dir_blk0_nr + i);

    /* update dir inode */
    sync_inode(dir_inode);
}

alloc_imap_bit()和alloc_smap_bit()都是对位图的操作。值得注意的是在sector-map中分配扇区时我们没有考虑“空洞太小”的情况,或者说,我们一旦找到一个未使用的扇区,就认为以它为开头存在连续的nr_sects_to_alloc个扇区。我们这样假设是有道理的,一方面,参数nr_sects_to_alloc应该始终为NR_DEFAULT_FILE_SECTS或者它的倍数,另一方面,只要我们每次都这样分配磁盘,那么理论上,除非在一块磁盘的结尾处,我们不会遇到一处小于NR_DEFAULT_FILE_SECTS的连续扇区。

new_inode()和new_dir_entry()相对简单一些,都是将相应的项赋值后马上写回磁盘,保持内存和磁盘的数据一致。

在do_open()中,调用create_file()之后文件其实就已经创建完毕了,之所以函数没有立刻结束,是因为创建完之后立即进行了“打开”操作,对照图9.12可知,之后是起到连接进程表、f_desc_table[]和inode_table[]的作用,三部分连接起来之后,文件就认为是被“打开”了。然后对文件属性进行判断,如果是字符设备特殊文件的话就交给相应的驱动程序,至于交给哪个驱动程序是由文件的设备号决定的,它实际上应该是TTY进程。我们现在还没有用过特殊文件,所以这段代码暂时不会执行。

到这里,创建文件的主要过程已经清楚了,最核心的函数其实就是由do_open()调用的create_file()。了解了这个主干,我们接下来看看其中涉及的一些细枝末节。

创建文件所涉及的其它函数

strip_path()

首先看看create_file()中用到的strip_path(),代码如下所示。

代码 fs/misc.c,strip_path(),这是新建的文件。

/**
 * Get the basename from the fullpath
 * 
 * In Orange'S FS v1.0, all files are stored in the root dirctory.
 * There is no sub-folder thing.
 * 
 * This routine should be called at the very beginning of file operations 
 * such as open(), read() and write(). It accepts the full path and returns 
 * two things: the basename and a ptr of the root dir's i-node.
 * 
 * e.g. After strip_path(filename, "/blah", ppinode) finishes, we get:
 *      - filename: "blan"
 *      - *ppinode: root_inode
 *      - ret val: 0 (successful)
 * 
 * Currently an acceptable pathname should begin with at most one '/' preceding a filename.
 * 
 * Filenames may contain any character except '/' and '\\0'.
 * 
 * @param[out]  filename The String for the result.
 * @param[in]   pathname The full pathname.
 * @param[out]  ppinode  The ptr of the dir's inode will be stored here.
 * 
 * @return Zero if success, otherwise the pathname is not valid.
 */
PUBLIC int strip_path(char * filename, const char * pathname, struct inode** ppinode)
{
    const char * s = pathname;
    char * t = filename;

    if (s == 0) {
        return -1;
    }

    if (*s == '/') {
        s++;
    }

    while (*s) { /* check each character */
        if (*s == '/') {
            return -1;
        }
        *t++ = *s++;
        /* if filename is too long, just truncate it */
        if (t - filename >= MAX_FILENAME_LEN) {
            break;
        }
    }
    *t = 0;

    *ppinode = root_inode;

    return 0;
}

虽然函数名有点晦涩,但代码做的事情很简单:把路径分成文件名和文件夹两部分。比如strip_path(filename, path, &dir_inode)中path是文件的路径,是输入,filename和dir_inode都是输出。函数调用成功之后,filename里将包含“纯文件名”,即不含路径的文件名,dir_inode这个inode指针将指向文件所在文件夹的i-node。

一言蔽之,strip_path()的主要作用便是定位直接包含给定文件的文件夹,并得到给定文件夹在此文件夹中的名称。由于当前我们的文件系统是扁平的,所以这个函数返回之后dir_inode指向的将永远是根目录“/”的i-node。

search_file()

我们前面遇到这个函数时将它略过了,因为它其实是为下一步打开文件的操作而准备的,代码如下所示。

代码 fs/misc.c,search_file()。

/**
 * Search the file and return the inode_nr.
 * 
 * @param[in] path  The full path of the file to search.
 * @return          Ptr to the i-node of the file if successful, otherwise zero.
 * 
 * @see open()
 * @see do_open()
 */
PUBLIC int search_file(char * path)
{
    int i, j;

    char filename[MAX_PATH];
    memset(filename, 0, MAX_FILENAME_LEN);
    struct inode * dir_inode;
    if (strip_path(filename, path, &dir_inode) != 0) {
        return 0;
    }

    if (filename[0] == 0) { /* path: "/" */
        return dir_inode->i_num;
    }

    /* Search the dir for the file */
    int dir_blk0_nr = dir_inode->i_start_sect;
    int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE - 1) / SECTOR_SIZE;
    int nr_dir_entries = dir_inode->i_size / DIR_ENTRY_SIZE; /* including unused slots(the file has been deleted but the slot is still there) */
    int m = 0;
    struct dir_entry * pde;
    for (i = 0; i < nr_dir_blks; i++) {
        RD_SECT(dir_inode->i_dev, dir_blk0_nr + i);
        pde = (struct dir_entry *)fsbuf;
        for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++, pde++) {
            if (memcmp(filename, pde->name, MAX_FILENAME_LEN) == 0) {
                return pde->inode_nr;
            }
            if (++m > nr_dir_entries) {
                break;
            }
        }
        if (m > nr_dir_entries) { /* all entries have been iterated */
            break;
        }
    }

    /* file not found */
    return 0;
}

我们还是通过strip_path()来得到文件所在目录的i-node,通过这个i-node来得到目录所在的扇区,然后读取这些扇区,查看里面是否有我们要找的文件,如果找到就返回文件的i-node,如果没找到就返回零。

这里使用了一个memcmp()比较相等的函数,它的具体定义如下所示。

代码 lib/misc.c,memcmp()。

/**
 * Compare memory areas.
 * 
 * @param s1  The 1st area.
 * @param s2  The 2nd area.
 * @param n   The first n bytes will be compared.
 * 
 * @return  an integer less than, equal to, or greater than zero if the first
 *          n bytes of s1 is found, respectively, to be less than, to match,
 *          or  be greater than the first n bytes of s2.
 */
PUBLIC int memcmp(const void * s1, const void *s2, int n)
{
    if ((s1 == 0) || (s2 == 0)) { /* for robustness */
        return (s1 - s2);
    }

    const char * p1 = (const char *)s1;
    const char * p2 = (const char *)s2;
    int i;
    for (i = 0; i < n; i++,p1++,p2++) {
        if (*p1 != *p2) {
            return (*p1 - *p2);
        }
    }
    return 0;
}

get_inode()和sync_inode()

对于文件而言,i-node无疑是其灵魂,在对文件操作的过程中,从打开到关闭,我们始终需要面对i-node。在这里,我们用了一个缓冲区来存放系统中所有的i-node:inode_table[]。当我们需要用到一个i-node时,我们就在inode_table[]中找一个位置将它放进去,所用到的函数便是这个get_inode,代码如下所示。

代码 fs/main.c,get_inode()。

/**
 * <Ring 1> Get the inode ptr of given inode nr. A cache -- inode_table[] -- is 
 * maintained to make things faster. If the inode requested is already there,
 * just return it. Otherwise the inode will be read from the disk.
 * 
 * @param dev   Device nr.
 * @param num   I-node nr.
 * 
 * @return The inode ptr requested.
 */
PUBLIC struct inode * get_inode(int dev, int num)
{
    if (num == 0) {
        return 0;
    }

    struct inode * p;
    struct inode * q = 0;
    for (p = &inode_table[0]; p < &inode_table[NR_INODE]; p++) {
        if (p->i_cnt) { /* not a free slot */
            if ((p->i_dev == dev) && (p->i_num == num)) {
                /* this is the inode we want */
                p->i_cnt++;
                return p;
            }
        } else { /* a free slot */
            if (!q) { /* q hasn't been assigned yet */
                q = p; /* q <- the 1st free slot */
            }
        }
    }

    if (!q) {
        panic("the inode table is full");
    }

    q->i_dev = dev;
    q->i_num = num;
    q->i_cnt = 1;

    struct super_block * sb = get_super_block(dev);
    int blk_nr = 1 + 1 + sb->nr_imap_sects + sb->nr_smap_sects + ((num - 1) / (SECTOR_SIZE / INODE_SIZE));
    RD_SECT(dev, blk_nr);
    struct inode * pinode = (struct inode*)((u8*)fsbuf + ((num - 1) % (SECTOR_SIZE / INODE_SIZE)) * INODE_SIZE);
    q->i_mode = pinode->i_mode;
    q->i_size = pinode->i_size;
    q->i_start_sect = pinode->i_start_sect;
    q->i_nr_sects = pinode->i_nr_sects;
    return q;
}

/**
 * Decrease the reference nr of a slot in inode_table[]. When the nr reaches 
 * zero, it means the inode is not used any more and can be overwritten by 
 * a new inode.
 * 
 * @param pinode    I-node ptr.
 */
PUBLIC void put_inode(struct inode * pinode)
{
    assert(pinode->i_cnt > 0);
    pinode->i_cnt--;
}

/**
 * <Ring 1> Write the inode back to the disk. Commonly invoked as soon as the 
 *          inode is changed.
 * 
 * @param p I-node ptr
 */
PUBLIC void sync_inode(struct inode * p)
{
    struct inode * pinode;
    struct super_block * sb = get_super_block(p->i_dev);
    int blk_nr = 1 + 1 + sb->nr_imap_sects + sb->nr_smap_sects + ((p->i_num - 1) / (SECTOR_SIZE / INODE_SIZE));
    RD_SECT(p->i_dev, blk_nr);
    pinode = (struct inode*)((u8*)fsbuf + (((p->i_num - 1) % (SECTOR_SIZE / INODE_SIZE)) * INODE_SIZE));
    pinode->i_mode = p->i_mode;
    pinode->i_size = p->i_size;
    pinode->i_start_sect = p->i_start_sect;
    pinode->i_nr_sects = p->i_nr_sects;
    WR_SECT(p->i_dev, blk_nr);
}

如果一个inode已经被读入inode_table[]这个缓冲区了,那么下一次在需要它时,我们不需要再进行一次磁盘I/O,直接从缓冲区中读出来就可以了。在这里我们使用了比较原始的策略来保持磁盘和缓冲区的一致性:一旦内存中的值发生改变,则立即写入磁盘——这一过程由sync-inode()来完成。

对于缓冲区的管理是这样的:如果一个inode的i_cnt项为零,那么此项被认为是未使用,于是可以分配给新读入的i-node。一旦一个i-node读入,那么i_cnt自加。当i-node用完之后,使用者应调用一个put_inode(),这样i_cnt自减。当i_cnt自减至零时,说明不再有人继续使用这个i-node,它就又变成一个空项了。

init_fs()

init_fs()并不是一个新函数,不过其中增加了一些内容,代码如下所示。

代码 fs/main.c,init_fs()。

/**
 * <Ring 1> Do some preparation.
 */
PRIVATE void init_fs()
{
    int i;

    /* f_desc_table[] */
    for (i = 0; i < NR_FILE_DESC; i++) {
        memset(&f_desc_table[i], 0, sizeof(struct file_desc));
    }

    /* inode_table[] */
    for (i = 0; i < NR_INODE; i++) {
        memset(&inode_table[i], 0, sizeof(struct inode));
    }

    /* super_block[] */
    struct super_block * sb = super_block;
    for (; sb < &super_block[NR_SUPER_BLOCK]; sb++) {
        sb->sb_dev = NO_DEV;
    }

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

    /* make FS */
    mkfs();

    /* load super block of ROOT */
    read_super_block(ROOT_DEV);

    sb = get_super_block(ROOT_DEV);
    assert(sb->magic == MAGIC_V1);

    root_inode = get_inode(ROOT_DEV, ROOT_INODE);
}

函数开头初始化了三个缓冲区:f_desc_table[]、inode_table[]和super_block[]。前两者我们已经做了说明,super_block[]使用方法类似,它用来存放超级块。每个分区都对应一个超级块,我们开始使用这个分区时(通常以“打开”设备为标志),会将其超级块读入内存,放入super_block[]这个缓冲区。之后我们可以由函数get_super_block()随时得到其指针。

中间部分是打开根设备和创建分区。对于一个扁平的文件系统,所有的文件都包含在根目录下面,所以根目录文件的i-node地位非常重要,这里我们用了一个全局变量root_inode来存放其指针。

使用到的全局变量和缓冲区定义在global.h中,具体如下所示:

EXTERN  struct file_desc    f_desc_table[NR_FILE_DESC];
EXTERN  struct inode        inode_table[NR_INODE];
EXTERN  struct super_block  super_block[NR_SUPER_BLOCK];
EXTERN  MESSAGE             fs_msg;
EXTERN  PROCESS *           pcaller;
EXTERN  struct inode *      root_inode;

read_super_block()和get_super_block()

前面我们介绍了super_block[],现在要看的这两个函数就是操作它的接口,代码如下所示。

代码 fs/main.c,超级块缓冲区。

/**
 * <Ring 1> Read super block from the given device then write it into a free
 *          super_block[] slot.
 * 
 * @param dev   From which device the super block comes.
 */
PRIVATE void read_super_block(int dev)
{
    int i;
    MESSAGE driver_msg;

    driver_msg.type = DEV_READ;
    driver_msg.DEVICE = MINOR(dev);
    driver_msg.POSITION = SECTOR_SIZE * 1;
    driver_msg.BUF = fsbuf;
    driver_msg.CNT = SECTOR_SIZE;
    driver_msg.PROC_NR = TASK_FS;
    assert(dd_map[MAJOR(dev)].driver_nr != INVALID_DRIVER);
    send_recv(BOTH, dd_map[MAJOR(dev)].driver_nr, &driver_msg);

    /* find a free slot in super_block[] */
    for (i = 0; i < NR_SUPER_BLOCK; i++) {
        if (super_block[i].sb_dev == NO_DEV) {
            break;
        }
    }
    if (i == NR_SUPER_BLOCK) {
        panic("super block slots used up");
    }

    assert(i == 0); /* currently we use only the 1st slot */

    struct super_block * psb = (struct super_block *)fsbuf;

    super_block[i] = *psb;
    super_block[i].sb_dev = dev;
}

/**
 * <Ring 1> Get the super block from super_block[].
 * 
 * @param dev   Device nr.
 * 
 * @return Super block ptr.
 */
PUBLIC struct super_block * get_super_block(int dev)
{
    struct super_block * sb = super_block;
    for (; sb < &super_block[NR_SUPER_BLOCK]; sb++) {
        if (sb->sb_dev == dev) {
            return sb;
        }
    }

    panic("super block of device %d not found.\n", dev);

    return 0;
}

代码中,read_super_block()的作用是将一个设备的超级块读入缓存,get_super_block()可得到给定设备的超级块指针。在获取超级块时,我们采用了比较原始的遍历法来找到哪个是我们需要的。

关闭文件

跟创建文件比起来,关闭文件显得十分简单,由代码可知,CLOSE消息是由do_close()来处理的,它的函数体代码如下所示。

代码 fs/open.c,关闭文件。

/**
 * Handle the message CLOSE.
 * 
 * @return Zero if success.
 */
PUBLIC int do_close()
{
    int fd = fs_msg.FD;
    put_inode(pcaller->filp[fd]->fd_inode);
    pcaller->filp[fd]->fd_inode = 0;
    pcaller->filp[fd] = 0;

    return 0;
}

代码主要是三个语句:

  • 调用put_inode()释放inode_table[]中的条目;
  • 将filp[fd]->fd_inode清零释放f_desc_table[]中的条目;
  • 将filp[fd]清零释放进程表中的fd条目。

经过这三个语句,之前为文件所分配的资源就全部释放了,文件也就可被认为是“关闭”了。

好了,现在我们就差最后一个close()函数了,现在来编写它,代码如下所示。

代码 lib/close.c,close(),这是新建的文件。

/**
 * Close a file descriptor.
 * 
 * @param fd  File descriptor.
 * 
 * @return Zero if successful, otherwise -1.
 */
PUBLIC int close(int fd)
{
    MESSAGE msg;
    msg.type = CLOSE;
    msg.FD = fd;

    send_recv(BOTH, TASK_FS, &msg);

    return msg.RETVAL;
}

查看已创建的文件

终于完成了open()和close()两个系统调用,我们现在可以make运行一下,看一下效果了,不要忘记,由于新增了几个C文件,Makefile需要进行相应的修改。运行结果如下图所示。

可以看到,进程TestA打印出了新创建的文件的fd:0。根据我们的代码,这个数字应该是正确的,不过文件到底有没有创建成功,我们还是要看看磁盘映像:

可以看到,inode-map变成了二进制的“11 1111”(十六进制3Fh),文件系统创建之初这里是1Fh,这里第5位(从0开始数)由0变成了1。也就是说新增加了一个i-node,其编号是5。

我们马上看一下inode_array,从1开始数,数到5是c01280处,对照inode结构体的声明可知:

  • i_mode为8000h,这正是I_REGULAR的值。
  • i_size为0,因为目前它还是个空文件。
  • i_start_sect为909h。从超级块内容可知,sb.n_1st_sect为109h,由于第109h扇区占用sector-map中第1位(第0位保留),所以第909h扇区占用第801h位。
  • i_nr_sects为800h(十进制2048),正是NR_DEFAULT_FILE_SECTS的值,结合i_start_sect可知,文件在sector-map中占用第801h到第1000h位。

在文件系统刚刚建立起来的时候,sector-map第0h位到第800h位被占,其中第0h位是保留位,第1h位到第800h位(共800h位)属于根目录文件,在这里我们新建的文件“/blah”又占用了第801h位到第1000h位,换算出来,从第0字节到第1FFh字节(共计200字节)都被占满,另外第200h字节被占用了1位。这跟我们得到的磁盘映像的信息是吻合的。

再来看一下根目录文件,很容易发现新增加的条目,其i-node号为5,这跟inode-map中是对应的,文件名是“blah”。所有信息都是正确的,这意味着我们的文件创建工作成功了。

这里可能会有一个问题,就是i-node号和inode-map中的位置是件容易迷糊的事情,我们不妨再来理顺一下:

对根目录而言,i-node号为1(ROOT_INODE定义为1),在inode-map中占用第1位(从0开始数),具体i-node数据位于inode_array[0]。于是,第M号i-node在inode-map中占用第M位(从0开始数),具体i-node数据位于inode_array[M-1]。

我们还可以知道,inode_array中的第M项(从0开始数)对应第M+1号i-node以及inode-map中的第M+1位(从0开始数)。

与此类似,根目录区的开始扇区即为第sb.n_1st_sect扇区,占用sector-map中的第1位(从0开始数)。于是,第M扇区(以本分区的开始扇区为0扇区)对应sector-map中的第(M-super_block.n_1st_sect+1)位。同时sector-map中的第M位对应第(M-1+super_block.n_1st_sect)扇区。

欢迎关注我的公众号

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