本文分为两部分:
第一部分将详细分析JOS
的文件系统及文件描述符的实现方法。
第二部分将实现工做路径,提供新的系统调用,完善用户空间工具。
本文中支持的新特性:git
支持进程工做目录 提供getcwd
与chdir
github
新的syscall
shell
SYS_env_set_workpath
修改工做路径ls
功能完善pwd
输出当前工做目录cat
接入工做目录touch
因为文件属性没啥可改的,用于建立文件mkdir
建立目录文件msh
更高级的shell
还未彻底完工 支持cd
支持默认二进制路径为 bin
Github:https://github.com/He11oLiu/MOSruby
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
给文件系统服务器,便可实现读/写文件的功能。JOS文件系统是直接映射到内存空间DISKMAP
到DISKMAP + DISKSIZE
这块空间。故其支持的文件系统最大为3GB.服务器
ide.c
文件系统底层PIO
驱动放在ide.c
中。注意在IDE
中,是以硬件的角度来看待硬盘,其基本单位是sector
,不是block
。markdown
bool ide_probe_disk1(void)
用于检测disk1
是否存在。voidide_set_disk(int diskno)
用于设置目标磁盘。ide_read ide_write
用于磁盘读写。bc.c
文件系统在内存中的映射是基于block cache
的。以一个block
为单位在内存中为其分配单元。注意在bc
中,是以操做系统的角度来看待硬盘,其基本单位是block
,不是sector
。app
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
标记。
fs.c
文件系统是基于刚才的block cache
和底层ide
驱动的。
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 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)
将全部的文件写回硬盘
服务器主要逻辑umain
: 初始化文件系统,初始化服务器,开始接收请求。
服务器具体函数见上面实现。
int openfile_alloc(struct OpenFile **o)
用于服务器分配一个openfile
结构体
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)
后面的与这个相似
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
以前已经分析过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
管道是一种把两个进程之间的标准输入和标准输出链接起来的机制,从而提供一种让多个进程间通讯的方法,当进程建立管道时,每次都须要提供两个文件描述符来操做管道。其中一个对管道进行写操做,另外一个对管道进行读操做。对管道的读写与通常的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
。
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
因为写到这里第一次在用户空间读取文件,简要记录一下读取文件的过程。
首先是文件结构,在lab5中设计文件系统的时候设计的,保存在struct File
中,用户能够根据此结构体偏移来找具体的信息。
再是fsformat
中提供的与文件系统相关的接口。这里用到了readn
。其只是对于read
的一层包装。
回到ls
自己的逻辑上。ls
主要是读取path
文件,并将其下全部的文件名所有打印出来。
因为以前写的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));
}
发现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);
}
建立文件直接利用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);
}
这个只须要修改好支持工做路径便可
#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
的时候发现问题:以前没有解决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]
$