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

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

让mkfs()只执行一次

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

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

cpp 复制代码
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()。

cpp 复制代码
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()。

cpp 复制代码
/**
 * 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,这是新建的文件。

cpp 复制代码
/**
 * 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()。

cpp 复制代码
/**
 * 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消息。

cpp 复制代码
/**
 * <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的操作就省略了。

欢迎关注我的公众号

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

相关推荐
二闹3 分钟前
第十六章:监理基础知识(16.1监理的意义和作用--16.5监理要素)
笔记·产品经理
致***锌1 小时前
etf期权和个股期权哪个期权费更贵?
笔记
懒惰的bit10 天前
STM32F103C8T6 学习笔记摘要(四)
笔记·stm32·学习
zkyqss10 天前
OVS Faucet练习(下)
linux·笔记·openstack
浦东新村轱天乐10 天前
【麻省理工】《how to speaking》笔记
笔记
奔跑的蜗牛AZ10 天前
TiDB 字符串行转列与 JSON 数据查询优化知识笔记
笔记·json·tidb
cwtlw10 天前
Excel学习03
笔记·学习·其他·excel
杭州杭州杭州10 天前
计算机网络笔记
笔记·计算机网络
cyborg10 天前
终于再也不用在notion中写公式了
笔记
循环过三天10 天前
1.2、CAN总线帧格式
笔记·stm32·单片机·嵌入式硬件·学习