OS挑战性任务
lab6-challenge shell
要求总览
- 相对路径支持
- 环境变量管理
- 指令输入优化
- 无后缀指令支持
- 终端输入输出优化
- 历史指令支持
- 实现更多功能
- 追加重定向功能
- 注释功能
- 变量替换
- 反引号执行功能
- 指令条件执行功能
- 实现更多指令
- 内建指令篇
history
cd
pwd
exit
declare
unset
- 正常指令篇
ls
rm
touch
mkdir
- 内建指令篇
1.相对路径支持
首先需要实现工作目录,为每个进程加入属性env_cwd
,并在env_alloc/sys_exofork
时使得子进程继承父进程的工作目录struct Env {
//...
char env_cwd[MAXPATHLEN];
//...
}
// env_alloc
strcpy(e->env_cwd, "/");
// sys_exofork
strcpy(e->env_cwd, curenv->env_cwd);
实现辅助函数normalize_path
,协助转换为绝对路径,该函数支持传入路径相对或绝对
- 对于相对路径,将适配
cwd
目录得到绝对路径 - 对于绝对路径,直接返回
void back_path(char* path); // 返回上一级目录
void append_path(char* path, const char* name); // 叠加路径
// to normalize path or abspath
int normalize_path(const char* path, const char* cwd, char* abspath) {
if (path[0] == '/') {
// absolute path
if (strlen(path) > MAXPATHLEN) {
return -E_BAD_PATH;
}
strcpy(abspath, path);
return 0;
} else {
char temp[2 * MAXPATHLEN + 1];
strcpy(temp, cwd);
char name[MAXNAMELEN];
int name_str = 0;
int max_len = strlen(path);
int i;
for (i = 0; i < max_len; i++) {
if (path[i] == '/') {
name[name_str] = '\0';
if (name_str >= MAXNAMELEN) {
return -E_BAD_PATH;
}
append_path(temp, name);
name_str = 0;
} else {
name[name_str++] = path[i];
}
}
name[name_str] = '\0';
if (name_str != 0) {
if (name_str >= MAXNAMELEN) {
return -E_BAD_PATH;
}
append_path(temp, name);
}
if (strlen(temp) <= MAXPATHLEN) {
strcpy(abspath, temp);
} else {
return -E_BAD_PATH;
}
}
return 0;
}
void back_path(char* path) { |
为了避免修改各种函数,我们将路径转换放在serv.c
底层中,这样我们上层的open/remove
等函数就能自动支持相对路径,以serve_open
举例,我们不再直接使用rq->req_path
而是先使用normalize_path
将其转换为绝对路径// original
if ((r = open_alloc(&o)) < 0) {
ipc_send(envid, r, 0, 0);
return;
}
// new
char cwd[MAXPATHLEN];
char path[MAXPATHLEN];
syscall_get_cwd(envid, cwd);
if ((r = normalize_path(rq->req_path, cwd, path)) < 0) {
ipc_send(envid, r, 0, 0);
return;
}
// original
if ((rq->req_omode & O_CREAT) && (r = file_create(path, &f)) < 0 && r != -E_FILE_EXISTS) {
ipc_send(envid, r, 0, 0);
return;
}
最后实现系统调用syscall_get/set_cwd
用于设置、获取进程的工作目录
- 首先,不需要检查权限,这里主要是为了让子Shell给父Shell设置工作目录
- 其次,请在kseg0地址实现字符串拷贝,否则会产生难以预期的问题例如
address to low
等等,特别是可能涉及两个不同进程的虚拟空间至此,MOS已经具备支持相对路径的所有必要条件了。在后续实现int sys_set_cwd(u_int envid, const char* path) {
struct Env* e;
try(envid2env(envid, &e, 0));
u_long va = (u_long )path;
Pte* pte;
page_lookup(curenv->env_pgdir, va, &pte);
u_long pa = PTE_ADDR(*pte) | PTE_FLAGS(va);
u_long kva = KADDR(pa);
const char* kernpath = (char*) kva;
if (strlen(kernpath) > 1024) {
return -E_BAD_PATH;
}
strcpy(e->env_cwd, kernpath);
return 0;
}
int sys_get_cwd(u_int envid, char* path) {
struct Env* e;
try(envid2env(envid, &e, 0));
u_long va = (u_long)path;
Pte* pte;
page_lookup(curenv->env_pgdir, va, &pte);
u_long pa = PTE_ADDR(*pte) | PTE_FLAGS(va);
u_long kva = KADDR(pa);
char* kernpath = (char*) kva;
strcpy(kernpath, e->env_cwd);
return 0;
}cd / pwd
时装载即可
2.环境变量管理
同理,为每个进程设置特定的的虚拟地址用作存储环境变量的空间,我使用了0x50000000
,远离文件存放的虚拟空间// ---环境变量
struct Var
{
char name[MAXVARNAMELEN + 1];
char value[MAXVARVALUELEN + 1];
int flag;
};
接下来为init.c
启动的Shell
,也就是每次显示的父Shell,设置环境变量空间,这里我直接判断该Shell是否是2号进程(init
进程)的子进程,如果是则说明该Shell不需要继承任何Shell的环境变量,重新申请一片空间即可void init_environs() {
debugf("[%08x]: parent_id: %08x\n", env->env_id, env->env_parent_id);
struct Env* e = &envs[2];
debugf("The second process: [%08x], with parent id: [%08x]\n", e->env_id, e->env_parent_id);
if (env->env_parent_id == e->env_id) {
debugf("[%08x]: The First Shell\n", env->env_id);
// 便于fork子进程更改
debugf("[%08x]: alloc a page for 0x%08x\n", env->env_id, ENVIRON_VA);
syscall_mem_alloc(0, (void *)ENVIRON_VA, PTE_LIBRARY | PTE_D | PTE_V);
debugf("[%08x]: alloc physical page 0x%08x\n", env->env_id, vpt[VPN(ENVIRON_VA)] & ~0x3ff);
} else {
debugf("[%08x]: Not The First Shell\n", env->env_id);
debugf("[%08x]: recv physical page 0x%08x\n", env->env_id, vpt[VPN(ENVIRON_VA)] & ~0x3ff);
debugf("[%08x]: alloc a page for 0x%08x\n", env->env_id, ENVIRON_VA);
struct Var * vars = (struct Var*)ENVIRON_VA;
for (int i = 0; i < VAR2BLK; i++) {
struct Var* cur = vars + i;
if ((cur->flag & VAR_V)) {
debugf("[%08x]: get ENV $%s = %s\n", env->env_id, cur->name, cur->value);
}
}
}
}
对于不是由2号进程开启的Shell
进程,其必然由某个Shell进程启动,我们将ENVIRON_VA
位置的页面设置为PTE_LIBRARY
,则子进程自动继承,对于后续问题,我们在实现declare时再讨论
总之,目前我们的Shell可以将环境变量传递下去了
3.指令输入优化
无.b后缀优化
在目前spawn的基础上,尝试为命令加上/
和.b
然后再次运行。例如int child = spawn(argv[0], argv);
if (child < 0) {
char cmd_b[1024];
int cmd_len = strlen(argv[0]);
strcpy(cmd_b, argv[0]);
// 非工作目录使用ls.b
if (cmd_b[cmd_len - 1] == 'b' && cmd_b[cmd_len - 2] == '.') {
strcpy(cmd_b, "/");
strcat(cmd_b, argv[0]);
} else {
// 使用ls
strcpy(cmd_b, "/");
strcat(cmd_b, argv[0]);
cmd_b[cmd_len + 1] = '.';
cmd_b[cmd_len + 2] = 'b';
cmd_b[cmd_len + 3] = '\0';
}
child = spawn(cmd_b, argv);
}
debugf("[%08x] is spawned [%08x] in runcmd_normal for normal code\n", child, env->env_id);
终端输出优化
目前Shell使用的readline
的显示功能仅仅是我们输入QEMU后附带的,不利于我们编辑或者查看
为此我们需要专门实现打印对于终端的输入,每次用户输入字符后重新打印终端void draw(const char* prompt, const char*buf, int line_len, int cursor, int position) {
if (position == 1) {
printf("\x1b[B");
}
printf("\r\x1b[K");
printf("%s", prompt);
printf("%s", buf);
if (line_len > 0) { // 仅当有内容时才移动
for (int i = 0; i < (line_len - cursor); i++)
{
printf("\b"); // 光标左移一个字符
}
}
}
- 该函数用于打印光标和输入,
- 大概是由于QEMU本身具有一定解释的能力,其会自动解释up-arrow、down-arrow,为此我只能在用户输入up-arrow时输出一个down-arrow抵消,对于down-arrow则不需要额外输出,因为我们每次输入都在最底部,down-arrow不会产生影响
重写readline
函数,使其具备循环读入和打印功能,目前先省略和history
有关的部分,这部分内容和后续history
指令一并void display(char* buf, u_int n, const char* prompt, int interact) {
const char * msg = "i am up arrow";
int r;
int cursor = 0; // 光标位置
int line_len = 0; // 输入总字符数
char* now = buf; // 保留用户当前输入
buf[0] = '\0';
if (interact) {
draw(prompt, buf, line_len, cursor, 0);
}
// read
while (1) {
char c;
if ((r = read(0, &c, 1)) != 1) {
if (r < 0) {
debugf("read error : %d\n", r);
}
exit();
}
int modified = 0;
int position = 0;
switch (c) {
case '\r':
case '\n':
buf[line_len] = '\0';
if (now != buf) {
strcpy(now, buf);
}
return;
// delete
case '\b':
case 0x7f:
if (cursor > 0) {
memmove(&buf[cursor - 1], &buf[cursor], line_len - cursor);
cursor--;
line_len--;
buf[line_len] = '\0';
modified = 1;
}
break;
// up down left right
case '\x1b':
char seq[3];
if (read(0, &seq[0], 1) != 1) continue;
if (seq[0] == '[') {
// read A/B/C/D
if (read(0, &seq[1], 1) != 1) continue;
switch (seq[1]) {
case 'A': // up-arrow
break;
case 'B': // down-arrow
break;
case 'C': // left-arrow
if (cursor < line_len) {
cursor++;
}
break;
case 'D': // right arrow
if (cursor > 0) {
cursor--;
}
break;
}
}
break;
// Ctrl 快捷键
case 0x01: // Ctrl-A
cursor = 0;
break;
case 0x05: // Ctrl-E
cursor = line_len;
break;
case 0x0B: // Ctrl-K
if (cursor < line_len) {
if (history_inst != history_cnt) {
strcpy(now, buf);
buf = now;
history_inst = history_cnt;
}
buf[cursor] = '\0';
line_len = cursor;
modified = 1;
}
break;
case 0x15: // Ctrl-U
if (cursor > 0) {
memmove(buf, &buf[cursor], line_len - cursor + 1);
line_len -= cursor;
cursor = 0;
modified = 1;
}
break;
case 0x17: // Ctrl-W
if (cursor > 0) {
int end = cursor;
// skip space
while (end > 0 && buf[end - 1] == ' ') {
end--;
}
int start = end;
// skip word
while (start > 0 && buf[start - 1] != ' ') {
start--;
}
if (end > start) {
memmove(&buf[start], &buf[end], line_len - end + 1);
line_len -= (end - start);
cursor = start;
modified = 1;
}
break;
}
break;
// ASCII
default:
if (c < 32 || c > 126) break;
if (line_len < n - 1) {
if (cursor < line_len) {
memmove(&buf[cursor + 1], &buf[cursor], line_len - cursor);
}
buf[cursor] = c;
cursor++;
line_len++;
buf[line_len] = '\0';
modified = 1;
}
break;
}
// history
if (interact) {
draw(prompt, buf, line_len, cursor, position);
}
}
}
历史指令功能
为了实现历史指令,我们首先为/.mosh_history
建立一个缓冲区存储信息const int HISTORY_SIZE = 20; // MAX
static char history_buf[21][1024]; // 缓存/.mosh_history
static int history_fd = -1; // /.mosh_history fd
static int history_cnt = 0; // 实际存放历史指令数
char buf[1024]; // 每次读入缓冲区
然后编写read_in_history
和write_out_history
辅助函数用于处理/.mosh_history
void write_out_history() {
int r;
int i;
if (history_fd < 0) return;
if (history_cnt < HISTORY_SIZE) {
struct Fd* fd = (struct Fd*)num2fd(history_fd);
fd->fd_offset = ((struct Filefd*) fd)->f_file.f_size;
} else {
ftruncate(history_fd, 0);
seek(history_fd, 0);
for (int i = 1; i < HISTORY_SIZE; i++) {
write(history_fd, history_buf[i], strlen(history_buf[i]));
write(history_fd, "\n", 1);
}
}
write(history_fd, buf, strlen(buf));
write(history_fd, "\n", 1);
}
void read_in_history() {
int r;
history_fd = -1;
if ((history_fd = open("/.mosh_history", O_RDWR)) < 0) {
if ((r = create("/.mosh_history", FTYPE_REG)) != 0) {
debugf("can't create /.mosh_history: %d\n", r);
return;
}
if ((r = open("/.mosh_history", O_RDWR)) < 0) {
debugf("can't open /.mosh_history: %d\n", r);
}
history_fd = r;
}
history_cnt = 0;
if (history_fd < 0) return;
for (int i = 0; i < HISTORY_SIZE; i++) {
char* pstr = history_buf[i];
while (1) {
if ((r = read(history_fd, pstr, 1)) != 1) {
if (r < 0) {
debugf("error when read history for %d\n", r);
exit();
}
if (r == 0) {
*pstr = '\0';
return;
}
}
if (*pstr == '\n') {
*pstr = '\0';
history_cnt++;
break;
}
pstr++;
}
}
}
create
函数目前不是必须的,使用带有O_CREAT
的open
函数同样可以达到目的,现在所有的历史指令按照从旧到新的顺序排列在缓冲区中
随后为了方便用知道用户目前处于哪条历史指令,我们使用history_inst
记录,0 ~ history_cnt - 1
表示历史指令,history_cnt
即用户现在编辑的指令,在原有display
基础上加入即可case 'A': // up-arrow
// debugf("%s\n", msg);
position = 1;
if (history_cnt > 0) {
if (history_inst > 0) {
history_inst--;
buf = history_buf[history_inst];
line_len = strlen(buf);
cursor = line_len;
}
}
break;
case 'B': // down-arrow
position = -1;
if (history_cnt > 0) {
if (history_inst < history_cnt - 1) {
history_inst++;
buf = history_buf[history_inst];
line_len = strlen(buf);
cursor = line_len;
} else if (history_inst == history_cnt - 1) {
history_inst++;
buf = now;
line_len = strlen(now);
cursor = line_len;
}
}
break;
- 由于我们只是简单修改了buf指针的指向,这意味着,如果用户调出某个历史指令,然后进行编辑,我们必须进行深克隆(,即复制到
全局buf
中,然后再修改,为此专门保留了指向全局buf
的指针now至此,MOS已经能较为优雅地和用户交互了// 如果希望对buf进行修改,判断是否是历史指令并复制
if (history_inst != history_cnt) {
strcpy(now, buf);
buf = now;
history_inst = history_cnt;
}
4.实现更多功能
原有的Shell解析过于简陋,为此我们需要再其基础上递归下降执行命令,不过还是一步一步叠加吧,目前Shell运行逻辑如下init.c------|------wait
spawn
|
sh.c-|------|------wait
fork
|
runcmd-|------|------------|------wait
(fork) spawn
| |
| /cmd.b-|------
|
parsecmd-|-----|-----------|------wait
| |
(fork) spawn
... ...wait
会等待所有子进程结束再返回(包括fork/spawn
)()
代表fork
的执行并不是确定的,只有存在pipe
时才会进行该操作
请记住上面这个略显丑陋的执行流程图,下面我们从简到繁,一步步扩充它
重定向追加
加入识别>>
,使用O_APPEND
参数在底层实现,或者直接获取fd改变其offset,其余同重定向输出一致
注释功能
加入识别#
,然后直接返回argc
不继续parse
加入变量替换功能
这个需要在get_next_token
中修改,如果在parsecmd
中加入的话,只能识别单个的变量例如$ls_alias
,但是无法识别类似/dir1/$dir_path/dir2
这种复合的变量
type = _getNextToken(&begin, &last_end);
if (type == TOKEN_WORD) {
char* s;
if ((s = strchr(begin, '$')) != 0) {
debugf("%s contains $\n", begin);
*s = '\0';
s++;
strcpy(cmd_buf, begin);
char name[MAXVARNAMELEN + 1];
char* p = name;
char value[MAXVARVALUELEN + 1];
while (ISVAR(*s)) {
*p = *s;
p++;
s++;
}
*p = '\0';
if (search(name, value) != 1) {
debugf("syntax error: unrecognized variable '%s'\n", name);
exit();
}
strcat(cmd_buf, value);
if (*s) {
strcat(cmd_buf, s);
}
*buf = cmd_buf;
} else {
*buf = begin;
}
} else {
*buf = begin;
}
加入反引号功能
该功能我决定在runcmd
前面充当预处理的功能,即指令在进入runcmd
前先处理反引号。如果识别到了反引号,则再fork
一个进程用runcmd
进行执行,并使用pipe接收该进程的输出,然后拼接指令,执行流程变为init.c------|------wait
spawn
|
sh.c-|------|------wait
fork
|
revertcmd-|-----------|------(wait)
| (fork)
| |
| runcmd-|
| ...
|
runcmd-|------|------------|------wait
(fork) spawn
| |
| /cmd.b-|------
|
parsecmd-|-----|-----------|------wait
| |
(fork) spawn
... ...int pid = fork();
if (pid == -1) {
return -1;
}
if (pid == 0) {
dup(pipefd[1], 1);
close(pipefd[1]);
close(pipefd[0]);
// debugf("`child` running command %s\n", cmd);
runcmd(cmd);
// debugf("`child` finished running command %s\n", cmd);
exit();
} else {
// dup(pipefd[0], 0);
// debugf("[%08x] is forked from [%08x] in _execute for runcmd_conditional\n", pid, env->env_id);
close(pipefd[1]);
int r;
int bytes = 0;
int i;
for (i = 0; i < maxLen; i++) {
if ((r = read(pipefd[0], output + i, 1)) != 1) {
if (r < 0) {
debugf("read error: %d\n", r);
}
break;
}
bytes++;
}
output[i] = '\0';
// debugf("DEBUG: _execute captured %d bytes. Output is: <START_OF_OUTPUT>%s<END_OF_OUTPUT>\n", bytes, output);
// debugf("DEBUG: _execute output (hex bytes): <START>");
// debugf("[%08x] `parent` read output: <%s>\n", env->env_id, output);
close(pipefd[0]);
wait(pid);
}
加入条件执行功能
其实条件执行的每个部分都是独立的指令,所以我们可以将其单独执行,对于是否执行成功,我们按照提示修改进程的exit
函数,子进程销毁前先给父进程发生执行消息void exit(void) {
// After fs is ready (lab5), all our open files should be closed before dying.
close_all();
syscall_ipc_try_send(env->env_parent_id, env_exit_status, 0, 0);
syscall_env_destroy(0);
user_panic("unreachable code");
}
void libmain(int argc, char **argv) {
// set env to point at our env structure in envs[].
env = &envs[ENVX(syscall_getenvid())];
// call user main routine
env_exit_status = main(argc, argv);
// exit gracefully
exit();
}
接下来我们按照&&
、||
、;
切分指令并运行runcmd
if (r == 0) {
// capture output from backquote commands
char new_buf[1024];
strcpy(new_buf, cmd_buf);
revert_commands(new_buf);
exit_status = runcmd(new_buf);
syscall_ipc_try_send(env->env_parent_id, exit_status, 0, 0);
exit();
} else {
// debugf("[%08x] is forked from [%08x] in runcmd_conditonal for runcmd\n", r, env->env_id);
syscall_ipc_recv(0);
wait(r);
exit_status = env->env_ipc_value;
// debugf("command %s and op %c exit with return value %d\n", cmd_buf, op, exit_status);
}
至此,我们所有基础的工作都已经就位,接下来只要在基础上编写指令即可
5.实现更多指令
这里采用函数表的同一格式int runcmd(char *s) {
// ...
if (strcmp(argv[0], "history") == 0) {
// built-in cmd history
type = BUILTIN_history;
} else if (strcmp(argv[0], "cd") == 0) {
// built-in cmd cd
type = BUILTIN_cd;
} else if (strcmp(argv[0], "pwd") == 0) {
// built-in cmd pwd
type = BUILTIN_pwd;
} else if (strcmp(argv[0], "exit") == 0) {
// built-in cmd exit
type = BUILTIN_exit;
} else if (strcmp(argv[0], "declare") == 0) {
// built-in cmd declare
type = BUILTIN_declare;
} else if (strcmp(argv[0], "unset") == 0) {
// built-in cmd unset
type = BUILTIN_unset;
} else {
// normal cmd
func = runcmd_normal;
}
if (type < MAX_BUILTIN) {
func = builtin_table[type];
}
int r = func(argc, argv);
if (rightpipe) {
wait(rightpipe);
}
return r;
}
我们将一般指令封装入runcmd_normal
中,内建指令不使用spawn
可以直接执行
内建指令
由于我们每次都是先fork
子进程在进行runcmd
,这就有一点问题,我们在子进程做出的修改不会影响到父进程,这无法满足内建指令的需求。
我们发现(为了偷懒),内建指令不会作为管道的接收方,这意味着,同一个内建指令。最多只是原Shell的直接子进程来执行的,所以我们可以通过IPC将需要做的事情传递给父进程等等
history
读文件即可,略
cd
即改变当前Shell的工作目录,注意其实还是改变的是fork子进程的,所以我们通过syscall_set_cwd
函数为父进程设置即可void flush_path(const char* curpath) {
char path[MAXPATHLEN];
debugf("flush %s to env %08x\n", curpath, env->env_id);
syscall_set_cwd(0, curpath);
syscall_get_cwd(0, path);
debugf("env %08x curpath: %s\n", env->env_id, path);
path[0] = '\0';
debugf("flush %s to env %08x\n",curpath, env->env_parent_id);
syscall_set_cwd(env->env_parent_id, curpath);
syscall_get_cwd(env->env_parent_id, path);
debugf("env %08x curpath: %s\n", env->env_parent_id, path);
}
pwd
使用syscall_get_cwd
即可。对于这种不会改变状态的内建指令我们不需要过多考虑,因为子进程继承了父进程大量的信息
exit
自己退出的同时,通知父进程退出void sysexit(int exit_status) {
close_all();
syscall_ipc_try_send(env->env_parent_id, exit_status, 0, 0);
syscall_env_destroy(0);
user_panic("unreachable code");
}
void exit(int argc, char** argv) {
sysexit(-E_EXIT);
}
// parent shell
else {
// debugf("[%08x] is forked from [%08x] in runcmd_conditonal for runcmd\n", r, env->env_id);
syscall_ipc_recv(0);
wait(r);
exit_status = env->env_ipc_value;
if (exit_status == -E_EXIT) {
exit();
}
// debugf("command %s and op %c exit with return value %d\n", cmd_buf, op, exit_status);
}
特判一下子进程发送的消息即可,如果是-E_EXIT
则父进程也直接退出
declare
实现是较为简单的,现在可以解释ENVIRON_VA
的使用方式了
由于该指令是内建指令,我们设置页面为PTE_LIBRARY
则可以让子进程直接修改我们环境变量表,但是我们只想fork
产生的子进程修改我们的环境变量(即执行的declare指令)。对于spawn
执行其他指令(sh等)我们需要其继承环境变量即可。
所以我们修改spawn.c
,特判ENVIRON_VA
,如果是的话,那么拷贝一个副本,并对其中的非环境变量进行更新if (va == ENVIRON_VA) {
if ((r = syscall_mem_alloc(child, va, perm)) < 0) {
debugf("spawn: can't alloc page %x for %x: %d\n", va, child, r);
goto err2;
}
if ((r = syscall_mem_cpy(0, va, child, va)) < 0) {
debugf("spawn: can't copy page %x for %x: %d", va, child, r);
goto err2;
}
if ((r = syscall_fresh_environs(child)) < 0) {
debugf("spawn: can't fresh envrions in %x: %d",child, r);
goto err2;
}
continue;
}u_long env2kva(struct Env* env, u_long va) {
Pte *pte;
page_lookup(env->env_pgdir, va, &pte);
u_long pa = PTE_ADDR(*pte) | PTE_FLAGS(va);
u_long kva = KADDR(pa);
return kva;
}
int sys_mem_cpy(u_int srcid, void* srcva, u_int dstid, void* dstva) {
struct Env* srcenv;
struct Env* dstenv;
Pte* srcpte;
Pte* dstpte;
if (is_illegal_va(srcva) || is_illegal_va(dstva)) {
return -E_INVAL;
}
try(envid2env(srcid, &srcenv, 1));
try(envid2env(dstid, &dstenv, 1));
u_long srckva = env2kva(srcenv, srcva);
u_long dstkva = env2kva(dstenv, dstva);
memcpy((void*) dstkva, (void*) srckva, PAGE_SIZE);
return 0;
}
int sys_fresh_environs(u_int envid) {
struct Env* e;
try(envid2env(envid, &e, 1));
u_long kva = env2kva(e, ENVIRON_VA);
int i;
for (i = 0; i < VAR2BLK; i++) {
struct Var* cur = ((struct Var *) kva) + i;
if ((cur->flag & VAR_V) && !(cur->flag & VAR_E)) {
cur->flag &= ~VAR_V;
}
}
return 0;
}
unset
同上略
普通指令
其实只有ls
需要做一点修改,原有的ls.c
只支持从根目录进行char cwd[MAXPATHLEN];
syscall_get_cwd(0, cwd);
char path[MAXPATHLEN];
char prefix[MAXPATHLEN];
int r;
if (argc == 0) {
if ((r = normalize_path(cwd, cwd, path)) < 0) {
}
strcpy(prefix, path);
ls(path, prefix);
} else {
for (i = 0; i < argc; i++) {
if ((r = normalize_path(argv[i], cwd, path)) < 0) {
}
strcpy(prefix, path);
ls(path, prefix);
}
}
好了,目前Shell已经具备所有基础功能了,如果能加入tab
自动补全就好了(doge)