[自制操作系统] JOS文件系统详解&支持工作路径&MSH
本文分为两部分:
第一部分将详细分析JOS
的文件系统及文件描述符的实现方法。
第二部分将实现工作路径,提供新的系统调用,完善用户空间工具。
本文中支持的新特性:
支持进程工作目录 提供
getcwd
与chdir
新的
syscall
SYS_env_set_workpath
修改工作路径
- 新的用户程序
ls
功能完善pwd
输出当前工作目录cat
接入工作目录touch
由于文件属性没啥可改的,用于创建文件mkdir
创建目录文件msh
更高级的shell
还未完全完工 支持cd
支持默认二进制路径为bin
- 调整目标磁盘生成工具
Github:https://github.com/He11oLiu/MOS
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_read
与file_write
,均是对于blk
的操作。 - 再上面就是文件系统服务器,通过调用
file_read
实现功能了。 - 客户端通过对需求打包,发送
IPC
给文件系统服务器,即可实现读/写文件的功能。
文件系统&文件描述符 Overview
JOS文件系统是直接映射到内存空间DISKMAP
到DISKMAP + DISKSIZE
这块空间。故其支持的文件系统最大为3GB.
IDE ide.c
文件系统底层PIO
驱动放在ide.c
中。注意在IDE
中,是以硬件的角度来看待硬盘,其基本单位是sector
,不是block
。
bool ide_probe_disk1(void)
用于检测disk1
是否存在。voidide_set_disk(int diskno)
用于设置目标磁盘。ide_read ide_write
用于磁盘读写。
block cache bc.c
文件系统在内存中的映射是基于block cache
的。以一个block
为单位在内存中为其分配单元。注意在bc
中,是以操作系统的角度来看待硬盘,其基本单位是block
,不是sector
。
void *diskaddr(uint32_t blockno)
用于查找blockno
在地址空间中的地址。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)
设置对应位为0int alloc_block(void)
设置对应位为1
文件系统操作
void fs_init(void)
初始化文件系统。检测disk1
是否存在,检测super block
和bitmap block
。static int file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)
用于找到文件f
的fileno
个block
的blockno
。alloc
用于控制当f_indirect
不存在的时候,是否需要新申请一个block
。int file_get_block(struct File *f, uint32_t filebno, char **blk)
用于找到文件f
的fileno
个block
的地址。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)
从f
的offset
读取count
bytes的数据放入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一个fdint dev_lookup(int dev_id, struct Dev **dev)
获取不同的Deviceint close(int fdnum)
关闭fd
void close_all(void)
关闭全部int dup(int oldfdnum, int newfdnum)
dup
不是简单的复制,而是要将两个fd
的内容完全同步,其是通过虚拟内存映射做到的。read(int fdnum, void *buf, size_t n)
后面的与这个类似- 获取
fd
的fd_dev_id
并根据其获取dev
- 调用
dev
对应的function
- 获取
int seek(int fdnum, off_t offset)
用于设置fd
的offset
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
获取pipe
。p = (struct Pipe *)fd2data(fd);
然后从pipe->buf
中读取内容。维护p_rpos
。static ssize_t devpipe_write(struct Fd *fd, const void *vbuf, size_t n)
其从fd
对应的data
获取pipe
。p = (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]
$
[自制操作系统] JOS文件系统详解&支持工作路径&MSH的更多相关文章
- [转帖]Linux文件系统详解
Linux文件系统详解 https://www.cnblogs.com/alantu2018/p/8461749.html 贼复杂.. 从操作系统的角度详解Linux文件系统层次.文件系统分类.文件系 ...
- Java 详解 JVM 工作原理和流程
Java 详解 JVM 工作原理和流程 作为一名Java使用者,掌握JVM的体系结构也是必须的.说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成:Java ...
- iOS 后台持续定位详解(支持ISO9.0以上)
iOS 后台持续定位详解(支持ISO9.0以上) #import <CoreLocation/CoreLocation.h>并实现CLLocationManagerDelegate 代理, ...
- proc文件系统详解(原创)
Linux系统上的/proc目录是一种文件系统,即proc文件系统.与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过 ...
- 【史上最全】Hadoop 核心 - HDFS 分布式文件系统详解(上万字建议收藏)
1. HDFS概述 Hadoop 分布式系统框架中,首要的基础功能就是文件系统,在 Hadoop 中使用 FileSystem 这个抽象类来表示我们的文件系统,这个抽象类下面有很多子实现类,究竟使用哪 ...
- Linux Redhat 7.6 操作系统 下载安装详解
redhat 系统镜像分享 [百度网盘分享] (https://pan.baidu.com/s/1ALM6v1dAtPwmEt2tmyTghg ) 提取码:2i4o redhat 7.6版本安装详解 ...
- Linux crontab 命令详解(含配置文件路径)
编辑/etc/crontab 文件配置cron cron 服务每分钟不仅要读一次/var/spool/cron内的所有文件,还需要读一次/etc/crontab,因此我们配置这个文件也能运用cron服 ...
- ucore文件系统详解
最近一直在mooc上学习清华大学的操作系统课程,也算是复习下基本概念和原理,为接下来的找工作做准备. 每次深入底层源码都让我深感操作系统实现的琐碎,即使像ucore这样简单的kernel也让我烦躁不已 ...
- FastDFS 分布式文件系统详解
什么是文件系统 文件系统是操作系统用于在磁盘或分区上组织文件的方法和数据结构.磁盘空间是什么样的我们并不清楚,但文件系统可以给我们呈现一个非常清晰的表象,我们可以创建.删除.修改和复制这些文件,而实现 ...
随机推荐
- amd和cmd区别
作者:玉伯 链接:https://www.zhihu.com/question/20351507/answer/14859415 来源:知乎 著作权归作者所有,转载请联系作者获得授权. AMD 是 R ...
- Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is.
我用String代替了链表显示,本题的大意是每k个进行逆序处理,剩下的不够k个的就按照原顺序保留下来. public class ReverseNodes { public static void m ...
- 自定义Git之忽略特殊文件
有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件啦,等等,每次git status都会显示Untracked files ...,有强迫症的童鞋心里肯定 ...
- .net Ajax使用
function CheckUsername() { var strName = $("#txtUserName").val(); $.ajax({ type: "GET ...
- .NET Core 2.0和ASP.NET Core 2.0正式版抢先体验
.NET Core 2.0和ASP.NET Core 2.0正式版抢先体验 .NET Standard 2.0 is final Broad platform support. .NET Standa ...
- 学起来 —— CSS 入门基础
Hello,大家好! 小女来更博啦!CSS福利送上~~~ 首先给大家介绍一下CSS到底是什么? 一.CSS概念 W3C规范中,要求有三条:一 为"两个分离",二 为语言遵循语义化, ...
- get post请求
GET 从指定的资源请求数据 /test/demo_form.asp?name1=value1&name2=value2 请求可被缓存 请求保留在浏览器历史记录中 请求可被收藏为书签 请求不应 ...
- webpack 引入 bootstrap
Bootstrap中是一种事实上的界面标准,标准到现在的网站大量的使用它.如果可以使用webpack引入的bootstrap,就可以一个npm install完成项目的依赖,而不必手工的添加到html ...
- 社交系统ThinkSNS+ 发布通知!
社交系统ThinkSNS 最新版本ThinkSNS+将于7月15日正式发布开源版本web+H5,同时发布Android APP和iOS APP.我们将告别内测阶段,正式对外发布. 没错,你们没看错,就 ...
- selenium更加高效的PageObject 对象操作代码
重新封装了的selenium代码,包括click事件,sendkeys事件,select事件,以及对readonly日期控件的处理 package com.common; import java.ut ...