BUAA_OS_Shell实现文档
本人代码完成顺序也是按照如下顺序进行
实现不带 .b
后缀指令
这可以说是最简单的部分,只需要在spawn函数中添加特判即可,如果没找到文件,现在在后缀添加.b再次尝试是否可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int len = strlen(prog); if ((fd = open(prog, O_RDONLY)) < 0) { if (fd == -E_NOT_FOUND && (len < 2 || prog[len - 1] != 'b' || prog[len - 2] != '.')) { char prog_bin[MAXPATHLEN]; strcpy(prog_bin, prog); prog_bin[len] = '.'; prog_bin[len + 1] = 'b'; prog_bin[len + 2] = '\0'; if((fd = open(prog_bin , O_RDONLY)) < 0){ return fd; } } else { return fd; } }
|
实现一行多指令
使用 ;
将多条指令隔开从而从左至右依顺序执行每条指令的功能.
fork出一个子进程,让子进程继续执行,父进程等待子进程执行结束后继续执行;
右侧命令,不断递归达到目的。
1 2 3 4 5 6 7 8 9
| case ';': r = fork(); if (r) { wait(r); return parsecmd(argv, rightpipe); } else { return argc; } break;
|
实现引号支持
类似于OO第一单元,需要将引号内部的内容看作是一个token,针对_gettokken()中的代码,只需要做如下添加并更新p1,p2的位置即可。
1 2 3 4 5 6 7 8 9 10 11 12
| if (*s == '\"') { *s = 0; s++; *p1 = s; while(*s != 0 && *s != '\"') { s++; } *s = 0; s++; *p2 = s; return 'w'; }
|
实现注释功能
最简单的部分!!!
读到#
意味着后面可以不读了
1 2 3
| if (*s == 0 || *s == '#') { return 0; }
|
实现更多指令
首先类似于lab5-exam,在文件系统中增加create服务。并将fsipc_create()和create添加到user/lib/lib.h中。并在/user/include/fsreq.h中的enum中增加FSREQ_CREATE
1 2 3 4 5 6 7 8 9 10 11
| void serve_create(u_int envid, struct Fsreq_create *rq);
int fsipc_create(const char* path, int f_type);
int create(const char* path, int f_type);
struct Fsreq_create { char req_path[MAXPATHLEN]; int f_type; };
|
由于mkdir
中需要判断对应path下的文件类型
1 2 3
| int file_get_type(struct File *f);
#define O_GETTYPE 0x0888
|
file_get_type()
我的写法如下
1 2 3 4 5 6 7 8
| int file_get_type(struct File *f) { if (f->f_type == FTYPE_REG) { return -2237; } else if (f->f_type = FTYPE_DIR){ return -1147; } return 0; }
|
并且修改serve_open()使得在open时可以通过返回值判断文件类型增加如下
1 2 3 4 5 6
| if (rq->req_omode & O_GETTYPE) { if ((r = (file_get_type(f))) < 0) { ipc_send(envid, r, 0, 0); return; } }
|
完成上述工作,我耗费了大量时间思考,接下来的工作就比较简单了
touch.c
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <lib.h>
int main(int argc, char **argv) { int r; if (!((r = open(argv[1], O_RDONLY)) < 0)) { return -1; } if ((r = create(argv[1], FTYPE_REG)) < 0) { fprintf(1, "touch: cannot touch '%s': No such file or directory\n", argv[1]); return -1; } return 0; }
|
mkdir.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <lib.h>
int main(int argc, char **argv) { int r; if (strcmp(argv[1], "-p") == 0) { if (!((r = open(argv[2], O_RDONLY)) < 0)) { return -1; } if ((r = create(argv[2], FTYPE_DIR)) < 0) { char *path = argv[2]; char p[MAXPATHLEN]; int len = strlen(path); for (int i = 0; i < len; i++) { if (path[i] == '/') { create(p, FTYPE_DIR); } p[i] = path[i]; } create(p, FTYPE_DIR); } } else { if (!((r = open(argv[1], O_RDONLY)) < 0)) { fprintf(1, "mkdir: cannot create directory '%s': File exists\n", argv[1]); return -1; } if ((r = create(argv[1], FTYPE_DIR)) < 0) { fprintf(1, "mkdir: cannot create directory '$s': No such file or directory\n", argv[1]); return -1; } } return 0; }
|
rm.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <lib.h>
int main(int argc, char **argv) { struct Fd *fd; struct Filefd *ffd; int r; if (strcmp(argv[1], "-r") == 0) { if ((r = open(argv[2], O_RDONLY)) < 0) { fprintf(1, "rm: cannot remove '%s': No such file or directory\n", argv[2]); return -1; } if ((r = remove(argv[2])) < 0) { return -1; } } else if (strcmp(argv[1], "-rf") == 0) { if ((r = open(argv[2], O_RDONLY)) < 0) { return -1; } if ((r = remove(argv[2])) < 0) { return -1; } } else { if ((r = open(argv[1], O_RDONLY)) < 0) { fprintf(1, "rm: cannot remove '%s': No such file or directory\n", argv[1]); return -1; } r = open(argv[1], O_GETTYPE); if (r == -1147) { fprintf(1, "rm: cannot remove '%s': Is a directory\n", argv[1]); return -1; } else { remove(argv[1]); } } return 0; }
|
实现追加重定向
解决偏移量即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| case '>': if (gettoken(0, &t) != 'w') { debugf("syntax error: > not followed by word\n"); exit(1); } if (flag == 1) { fd=open(t, O_WRONLY | O_CREAT); if (fd < 0) { debugf("failed to open '%s'\n",t); exit(1); } struct Stat st; stat(t, &st); seek(fd, st.st_size); } else { fd=open(t, O_WRONLY | O_CREAT | O_TRUNC); if (fd < 0) { debugf("failed to open '%s'\n",t); exit(1); } } dup(fd,1); close(fd);
break;
|
坑点:open时不只有只写操作!!!
实现反引号
我的总体处理类似于;
处理,不过需要在gettoken时将一对`变成一个,但是这样并不适用于整个所有,个人认为更加有效的处理是将反引号内部运行的结果作为参数传给echo,而不是直接给echo传空值。
1 2 3 4 5 6 7 8 9
| case '`': r = fork(); if (r) { wait(r); return argc; } else { return parsecmd(argv, rightpipe); } break;
|
1 2 3 4 5 6 7 8
| if (*s == '`') { char *p = s; p++; while (*p != '`') { p++; } *p = ' '; }
|
实现指令条件执行
好难!!!
根据提示修改exit函数,并通过ipc将flag信号传递给父进程。
1 2 3 4 5 6 7 8 9 10 11
| void exit(int flag) { #if !defined(LAB) || LAB >= 5 close_all(); #endif
syscall_ipc_try_send(envs[ENVX(env->env_parent_id)].env_parent_id, flag, 0, 0); syscall_env_destroy(0); user_panic("unreachable code"); }
|
接下来就是根据返回值进行不同操作,具体如下
返回值 |
当前符号 |
目标 |
0 |
\ |
\ |
|
寻找下一个&&后命令 |
1 |
\ |
\ |
|
指向下一命令判断返回值 |
0 |
&& |
指向下一命令判断返回值 |
1 |
&& |
寻找下一个\ |
\ |
后命令 |
从env中获取到exit的返回值,在读取到&&和||时进行上述操作即可
1 2 3 4
| if ((r = (syscall_ipc_recv(0)) < 0)) { return r } int value = env -> env_ipc_value
|
坑点:父进程不用wait子进程!!!!!!!!!!!!!!
前后台任务
好难!!!!!!!!!,耗时最长的部分,主要理解一直有误,导致一直卡着
开始一直在用户空间储存结构体发现不对,才将其放在内核态。着实理解不足
在env.h
中增加结构体
1 2 3 4 5 6 7 8
| #define MAXJOBS 1000 struct job{ int job_id; int status; u_int env_id; int killed; char cmd[100]; };
|
并为用户态增加如下系统调用达到访问jobs目的
1 2 3 4 5
| int syscall_fg_job(int fgId); int syscall_kill_job(int killId); int syscall_print_job(); int syscall_add_job(u_int envid, char * cmd); int syscall_done_job(u_int envid);
|
剩下部分就和往年类似,根据hangup判断是否需要等待
1 2 3 4 5 6 7 8 9 10
| r = fork(); if (r) { hangup = 1; syscall_add_job(r, cmd); return parsecmd(argv, rightpipe); } else { hangup = 0; return argc; } break;
|
接下来,kill、fg等各参数根据argv中的参数进行各类操作即可,这里我的syscall_fg_job(fgId)
起到一个返回对应fg的envid的作用,然后让进程等待该进程则可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| void runcmd(char *s) { save_history(s); strcpy(cmd, s); gettoken(s, 0);
char *argv[MAXARGS]; int rightpipe = 0; int argc = parsecmd(argv, &rightpipe); if (argc == 0) { return; } argv[argc] = 0; if (strcmp(argv[0], "fg") == 0) { int fgId = 0; char *str = argv[1]; while (*str >= '0' && *str <= '9') { fgId = fgId * 10 + (*str - '0'); str++; } int envid = syscall_fg_job(fgId); wait(envid); exit(0); return; } else if (strcmp(argv[0], "kill") == 0) { int killId = 0; char *str = argv[1]; while (*str >= '0' && *str <= '9') { killId = killId * 10 + (*str - '0'); str++; } int envid = syscall_kill_job(killId); exit(0); return; } else if (strcmp(argv[0], "jobs") == 0) { syscall_print_job(); exit(0); return; } else if (strcmp(argv[0], "history") == 0) { print_history(); exit(0); return; } int child = spawn(argv[0], argv); close_all(); if (child >= 0) { if (hangup == 0) { wait(child); } } else { debugf("spawn %s: %d\n", argv[0], child); } if (rightpipe) { wait(rightpipe); } exit(0); }
|
注意最后在exit时要通过系统调用将进程标记为结束。
1 2 3 4 5 6 7 8 9 10 11
| void exit(int flag) { #if !defined(LAB) || LAB >= 5 close_all(); #endif
syscall_ipc_try_send(envs[ENVX(env->env_parent_id)].env_parent_id, flag, 0, 0); syscall_done_job(env->env_id); syscall_env_destroy(0); user_panic("unreachable code"); }
|
系统调用内部主要就是各种结构体的赋值,内部参数的输出,较为简单。
做完之后回看这部分,其实没有想的那么难,把思路理清楚,虽然工作量很大,但做起来很快。
历史指令
难点主要在读取上下键极其后续处理上
键 |
编码 |
上 |
27 ‘[‘ ‘A’ |
下 |
27 ‘[‘ ‘B’ |
需要完成以下函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| void save_history(char *cmd); void print_history();
void save_history(char *cmd) { int fd; if ((fd = open(".mosh_history", O_CREAT | O_WRONLY )) < 0){ return; } struct Stat st; stat(".mosh_history", &st); seek(fd, st.st_size); write(fd, cmd, strlen(cmd)); write(fd, "\n", 1); close(fd); return; }
void print_history() { int r; if ((r = open(".mosh_history", O_RDONLY)) < 0){ return; } char buf; while (read(r, &buf, 1)) { fprintf(1, "%c", buf); } close(r); }
|
save_history有点类似于重定向,需要处理一下偏移量的问题。
print_history记得在runcmd()的第一句执行save_history(s)
保存以下即可