lab6-challenge shell

要求总览

  1. 相对路径支持
  2. 环境变量管理
  3. 指令输入优化
    • 无后缀指令支持
    • 终端输入输出优化
    • 历史指令支持
  4. 实现更多功能
    • 追加重定向功能
    • 注释功能
    • 变量替换
    • 反引号执行功能
    • 指令条件执行功能
  5. 实现更多指令
    • 内建指令篇
      • 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) {
int max_len = strlen(path);
int flags = 0;
int i;
// error empty path
if (max_len == 0) {
strcpy(path, "/");
return;
}
// root path
if (max_len == 1 && path[max_len - 1] == '/') {
return;
}

while (max_len >1 && path[max_len - 1] == '/') {
max_len--;
}
path[max_len -1] = '\0';
int index = max_len;
for (i = max_len - 1; i >= 0; i--) {
if (path[i] == '/') {
index = i;
break;
}
}
if (index == 0) {
// root
path[1] = '\0';
} else if (index != max_len) {
path[index] = '\0';
} else {
// error
strcpy(path, "/");
}
}

void append_path(char* abspath, const char* name) {
if (strcmp(name, ".") == 0) {
return;
} else if (strcmp(name, "..") == 0) {
back_path(abspath);
} else {
int len = strlen(abspath);
if (abspath[len - 1] != '/') {
abspath[len++] = '/';
abspath[len] = '\0';
}
strcat(abspath, name);
}
}

为了避免修改各种函数,我们将路径转换放在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等等,特别是可能涉及两个不同进程的虚拟空间
    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;
    }
    至此,MOS已经具备支持相对路径的所有必要条件了。在后续实现cd / pwd时装载即可

2.环境变量管理

同理,为每个进程设置特定的的虚拟地址用作存储环境变量的空间,我使用了0x50000000,远离文件存放的虚拟空间

// ---环境变量
#define ENVIRON_VA 0x50000000
#define VAR2BLK 64 // 最大存储环境变量数量
#define MAXVARNAMELEN 16 // 变量名最大长度 (不含末尾 '\0')
#define MAXVARVALUELEN 16 // 变量值最大长度 (不含末尾 '\0')
#define VAR_V 0x1 // is valid
#define VAR_R 0x2 // is readonly
#define VAR_E 0x4 // is envrionment
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_historywrite_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_CREATopen函数同样可以达到目的,现在所有的历史指令按照从旧到新的顺序排列在缓冲区中

随后为了方便用知道用户目前处于哪条历史指令,我们使用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
    // 如果希望对buf进行修改,判断是否是历史指令并复制
    if (history_inst != history_cnt) {
    strcpy(now, buf);
    buf = now;
    history_inst = history_cnt;
    }
    至此,MOS已经能较为优雅地和用户交互了

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这种复合的变量

#define ISVAR(c) \
(((c) >= 'a' && (c) <= 'z') || \
((c) >= 'A' && (c) <= 'Z') || \
((c) >= '0' && (c) <= '9') || \
((c) == '_'))

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.
#if !defined(LAB) || LAB >= 5
close_all();
#endif
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) {

#if !defined(LAB) || LAB >= 5
close_all();
#endif
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)