shell增强文档
下面将按照功能的增加来阐述自己的实现思路
实现不带.b后缀指令
实现思路
在 user/lib/spawn.c 的spawn函数中增加判断:若原始的路径打不来,则尝试打开原始的路径 + .b。先判断再加 .b 可以让带 .b 后缀的可执行文件不带 .b 执行,同时也支持其他格式的可执行文件
实现指令条件执行
实现思路
- 首先我对
user/lib/libos.c和user/libn/wait.c中的exit和wait做了处理,使他们可以利用ipc机制处理返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int wait_return(u_int envid) { const volatile struct Env *e; e = &envs[ENVX(envid)]; u_int who = 0; int status = ipc_recv(&who, 0, 0); if (who == envid) { while (e->env_id == envid && e->env_status != ENV_FREE) { syscall_yield(); } return status; } return -1; }
void exit(int status, int need) { close_all(); if (need) { env = &envs[ENVX(syscall_getenvid())]; ipc_send(env->env_parent_id, status, 0, 0); } syscall_env_destroy(0); user_panic("unreachable code"); }
|
目前就有两套wait——exit进行父子进程的等待与销毁:
那么这个返回值的过程只需要发生在spawn出的子进程与runcmd中的父进程即可,则两套wait——exit需要考虑具体用哪个(这里的实现可拓展性太差,应该统一到一起的,我太懒了),值得一提的是spawn出的子进程运行在user/lib/libos.c中的libmain函数中
- 既然可以处理返回值,那么在
user/sh.c中就要更改**_gettoken、parsecmd、runcmd函数,似的可以解析&& ||符号,处理&& ||符号时,我的处理是让子进程返回运行已解析的指令,父进程等待返回值后递归调用parsecmd**去处理,依靠函数调用传参来保存logic_op, last_logic_op, status等参数
测试
1 2 3 4 5 6 7 8 9
| true && ls true || ls false && ls false || ls true && true && false || true && true && ls || ls && ls && ls || ls || ls true && true && true && true && true && ls false || false || false || false || false || ls true && true && false || true && true && ls false || false || true && false || ls
|
实现更多指令
实现思路
三个指令依次实现在各自的文件下
- mkdir需要增加文件系统中对于权限位O_MKDIR的实现,对于指令参数的实现也可以参考已经给出的ls等指令的实现,利用ARGBEGIN宏与flag数组
- touch利用
O_RDONLY、O_CREAT调用已有的open接口即可
- rm我使用stat接口来判断路径是文件还是目录
实现反引号
实现思路
这里的实现思路是在user/sh.c的**_gettoken中解析到``的时候读取反引号内的内容,然后开一个管道且创建一个子进程,子进程拿到管道的写端,调用runcmd**执行反引号的内容;父进程则拿到管道的读端,将从管道中读到的内容封装成一个字符串参数,之后返回w
其中有一些小细节,如不要让管道堵塞,父进程不能wait子进程执行结束再读,而是参考管道的实现,二者并行;再者需要更改gettoken函数,使其可以保存上面讲到父进程读出的字符串参数
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
| if (*s == '`') { char *start = ++s; while (*s && *s != '`') s++; if (*s == '`') { *s++ = 0; int p[2]; pipe(p); int child = fork(); if (child == 0) { dup(p[1], 1); close(p[1]); close(p[0]); runcmd(start); exit(0, 0); } else { close(p[1]); char buffer[1024]; int n = 0; while(1) { int bytes_read = read(p[0], buffer + n, sizeof(buffer) - n - 1); if (bytes_read <= 0) { break; } n += bytes_read; } buffer[n] = 0; close(p[0]); wait(child); memcpy(buf, buffer, strlen(buffer) + 1); *p1 = 0; *p2 = s; return 'w'; } } }
int gettoken(char *s, char **p1) { static int c, nc; static char *np1, *np2; static char buffer[1024];
if (s) { nc = _gettoken(s, &np1, &np2, &buffer); return 0; } c = nc; if (np1 == 0) { *p1 = buffer; } else { *p1 = np1; } nc = _gettoken(np2, &np1, &np2, &buffer); return c; }
|
实现注释功能
实现思路
最简单的功能,parsecmd中读到#后返回argc即可
实现历史指令
实现思路
类似于后台的实现,由于甚至不需要进程间共享,只需要在sh进程中处理即可,故此处不做过多赘述,可以参考后面后台的实现
实现一行多指令
实现思路
在 user/sh.c 的parsecmd函数中增加对于;的处理,开一个子进程返回处理当前已经解析出来的指令,让父进程等待子进程结束后继续解析
1 2 3 4 5 6 7 8 9
| case ';':; left = fork(); if (left > 0) { wait(left); return parsecmd(argv, rightpipe); } else { return argc; } break;
|
实现追加重定向
实现思路
新增了一个文件权限O_APPEND,只需要在打开文件的时候将文件的偏移量移动到文件末尾即可,追加重定向时用此权限位打开文件
1 2 3 4
| if (fd->fd_omode & O_APPEND) { offset = f->f_file.f_size; }
|
实现引号支持
实现思路
实现较为简单,只需要在**_gettoken**解析时候将引号内内容看成整体即可
实现前后台任务管理
实现思路
这里的重点在于需要有一共享内存区域供所有的进程访问和修改,故有两种处理方式:在内核态中通过系统调用来处理Jobs、利用文件系统建立一共享内存。我选择的是后者,但是未增加文件锁(这里我觉得是有问题的,但是测试并没有很强,若是压力测试的情况下可能会出问题)
一下是我定义的数据结构与实现的一些函数:
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 59 60 61 62 63 64 65 66 67 68
| #define JOB_PATH "jobfile" #define MAXJOBS 64
struct Job { int job_id; int env_id; char status[10]; char cmd[1024]; };
struct Job jobs[MAXJOBS]; int next_job_id; int job_count;
void init_job_data() { int fd = open(JOB_PATH, O_CREAT | O_RDWR); if (fd < 0) { debugf("open jobfile fail\n"); return; } debugf("jobfile fd : %d\n", fd); next_job_id = 1; job_count = 0; int next_job_id_tmp; int job_count_tmp; write(fd, &next_job_id, sizeof(int)); write(fd, &job_count, sizeof(int)); seek(fd, 0); read(fd, &next_job_id_tmp, sizeof(int)); read(fd, &job_count_tmp, sizeof(int)); if (job_count == job_count_tmp && next_job_id == next_job_id_tmp) { debugf("job_data init success!\n"); } else { debugf("read and write init fail\n"); } close(fd); }
void read_job_data() { int fd = open(JOB_PATH, O_RDONLY); if (fd < 0) { debugf("Failed to open job data file for writing\n"); return; } read(fd, &next_job_id, sizeof(int)); read(fd, &job_count, sizeof(int)); read(fd, jobs, sizeof(jobs)); close(fd); }
void save_job_data() { int fd = open(JOB_PATH, O_WRONLY); if (fd < 0) { debugf("Failed to open job data file for writing\n"); return; } write(fd, &next_job_id, sizeof(int)); write(fd, &job_count, sizeof(int)); write(fd, jobs, sizeof(jobs)); close(fd); }
void add_job(int env_id, char *cmd); void update_job_status(int env_id, const char *status); void print_jobs(); void bring_job_to_foreground(int job_id); void kill_job(int job_id);
|
调用以上函数,在runcmd中对内置指令进行处理即可
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
| if (strcmp(argv[0], "jobs") == 0) { print_jobs(); exit(0, 0); }
if (strcmp(argv[0], "fg") == 0) { if (argc < 2) { printf("fg: job id required\n"); exit(0, 0); } int job_id = my_atoi(argv[1]); bring_job_to_foreground(job_id); exit(0, 0); }
if (strcmp(argv[0], "kill") == 0) { if (argc < 2) { printf("kill: job id required\n"); return; } int job_id = my_atoi(argv[1]); kill_job(job_id); return; }
if (background) { add_job(child, buffer); int status = wait_return(child); update_job_status(child, "Done"); }
|
同时注意sh进程不等待fork出的子进程调用runcmd,子进程等待spawn出的进程结束后再销毁