[自制操做系统] JOS文件系统详解&支持工做路径&MSH

这里写图片描述
本文分为两部分:
第一部分将详细分析JOS的文件系统及文件描述符的实现方法。
第二部分将实现工做路径,提供新的系统调用,完善用户空间工具。
本文中支持的新特性:git

  • 支持进程工做目录 提供getcwdchdirgithub

  • 新的syscallshell

    • SYS_env_set_workpath 修改工做路径
  • 新的用户程序
    • ls 功能完善
    • pwd 输出当前工做目录
    • cat 接入工做目录
    • touch 因为文件属性没啥可改的,用于建立文件
    • mkdir 建立目录文件
    • msh 更高级的shell 还未彻底完工 支持cd 支持默认二进制路径为 bin
  • 调整目标磁盘生成工具

Github:https://github.com/He11oLiu/MOSruby

JOS文件系统详解

文件系统总结

Regular env           FS env
   +---------------+   +---------------+
   | read |   | file_read |
   |   (lib/fd.c)  |   |   (fs/fs.c)   |
...|.......|.......|...|.......^.......|...............
   | v |   |       |       | RPC mechanism
   | devfile_read |   | serve_read |
   |  (lib/file.c) |   |  (fs/serv.c)  |
   |       |       |   |       ^       |
   | v |   |       |       |
   | fsipc |   | serve |
   |  (lib/file.c) |   |  (fs/serv.c)  |
   |       |       |   |       ^       |
   | v |   |       |       |
   | ipc_send |   | ipc_recv |
   |       |       |   |       ^       |
   +-------|-------+   +-------|-------+
           |                   |
           +-------------------+
  • 底层与磁盘有关的丢给ide_xx来实现,由于要用到IO中断,要给对应的权限
  • 经过bc_pgfault来实现缺页本身映射,利用flush_block来回写磁盘
  • 而后经过分block利用block cache实现对于磁盘的数据读入内存或者写回磁盘
  • 再上面一层的file_readfile_write,均是对于blk的操做。
  • 再上面就是文件系统服务器,经过调用file_read实现功能了。
  • 客户端经过对需求打包,发送IPC给文件系统服务器,便可实现读/写文件的功能。

文件系统&文件描述符 Overview

JOS文件系统是直接映射到内存空间DISKMAPDISKMAP + DISKSIZE这块空间。故其支持的文件系统最大为3GB.服务器

IDE ide.c

文件系统底层PIO驱动放在ide.c中。注意在IDE中,是以硬件的角度来看待硬盘,其基本单位是sector,不是blockmarkdown

  • bool ide_probe_disk1(void) 用于检测disk1是否存在。
  • voidide_set_disk(int diskno) 用于设置目标磁盘。
  • ide_read ide_write 用于磁盘读写。

block cache bc.c

文件系统在内存中的映射是基于block cache的。以一个block为单位在内存中为其分配单元。注意在bc中,是以操做系统的角度来看待硬盘,其基本单位是block,不是sectorapp

  • void *diskaddr(uint32_t blockno) 用于查找blockno在地址空间中的地址。ide

  • blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE 用于查找addr对应文件系统中的blockno函数

  • static void bc_pgfault(struct UTrapframe *utf) 用于处理读取不在内存中而出现page fault的状况。这时须要从file system经过PIO读取到block cache(也就是内存中新分配的一页)中,并作好映射。工具

  • void flush_block(void *addr) 用于写回硬盘,写回时清理PTE_D标记。

file system fs.c

文件系统是基于刚才的block cache和底层ide驱动的。

bitmap 相关

bitmap每一位表明着一个block的状态,用位操做检查/设置block状态便可。

  • bool block_is_free(uint32_t blockno) 用于check给定的blockno是不是空闲的。

  • void free_block(uint32_t blockno) 设置对应位为0

  • int alloc_block(void) 设置对应位为1

文件系统操做

  • void fs_init(void) 初始化文件系统。检测disk1是否存在,检测super blockbitmap block

  • static int file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc) 用于找到文件ffilenoblockblocknoalloc用于控制当f_indirect不存在的时候,是否须要新申请一个block

  • int file_get_block(struct File *f, uint32_t filebno, char **blk) 用于找到文件ffilenoblock的地址。

  • static int dir_lookup(struct File *dir, const char *name, struct File **file) 用于在dir下查找name这个文件。其遍历读取dir这个文件,并逐个判断其目录下每个文件的名字是否相等。

  • static int dir_alloc_file(struct File *dir, struct File **file)dir下新申请一个file。一样也是遍历全部的dir下的文件。找到第一个名字为空的文件,并把新的文件存在这里。

  • static int walk_path(const char *path, struct File **pdir, struct File **pf, char *lastelem) 用于从根目录获取path的文件,文件放在pf中,路径放在pdir中。若是找到了路径没有找到文件。最后的路径名放在lastelem中,最后的路径放在pdir中。

文件操做

  • int file_create(const char *path, struct File **pf) 用于建立文件。

  • int file_open(const char *path, struct File **pf) 打开文件。

  • ssize_t file_read(struct File *f, void *buf, size_t count, off_t offset)foffset读取countbytes的数据放入buf中。

  • int file_write(struct File *f, const void *buf, size_t count, off_t offset) 与上面的相似。

  • static int file_free_block(struct File *f, uint32_t filebno) 删除文件中的filebno

  • static void file_truncate_blocks(struct File *f, off_t newsize) 缩短文件大小。

  • int file_set_size(struct File *f, off_t newsize) 修改文件大小。

  • void file_flush(struct File *f) 将文件写回硬盘

  • void fs_sync(void) 将全部的文件写回硬盘

文件系统服务器 serv.c

  • 服务器主要逻辑umain: 初始化文件系统,初始化服务器,开始接收请求。

  • 服务器具体函数见上面实现。

  • int openfile_alloc(struct OpenFile **o)用于服务器分配一个openfile结构体

文件描述符 fd.c

  • struct fd 结构体

    struct Fd {
    int fd_dev_id;
    off_t fd_offset;
    int fd_omode;
    union {
        // File server files
        struct FdFile fd_file;
    };
    };

    其中fd_file用于发送的时候传入服务器对应的fileid

    包括了fd_id 文件读取的offset,读取模式以及FdFile

  • int fd2num(struct Fd *fd)fd获取其编号

  • char* fd2data(struct Fd *fd)fd获取文件内容

  • int fd_alloc(struct Fd **fd_store) 查找到第一个空闲的fd,并分配出去。

  • int fd_lookup(int fdnum, struct Fd **fd_store) 为查找fdnum的fd,并放在fd_store中。

  • int fd_close(struct Fd *fd, bool must_exist) 用于关闭并free一个fd

  • int dev_lookup(int dev_id, struct Dev **dev) 获取不一样的Device

  • int close(int fdnum) 关闭fd

  • void close_all(void) 关闭所有

  • int dup(int oldfdnum, int newfdnum) dup不是简单的复制,而是要将两个fd的内容彻底同步,其是经过虚拟内存映射作到的。

  • read(int fdnum, void *buf, size_t n) 后面的与这个相似

    • 获取fdfd_dev_id并根据其获取dev
    • 调用dev对应的function
  • int seek(int fdnum, off_t offset) 用于设置fdoffset

  • int fstat(int fdnum, struct Stat *stat) 获取文件状态。

    struct Stat
    {
    char st_name[MAXNAMELEN];
    off_t st_size;
    int st_isdir;
    struct Dev *st_dev;
    };
  • int stat(const char *path, struct Stat *stat) 获取路径状态。

具体关于文件描述符的设计见下图。

这里写图片描述

下面就来详细看现有的三个device

文件系统读写 file.c

  • 以前已经分析过devfile_xx的函数

  • static int fsipc(unsigned type, void *dstva)用于给文件系统服务器发送IPC

  • 这里是实例化了一个用于文件读取的dev

    struct Dev devfile =
    {
    .dev_id = 'f',
    .dev_name = "file",
    .dev_read = devfile_read,
    .dev_close = devfile_flush,
    .dev_stat = devfile_stat,
    .dev_write = devfile_write,
    .dev_trunc = devfile_trunc
    };

管道 pipe.c

关于pipe

管道是一种把两个进程之间的标准输入和标准输出链接起来的机制,从而提供一种让多个进程间通讯的方法,当进程建立管道时,每次都须要提供两个文件描述符来操做管道。其中一个对管道进行写操做,另外一个对管道进行读操做。对管道的读写与通常的IO系统函数一致,使用write()函数写入数据,使用read()读出数据。

同刚才的file的操做相似,这里是对于pipe的操做。

struct Dev devpipe =
    {
        .dev_id = 'p',
        .dev_name = "pipe",
        .dev_read = devpipe_read,
        .dev_write = devpipe_write,
        .dev_close = devpipe_close,
        .dev_stat = devpipe_stat,
};

pipe 的结构体以下

struct Pipe
{
    off_t p_rpos;              // read position
    off_t p_wpos;              // write position
    uint8_t p_buf[PIPEBUFSIZ]; // data buffer
};
  • int pipe(int pfd[2]) 申请两个新的fd,映射到同一个虚拟地址上,一边Read_only 一边Write_only便可。

  • static ssize_t devpipe_read(struct Fd *fd, void *vbuf, size_t n) 其从fd对应的data获取pipep = (struct Pipe *)fd2data(fd);而后从pipe->buf中读取内容。维护p_rpos

  • static ssize_t devpipe_write(struct Fd *fd, const void *vbuf, size_t n) 其从fd对应的data获取pipep = (struct Pipe *)fd2data(fd);而后向pipe->buf中写入内容。维护p_wpos

屏幕输入输出 console.c

struct Dev devcons =
    {
        .dev_id = 'c',
        .dev_name = "cons",
        .dev_read = devcons_read,
        .dev_write = devcons_write,
        .dev_close = devcons_close,
        .dev_stat = devcons_stat};

实现直接调用syscall便可,和以前实现的putchar相似。

支持工做路径以及更完整的工具

本本分将主要关注用户空间程序,并补全内核功能(支持工做路径)。
本部分主要包括如下用户应用程序:

ls      list directory contents
pwd     return working directory name
mkdir   make directories
touch   change file access and modification times(we only support create file)
cat     concatenate and print files
shell

list directory contents

读文件

因为写到这里第一次在用户空间读取文件,简要记录一下读取文件的过程。

首先是文件结构,在lab5中设计文件系统的时候设计的,保存在struct File中,用户能够根据此结构体偏移来找具体的信息。

再是fsformat中提供的与文件系统相关的接口。这里用到了readn。其只是对于read的一层包装。

功能实现

回到ls自己的逻辑上。ls 主要是读取path文件,并将其下全部的文件名所有打印出来。

return working directory name

因为以前写的JOS中每一个进程没有写工做目录。这里再加上工做目录。

struct env中加入工做目录,添加后env以下:

struct Env {
    struct Trapframe env_tf;    // Saved registers
    struct Env *env_link;       // Next free Env
    envid_t env_id;         // Unique environment identifier
    envid_t env_parent_id;      // env_id of this env's parent
    enum EnvType env_type;      // Indicates special system environments
    unsigned env_status;        // Status of the environment
    uint32_t env_runs;      // Number of times environment has run
    int env_cpunum;         // The CPU that the env is running on

    // Address space
    pde_t *env_pgdir;       // Kernel virtual address of page dir

    // Exception handling
    void *env_pgfault_upcall;   // Page fault upcall entry point

    // IPC
    bool env_ipc_recving;       // Env is blocked receiving
    void *env_ipc_dstva;        // VA at which to map received page
    uint32_t env_ipc_value;     // Data value sent to us
    envid_t env_ipc_from;       // envid of the sender
    int env_ipc_perm;       // Perm of page mapping received

    // work path
    char workpath[MAXPATH];
};

因为env对于用户是不能够写的,因此要添加新的syscall,进入内核态改。

enum {
    SYS_cputs = 0,
    SYS_cgetc,
    SYS_getenvid,
    SYS_env_destroy,
    SYS_page_alloc,
    SYS_page_map,
    SYS_page_unmap,
    SYS_exofork,
    SYS_env_set_status,
    SYS_env_set_trapframe,
    SYS_env_set_pgfault_upcall,
    SYS_yield,
    SYS_ipc_try_send,
    SYS_ipc_recv,
    SYS_getcwd,
    SYS_chdir,
    NSYSCALLS
};

因为JOS中用户其实能够读env中的内容,因此getcwd就不陷入内核态了,直接读取就好。

新建dir.c用于存放与目录有关的函数,实现getcwd

char *getcwd(char *buffer, int maxlen)
{
    if(!buffer || maxlen < 0)
        return NULL;
    return strncpy((char *)buffer,(const char*)thisenv->workpath,maxlen);
}

而对于修改目录,必需要陷入内核态了,新加syscall

int sys_chdir(const char *path)
{
    return syscall(SYS_chdir, 0, (uint32_t)path, 0, 0, 0, 0);
}

刚才的dir.c中加入用户接口

// change work path
// Return 0 on success, 
// Return < 0 on error. Errors are:
// -E_INVAL *path not exist or not a path
int chdir(const char *path)
{
    int r;
    struct Stat st;
    if ((r = stat(path, &st)) < 0)
        return r;
    if(!st.st_isdir)
        return -E_INVAL;
    return sys_chdir(path);
}

而后去内核添加功能

// change work path
// return 0 on success.
static int
sys_chdir(const char * path)
{
    strcpy((char *)curenv->workpath,path);
    return 0;
}

最后实现pwd

#include <inc/lib.h>

void umain(int argc, char **argv)
{
    char path[200];
    if(argc > 1)
        printf("%s : too many arguments\n",argv[0]);
    else
        printf("%s\n",getcwd(path,200));
}

make directories

发现JOS给咱们预留了标识位O_MKDIR,因为与普通的file_create不同,当有同名的文件存在的时候,但其不是目录的状况下,咱们仍然能够建立,因此新写了函数

int dir_create(const char *path, struct File **pf)
{
    char name[MAXNAMELEN];
    int r;
    struct File *dir, *f;

    if (((r = walk_path(path, &dir, &f, name)) == 0) &&
        f->f_type == FTYPE_DIR)
        return -E_FILE_EXISTS;
    if (r != -E_NOT_FOUND || dir == 0)
        return r;
    if ((r = dir_alloc_file(dir, &f)) < 0)
        return r;

    // fill struct file
    strcpy(f->f_name, name);
    f->f_type = FTYPE_DIR;

    *pf = f;
    file_flush(dir);
    return 0;
}

而后在serve_open下创建新的分支

// create dir
    else if (req->req_omode & O_MKDIR)
    {
        if ((r = dir_create(path, &f)) < 0)
        {
            if (!(req->req_omode & O_EXCL) && r == -E_FILE_EXISTS)
                goto try_open;
            if (debug)
                cprintf("file_create failed: %e", r);
            return r;
        }
    }

dir.c下提供mkdir函数

// make directory
// Return 0 on success,
// Return < 0 on error. Errors are:
// -E_FILE_EXISTS directory already exist
int mkdir(const char *dirname)
{
    char cur_path[MAXPATH];
    int r;
    getcwd(cur_path, MAXPATH);
    strcat(cur_path, dirname);
    if ((r = open(cur_path, O_MKDIR)) < 0)
        return r;
    close(r);
    return 0;
}

最后提供用户程序

#include <inc/lib.h>
#define MAXPATH 200

void umain(int argc, char **argv)
{
    int r;

    if (argc != 2)
    {
        printf("usage: mkdir directory\n");
        return;
    }
    if((r = mkdir(argv[1])) < 0)
        printf("%s error : %e\n",argv[0],r);
}

Create file

建立文件直接利用open中的O_CREAT选项便可。

#include <inc/lib.h>
#define MAXPATH 200

void umain(int argc, char **argv)
{
    int r;
    char *filename;
    char pathbuf[MAXPATH];
    if (argc != 2)
    {
        printf("usage: touch filename\n");
        return;
    }
    filename = argv[1];
    if (*filename != '/')
        getcwd(pathbuf, MAXPATH);
    strcat(pathbuf, filename);
    if ((r = open(pathbuf, O_CREAT)) < 0)
        printf("%s error : %e\n", argv[0], r);
    close(r);
}

cat

这个只须要修改好支持工做路径便可

#include <inc/lib.h>

char buf[8192];

void cat(int f, char *s)
{
    long n;
    int r;

    while ((n = read(f, buf, (long)sizeof(buf))) > 0)
        if ((r = write(1, buf, n)) != n)
            panic("write error copying %s: %e", s, r);
    if (n < 0)
        panic("error reading %s: %e", s, n);
}

void umain(int argc, char **argv)
{
    int f, i;
    char *filename;
    char pathbuf[MAXPATH];

    binaryname = "cat";
    if (argc == 1)
        cat(0, "<stdin>");
    else
        for (i = 1; i < argc; i++)
        {
            filename = argv[1];
            if (*filename != '/')
                getcwd(pathbuf, MAXPATH);
            strcat(pathbuf, filename);
            f = open(pathbuf, O_RDONLY);
            if (f < 0)
                printf("can't open %s: %e\n", argv[i], f);
            else
            {
                cat(f, argv[i]);
                close(f);
            }
        }
}

SHELL

Shell的时候发现问题:以前没有解决fork以及spawn时候的子进程的工做路径的问题。全部再一次修改了系统调用,将系统调用sys_chdir修改成可以设定指定进程的工做目录的系统调用。

int sys_env_set_workpath(envid_t envid, const char *path);

修改对应的内核处理:

// change work path
// return 0 on success.
static int
sys_env_set_workpath(envid_t envid, const char *path)
{
    struct Env *e;
    int ret = envid2env(envid, &e, 1);
    if (ret != 0)
        return ret;
    strcpy((char *)e->workpath, path);
    return 0;
}

这样就会fork出来的子进程继承父亲的工做路径。

shell中加入built-in功能,为将来扩展shell功能提供基础

int builtin_cmd(char *cmdline)
{
    int ret;
    int i;
    char cmd[20];
    for (i = 0; cmdline[i] != ' ' && cmdline[i] != '\0'; i++)
        cmd[i] = cmdline[i];
    cmd[i] = '\0';
    if (!strcmp(cmd, "quit") || !strcmp(cmd, "exit"))
        exit();
    if (!strcmp(cmd, "cd"))
    {
        ret = do_cd(cmdline);
        return 1;
    }
    return 0;
}

int do_cd(char *cmdline)
{
    char pathbuf[BUFSIZ];
    int r;
    pathbuf[0] = '\0';
    cmdline += 2;
    while (*cmdline == ' ')
        cmdline++;
    if (*cmdline == '\0')
        return 0;
    if (*cmdline != '/')
    {
        getcwd(pathbuf, BUFSIZ);
    }
    strcat(pathbuf, cmdline);
    if ((r = chdir(pathbuf)) < 0)
        printf("cd error : %e\n", r);
    return 0;
}

修改<> 支持当前工做路径

case '<': // Input redirection
            // Grab the filename from the argument list
            if (gettoken(0, &t) != 'w')
            {
                cprintf("syntax error: < not followed by word\n");
                exit();
            }
            // Open 't' for reading as file descriptor 0
            // (which environments use as standard input).
            // We can't open a file onto a particular descriptor,
            // so open the file as 'fd',
            // then check whether 'fd' is 0.
            // If not, dup 'fd' onto file descriptor 0,
            // then close the original 'fd'.

            if (t[0] != '/')
                getcwd(argv0buf, MAXPATH);
            strcat(argv0buf, t);
            if ((fd = open(argv0buf, O_RDONLY)) < 0)
            {
                cprintf("Error open %s fail: %e", argv0buf, fd);
                exit();
            }
            if (fd != 0)
            {
                dup(fd, 0);
                close(fd);
            }
            break;

        case '>': // Output redirection
            // Grab the filename from the argument list
            if (gettoken(0, &t) != 'w')
            {
                cprintf("syntax error: > not followed by word\n");
                exit();
            }
            if (t[0] != '/')
                getcwd(argv0buf, MAXPATH);
            strcat(argv0buf, t);
            if ((fd = open(argv0buf, O_WRONLY | O_CREAT | O_TRUNC)) < 0)
            {
                cprintf("open %s for write: %e", argv0buf, fd);
                exit();
            }
            if (fd != 1)
            {
                dup(fd, 1);
                close(fd);
            }
            break;

建立硬盘镜像

  • 利用mmap映射到内存,对内存读写。

    if ((diskmap = mmap(NULL, nblocks * BLKSIZE, PROT_READ | PROT_WRITE,
                        MAP_SHARED, diskfd, 0)) == MAP_FAILED)
        panic("mmap %s: %s", name, strerror(errno));

    diskmap开始,大小为nblocks * BLKSIZE

  • alloc用于分配空间,移动diskpos

void *
alloc(uint32_t bytes)
{
    void *start = diskpos;
    diskpos += ROUNDUP(bytes, BLKSIZE);
    if (blockof(diskpos) >= nblocks)
        panic("out of disk blocks");
    return start;
}
  • 块 123 在初始化的时候分配

    alloc(BLKSIZE);
    super = alloc(BLKSIZE);
    super->s_magic = FS_MAGIC;
    super->s_nblocks = nblocks;
    super->s_root.f_type = FTYPE_DIR;
    strcpy(super->s_root.f_name, "/");
    
    nbitblocks = (nblocks + BLKBITSIZE - 1) / BLKBITSIZE;
    bitmap = alloc(nbitblocks * BLKSIZE);
    memset(bitmap, 0xFF, nbitblocks * BLKSIZE);
  • writefile用于申请空间,写入磁盘

    void writefile(struct Dir *dir, const char *name)
    {
    int r, fd;
    struct File *f;
    struct stat st;
    const char *last;
    char *start;
    
    if ((fd = open(name, O_RDONLY)) < 0)
        panic("open %s: %s", name, strerror(errno));
    if ((r = fstat(fd, &st)) < 0)
        panic("stat %s: %s", name, strerror(errno));
    if (!S_ISREG(st.st_mode))
        panic("%s is not a regular file", name);
    if (st.st_size >= MAXFILESIZE)
        panic("%s too large", name);
    
    last = strrchr(name, '/');
    if (last)
        last++;
    else
        last = name;
    
    // 获取目录中的一个空位
    f = diradd(dir, FTYPE_REG, last);
    // 获取文件存放地址,分配空间
    start = alloc(st.st_size);
    // 将文件读如到磁盘中刚刚分配的地址
    readn(fd, start, st.st_size);
    // 完成文件信息
    finishfile(f, blockof(start), st.st_size);
    close(fd);
    }
    
    void finishfile(struct File *f, uint32_t start, uint32_t len)
    {
    int i;
    // 这个是刚才目录下传过来的地址,直接修改目录下的相应项
    f->f_size = len;
    len = ROUNDUP(len, BLKSIZE);
    for (i = 0; i < len / BLKSIZE && i < NDIRECT; ++i)
        f->f_direct[i] = start + i;
    if (i == NDIRECT)
    {
        uint32_t *ind = alloc(BLKSIZE);
        f->f_indirect = blockof(ind);
        for (; i < len / BLKSIZE; ++i)
            ind[i - NDIRECT] = start + i;
    }
    }
  • 目录结构体与什么时候将目录写入

    void startdir(struct File *f, struct Dir *dout)
    {
    dout->f = f;
    dout->ents = malloc(MAX_DIR_ENTS * sizeof *dout->ents);
    dout->n = 0;
    }
    
    void finishdir(struct Dir *d)
    {
    // 目录文件的大小
    int size = d->n * sizeof(struct File);
    // 申请目录文件存放空间
    struct File *start = alloc(size);
        // 将目录的文件内容放进去
    memmove(start, d->ents, size);
      // 补全目录在磁盘当中的信息
    finishfile(d->f, blockof(start), ROUNDUP(size, BLKSIZE));
    free(d->ents);
    d->ents = NULL;
    }
  • 添加bin路径,并在shell中相似path环境变量默认读取bin下的可执行文件

    opendisk(argv[1]);
    
    startdir(&super->s_root, &root);
    f = diradd(&root, FTYPE_DIR, "bin");
    startdir(f,&bin);
    for (i = 3; i < argc; i++)
        writefile(&bin, argv[i]);
    finishdir(&bin);
    finishdir(&root);
    
    finishdisk();

获取时间

又新增一个syscall,这里再也不累述,利用mc146818_read获取cmos时间便可。

int gettime(struct tm *tm)
{
    unsigned datas, datam, datah;
    int i;
    tm->tm_sec = BCD_TO_BIN(mc146818_read(0));
    tm->tm_min = BCD_TO_BIN(mc146818_read(2));
    tm->tm_hour = BCD_TO_BIN(mc146818_read(4)) + TIMEZONE;
    tm->tm_wday = BCD_TO_BIN(mc146818_read(6));
    tm->tm_mday = BCD_TO_BIN(mc146818_read(7));
    tm->tm_mon = BCD_TO_BIN(mc146818_read(8));
    tm->tm_year = BCD_TO_BIN(mc146818_read(9));
    return 0;
}

实机运行输出

check_page_free_list() succeeded!
check_page_alloc() succeeded!
check_page() succeeded!
check_kern_pgdir() succeeded!
check_page_free_list() succeeded!
check_page_installed_pgdir() succeeded!
====Graph mode on====    scrnx = 1024
   scrny = 768
MMIO VRAM = 0xef803000
===================== SMP: CPU 0 found 1 CPU(s)
enabled interrupts: 1 2 4
FS is running
FS can do I/O
Device 1 presence: 1
block cache is good
superblock is good
bitmap is good

# msh in / [12: 4:28]
$ cd documents

# msh in /documents/ [12: 4:35]
$ echo hello liu > hello

# msh in /documents/ [12: 4:45]
$ cat hello
hello liu

# msh in /documents/ [12: 4:49]
$ cd /bin

# msh in /bin/ [12: 4:54]
$ ls -l -F
- 37 newmotd - 92 motd - 447 lorem - 132 script - 2916 testshell.key - 113 testshell.sh - 20308 cat - 20076 echo - 20508 ls - 20332 lsfd - 25060 sh - 20076 hello - 20276 pwd - 20276 mkdir - 20280 touch - 29208 msh 
# msh in /bin/ [12: 4:57]
$
相关文章
相关标签/搜索