本文分爲兩部分:
第一部分將詳細分析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]
$