《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的操作就省略了。

欢迎关注我的公众号

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

相关推荐
Yawesh_best6 小时前
告别系统壁垒!WSL+cpolar 让跨平台开发效率翻倍
运维·服务器·数据库·笔记·web安全
Ccjf酷儿8 小时前
操作系统 蒋炎岩 3.硬件视角的操作系统
笔记
习习.y8 小时前
python笔记梳理以及一些题目整理
开发语言·笔记·python
在逃热干面9 小时前
(笔记)自定义 systemd 服务
笔记
DKPT10 小时前
ZGC和G1收集器相比哪个更好?
java·jvm·笔记·学习·spring
QT 小鲜肉12 小时前
【孙子兵法之上篇】001. 孙子兵法·计篇
笔记·读书·孙子兵法
星轨初途13 小时前
数据结构排序算法详解(5)——非比较函数:计数排序(鸽巢原理)及排序算法复杂度和稳定性分析
c语言·开发语言·数据结构·经验分享·笔记·算法·排序算法
QT 小鲜肉13 小时前
【孙子兵法之上篇】001. 孙子兵法·计篇深度解析与现代应用
笔记·读书·孙子兵法
love530love15 小时前
【笔记】ComfUI RIFEInterpolation 节点缺失问题(cupy CUDA 安装)解决方案
人工智能·windows·笔记·python·插件·comfyui
愚戏师16 小时前
MySQL 数据导出
数据库·笔记·mysql