《ORANGE’S:一个操作系统的实现》读书笔记(三十七)尾声(一)

发布时间:2024年01月23日

这里已经到了《ORANGE’S:一个操作系统的实现》这本书的最后一章了,最后一章的内容不像之前的章节有个主题,而是做一些之前没有做过的东西。包括:让mkfs()只执行一次、从硬盘引导、将OS安装到真实的计算机。这篇文章记录尾声的第一个内容:让mkfs()只执行一次。

让mkfs()只执行一次

目前我们的系统每次启动都是“全新”的,因为每一次init_fs()都会调用mkfs()刷新硬盘,一定程度上这比较有利于我们调试——每次启动时可保证文件系统是一样的,但它也存在明显的坏处,那就是上次建立的文件到下一次启动时就不见了。我们下面就来改变这一现状,代码如下所示。

代码 fs/main.c,让mkfs()只执行一次。

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

    /* read the super block of ROOT DEVICE */
    RD_SECT(ROOT_DEV, 1);

    sb = (struct super_block *)fsbuf;
    if (sb->magic != MAGIC_V1) {
        printl("{FS} mkfs\n");
        mkfs(); /* make FS */
    }
...
}

很简单,只需要每次先读取超级块,如果发现了魔数(Magic Number),则认为分区已经“装上Orange’s了”,否则调用mkfs()。

接下来你会发现,系统启动时不会每次都mkfs()了,但每次还是会执行一次解开cmd.tar的操作,无论是不是上次启动时解开过。我们这就来改一下untar(),让它解包之后就在cmd.tar这个文件中留个记号,下次看到记号,就不再傻傻得解包了。代码如下所示。

代码 kernel/main.c,修改untar()。

void untar(const char * filename)
{
    printf("[extract '%s'\n", filename);
    int fd = open(filename, O_RDWR);
    assert(fd != -1);

    char buf[SECTOR_SIZE * 16];
    int chunk = sizeof(buf);
    int i = 0;
    int bytes = 0;

    while (1) {
        bytes = read(fd, buf, SECTOR_SIZE);
        assert(bytes == SECTOR_SIZE); /* size of a TAR file must multiple of 512 */
        if (buf[0] == 0) {
            if (i == 0) {
                printf("    read not unpack the file.\n");
            }
            break;
        }
        i++;

        struct posix_tar_header * phdr = (struct posix_tar_header *)buf;

        /* calculate the file size */
        char * p = phdr->size;
        int f_len = 0;
        while (*p) {
            f_len = (f_len * 8) + (*p++ - '0'); /* octal */
        }

        int bytes_left = f_len;
        int fdout = open(phdr->name, O_CREAT | O_RDWR | O_TRUNC);
        if (fdout == -1) {
            printf("    failed to extract file: %s\n", phdr->name);
            printf(" aborted]\n");
            close(fd);
            return;
        }
        printf("    %s\n", phdr->name);
        while (bytes_left) {
            int iobytes = min(chunk, bytes_left);
            read(fd, buf, ((iobytes - 1) / SECTOR_SIZE + 1) * SECTOR_SIZE);
            bytes = write(fdout, buf, iobytes);
            assert(bytes == iobytes);
            bytes_left -= iobytes;
        }
        close(fdout);
    }

    if (i) {
        lseek(fd, 0, SEEK_SET);
        buf[0] = 0;
        bytes = write(fd, buf, 1);
        assert(bytes == 1);
    }

    close(fd);

    printf(" done, %d files extracted]\n", i);
}

这里增加了一个变量i,表示从cmd.tar中总共解出来多少文件。每次功能的解包操作之后(这时i必大于0),我们将cmd.tar的第一个字节置为0,这样下一次untar()执行到第15行时会发现第一个字节为零,于是退出。

如果我们增加或者改写了应用程序,通常会使用dd重新将cmd.tar写入磁盘。由于TAR文件的开始处是包含其中的文件的文件名,所以第一个字节必不为零,于是再次启动时,untar()发现第一个字节非零,从而进入解包的步骤。

值得注意的一点是再次解包时,很可能包内包含的文件已经在磁盘上存在了,所以我们需要将原来的文件内容清除,然后写入新内容,这需要引入O_TRUNC,加入到open()的参数中,见代码第33行。

引入O_TRUNC后,我们还要修改文件系统中的do_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 inode_nr = search_file(pathname);

    struct inode * pin = 0;

    if (inode_nr == INVALID_INODE) { /* file not exists */
        if (flags & O_CREAT) {
            pin = create_file(pathname, flags);
        } else {
            printl("{FS} file not exists: %s\n", pathname);
            return -1;
        }
    } else if (flags & O_RDWR) { /* file exists */
        if ((flags & O_CREAT) && (!(flags & O_TRUNC))) {
            assert(flags == (O_RDWR | O_CREAT));
            printl("{FS} file exists: %s\n", pathname);
            return -1;
        }
        assert((flags ==  O_RDWR                     ) ||
               (flags == (O_RDWR | O_TRUNC          )) ||
               (flags == (O_RDWR | O_TRUNC | O_CREAT)));

        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);
    } else { /* file exists, no O_RDWR flag */
        printl("{FS} file exists: %s\n", pathname);
        return -1;
    }

    if (flags & O_TRUNC) {
        assert(pin);
        pin->i_size = 0;
        sync_inode(pin);
    }

    if (pin) {
...
    } else {
        return -1;
    }

    return fd;
}

引入O_TRUNC之后do_open()的逻辑复杂了若干,大致可以描述为:

  • 如果文件不存在,则只要有O_CREAT就成功,没有就失败。
  • 如果文件存在,则判断是否有O_RDWR,若没有则失败,若有的话,则分以下情况:
    • 仅有O_RDWR,成功
    • 有O_CREAT但无O_TRUNC,失败
    • 有O_TRUNC(无论有没有O_CREAT),成功

代码中还使用了一个新的函数lseek(),它的作用是重新定位读或写的文件偏移量。它的代码如下所示,首先查看接用户口文件代码。

代码 lib/lseek.c,这是新建的文件。

/**
 * Reposition r/w file offset.
 * 
 * @param fd      File descriptor.
 * @param offset  The offset according to `whence'.
 * @param whence  SEEK_SET, SEEK_CUR or SEEK_END.
 * 
 * @return  The resulting offset location as measured in bytes from the beginning of the file.
 */
PUBLIC int lseek(int fd, int offset, int whence)
{
    MESSAGE msg;
    msg.type = LSEEK;
    msg.FD = fd;
    msg.OFFSET = offset;
    msg.WHENCE = whence;

    send_recv(BOTH, TASK_FS, &msg);

    return msg.OFFSET;
}

lseek()函数会向task_fs发送一个类型为LSEEK的消息,然后task_fs会在接收到这个消息后调用do_lseek()函数进行处理。

代码 fs/open.c,do_lseek()。

/**
 * Handle the message LSEEK.
 * 
 * @return The new offset in bytes from the beginning of the file if successful,
 *         otherwise a negative number.
 */
PUBLIC int do_lseek()
{
    int fd = fs_msg.FD;
    int off = fs_msg.OFFSET;
    int whence = fs_msg.WHENCE;

    int pos = pcaller->filp[fd]->fd_pos;
    int f_size = pcaller->filp[fd]->fd_inode->i_size;

    switch (whence) {
        case SEEK_SET:
            pos = off;
            break;
        case SEEK_CUR:
            pos += off;
            break;
        case SEEK_END:
            pos = f_size + off;
            break;
        default:
            return -1;
            break;
    }
    if ((pos > f_size) || (pos < 0)) {
        return -1;
    }
    pcaller->filp[fd]->fd_pos = pos;
    return pos;
}

在task_fs中添加处理LSEEK消息代码。

代码 fs/main.c,处理LSEEK消息。

/**
 * <Ring 1> The main loop of TASK FS.
 */
PUBLIC void task_fs()
{
...
            case LSEEK:
                fs_msg.OFFSET = do_lseek();
                break;
...
}

好了,我们现在可以make并运行程序了,还是提醒一下,不要忘记更改Makefile。运行结果如下图所示。

再次运行时,mkfs()和解开cmd.tar的操作就省略了。

欢迎关注我的公众号


?

公众号中对应文章附有当前文章代码下载说明。

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