实现不带 .b
后缀指令
你需要实现不带 .b
后缀的指令,但仍需兼容带有 .b
后缀的指令,如 ls
与 ls.b
都应能够正确列出当前目录下的文件。
外部指令的执行实际上是在spawn
函数中打开可执行的指令文件创建新进程,并将其装载在新建子进程中,运行指令文件中的主程序。实现思路是更改文件打开程序,当文件名对应文件存在时直接打开即可,不存在时文件名末尾加.b
再次尝试打开。
在spawn()
函数中更改文件打开方式,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if ((fd = open(prog, O_RDONLY)) < 0 ) { int len = 0 ; char newProg[128 ]; for (len = 0 ; len < 128 && prog[len] != '\0' ; len++) { newProg[len] = prog[len]; } newProg[len++] = '.' ; newProg[len++] = 'b' ; newProg[len++] = '\0' ; if ((fd = open(newProg, O_RDONLY)) < 0 ) { return fd; } }
实现指令条件执行
你需要实现 Linux shell 中的 &&
与 ||
。 对于 command1 && command2
,command2
被执行当且仅当 command1
返回 0;对于 command1 || command2
,command2
被执行当且仅当 command1
返回非 0 值。
注: 评测中保证不出现括号。并且需要注意的是,在 bash 中 &&
与 ||
的优先级相同,按照从左到右的顺序求值。
例如 cmd1 || cmd2 && cmd3
,若 cmd1
返回 0,则 cmd1
执行后 cmd2
不会被执行,cmd3
会被执行;若 cmd1
返回非 0 且 cmd2
返回非 0,则 cmd3
将不会被执行。
提示:你可能需要修改 MOS 中对用户进程 exit
的实现,使其能够返回值。
当cmd
执行分为多层,会出现parsercmd
进程,runcmd
进程,而每种进程又有负责执行的子进程和整体的父进程。指令执行结束后,会返回执行结果,并退出进程。实现思路是将每个指令的执行结果发送给父进程,并在parser
环节使用,总体上计算标志之前的全部指令运行结果,并判断是否执行当前指令。
在include/env.h
的进程结构体中加入返回值定义,具体代码如下:
1 2 3 4 struct Env { ... int env_return_value; };
在内核态中添加子进程返回值的系统调用函数,具体代码如下:
1 2 3 4 5 6 7 int sys_return_value (int value) { struct Env *e ; try(envid2env(curenv->env_parent_id, &e, 0 )); e->env_return_value = value; return 0 ; }
在用户态中添加相应的调用系统调用函数的信息发送函数,具体代码如下:
1 2 3 4 int syscall_return_value (int value) { return msyscall(SYS_return_value, value); }
更改exit()
函数,使其在退出进程的同时向父进程发送程序执行返回值,具体代码如下:
1 2 3 4 5 6 7 8 9 10 void exit (int value) {#if !defined(LAB) || LAB >= 5 close_all(); #endif debugf("child: 0x%x send %d to parent: 0x%x\n" , env->env_id, value, env->env_parent_id); syscall_return_value(value); syscall_env_destroy(0 ); user_panic("unreachable code" ); }
更改libmain()
函数,使其获得程序执行结束的返回值,具体代码如下:
1 2 3 4 5 6 void libmain (int argc, char **argv) { env = &envs[ENVX(syscall_getenvid())]; int return_value = main(argc, argv); exit (return_value); }
在_gettoken()
函数中增加运算符与&&
和或||
的解析,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if (*s == '&' && *(s + 1 ) == '&' ){ *p1 = s; *s++ = 0 ; *s++ = 0 ; *p2 = s; return 'y' ; } if (*s == '|' && *(s + 1 ) == '|' ){ *p1 = s; *s++ = 0 ; *s++ = 0 ; *p2 = s; return 'h' ; }
更改parsercmd()
函数的参数,使其可以传递上一个操作和之前全部命令执行结果,并增加与或运算的解析处理,具体代码如下:
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 69 70 int parsecmd (char **argv, int *rightpipe, int last, int flag) { ... } case 'y' :; if (last == 0 && flag != 0 ) { return parsecmd(argv, rightpipe, 0 , 1 ); } if (last == 1 && flag == 0 ) { return parsecmd(argv, rightpipe, 0 , flag); } r = fork(); if (r == 0 ) { return argc; } else { wait(r); if (!iscons(1 )) { dup(0 , 1 ); } if (!iscons(0 )) { dup(1 , 0 ); } int nextFlag = (last == 2 ) ? env->env_return_value : (last == 0 && (flag == 0 && env->env_return_value == 0 )) ? 0 : (last == 1 && (flag == 0 || env->env_return_value == 0 )) ? 0 : 1 ; return parsecmd(argv, rightpipe, 0 , nextFlag); } break ; case 'h' :; if (last == 0 && flag != 0 ) { return parsecmd(argv, rightpipe, 1 , 1 ); } if (last == 1 && flag == 0 ) { return parsecmd(argv, rightpipe, 1 , flag); } r = fork(); if (r == 0 ) { return argc; } else { wait(r); if (!iscons(1 )) { dup(0 , 1 ); } if (!iscons(0 )) { dup(1 , 0 ); } int nextFlag = (last == 2 ) ? env->env_return_value : (last == 0 && (flag == 0 && env->env_return_value == 0 )) ? 0 : (last == 1 && (flag == 0 || env->env_return_value == 0 )) ? 0 : 1 ; return parsecmd(argv, rightpipe, 1 , nextFlag); } break ;
更改parsercmd()
函数的参数后,还应更改其余解析位置的return parsecmd(argv, rightpipe, lastOp, flag)
。应注意,管道处传递时应该保持上一次的符号和之前的结果。
在runcmd()
函数中更改exit()
的返回值以及parsercmd()
的参数,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void runcmd (char *s) { gettoken(s, 0 ); char *argv[MAXARGS]; int rightpipe = 0 ; int argc = parsecmd(argv, &rightpipe, 2 , 0 ); if (argc == 0 ) { exit (1 ); } argv[argc] = 0 ; int child = spawn(argv[0 ], argv); close_all(); if (child >= 0 ) { wait(child); } else { debugf("spawn %s: %d\n" , argv[0 ], child); } if (rightpipe) { wait(rightpipe); } exit (env->env_return_value); }
实现更多指令
你需要实现 touch
,mkdir
,rm
指令,只需要考虑如下情形:
touch <file>
:创建空文件 file
,若文件存在则放弃创建,正常退出无输出。 若创建文件的父目录不存在则输出 touch: cannot touch '<file>': No such file or directory
。 例如 touch nonexistent/dir/a.txt
时应输出 touch: cannot touch 'nonexistent/dir/a.txt': No such file or directory
。
mkdir <dir>
:若目录已存在则输出 mkdir: cannot create directory '<dir>': File exists
,若创建目录的父目录不存在则输出 mkdir: cannot create directory '<dir>': No such file or directory
,否则正常创建目录。
mkdir -p <dir>
:当使用 -p
选项时忽略错误,若目录已存在则直接退出,若创建目录的父目录不存在则递归创建目录。
rm <file>
:若文件存在则删除 <file>
,否则输出 rm: cannot remove '<file>': No such file or directory
。
rm <dir>
:命令行输出: rm: cannot remove '<dir>': Is a directory
。
rm -r <dir>|<file>
:若文件或文件夹存在则删除,否则输出 rm: cannot remove '<dir>|<file>': No such file or directory
。
rm -rf <dir>|<file>
:如果对应文件或文件夹存在则删除,否则直接退出。
文件的增删主要在内核态进行,因此构建新的serv
函数,用于创建和删除文件。实现思路是在用户态创建相应fsipc
函数,并使用ipc
向fs
发送消息进行调用,在内核态创建对应服务函数,调用最底层的file
相关函数实现文件的构建与删除。创建指令文件touch.c
、mkdir.c
、rm.c
,并实现相应程序。
在fs
中创建新的底层文件操作函数,具体代码如下:
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 int file_create (char *path, struct File **file) { char name[MAXNAMELEN]; int r; struct File *dir , *f ; if ((r = walk_path(path, &dir, &f, name)) == 0 ) { return -E_FILE_EXISTS; } if (r != -E_NOT_FOUND || dir == 0 ) { return r; } if (dir_alloc_file(dir, &f) < 0 ) { return r; } strcpy (f->f_name, name); *file = f; return 0 ; } int file_remove (char *path) { int r; struct File *f ; if ((r = walk_path(path, 0 , &f, 0 )) < 0 ) { return r; } file_truncate(f, 0 ); f->f_name[0 ] = '\0' ; file_flush(f); if (f->f_dir) { file_flush(f->f_dir); } return 0 ; } int file_noDir_remove (char *path) { int r; struct File *f ; if ((r = walk_path(path, 0 , &f, 0 )) < 0 ) { return r; } if (f->f_type == FTYPE_DIR) { return -E_RM_DIR; } file_truncate(f, 0 ); f->f_name[0 ] = '\0' ; file_flush(f); if (f->f_dir) { file_flush(f->f_dir); } return 0 ; }
在user/include/fsreq.h
中构建增删文件传递信号的相应结构体,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 enum { ... FSREQ_CREATE, FSREQ_REMOVE, FSREQ_NDREMOVE, ... }; struct Fsreq_remove { char req_path[MAXPATHLEN]; int rm_type; }; struct Fsreq_create { char req_path[MAXPATHLEN]; uint32_t f_type; };
在fs
中创建新的服务函数,具体代码如下:
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 void serve_create (u_int envid, struct Fsreq_create *rq) { struct File *f ; int r; r = file_create(rq->req_path, &f); if (r < 0 ) { ipc_send(envid, r, 0 , 0 ); return ; } f->f_type = rq->f_type; ipc_send(envid, 0 , 0 , 0 ); } void serve_remove (u_int envid, struct Fsreq_remove *rq) { int r; r = file_remove(rq->req_path); ipc_send(envid, r, 0 , 0 ); } void serve_noDir_remove (u_int envid, struct Fsreq_remove *rq) { int r; r = file_noDir_remove(rq->req_path); ipc_send(envid, r, 0 , 0 ); }
在用户态下创建对应fsipc
函数,用于ipc
信号发送,具体代码如下:
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 int fsipc_create (const char *path, uint32_t type) { struct Fsreq_create *req ; req = (struct Fsreq_create *)fsipcbuf; if (strlen (path) >= MAXPATHLEN) { return -E_BAD_PATH; } strcpy ((char *)req->req_path, path); req->f_type = type; return fsipc(FSREQ_CREATE, req, 0 , 0 ); } int fsipc_remove (const char *path) { if (strlen (path) == 0 || strlen (path) > MAXPATHLEN) { return -E_BAD_PATH; } struct Fsreq_remove *req = (struct Fsreq_remove *)fsipcbuf; strcpy ((char *)req->req_path, path); return fsipc(FSREQ_REMOVE, req, 0 , 0 ); } int fsipc_noDir_remove (const char *path) { if (strlen (path) == 0 || strlen (path) > MAXPATHLEN) { return -E_BAD_PATH; } struct Fsreq_remove *req = (struct Fsreq_remove *)fsipcbuf; strcpy ((char *)req->req_path, path); return fsipc(FSREQ_NDREMOVE, req, 0 , 0 ); }
在用户态创建具体文件操作函数,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 int create (const char *path, uint32_t type) { return fsipc_create(path, type); } int remove (const char *path) { return fsipc_remove(path); } int nd_remove (const char *path) { return fsipc_noDir_remove(path); }
创建三个命令文件,调用文件操作函数,具体代码如下:
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 int main (int argc, char **argv) { int r; r = create(argv[1 ], FTYPE_REG); if (r < 0 && r != -E_FILE_EXISTS) { printf ("touch: cannot touch '%s': No such file or directory\n" , argv[1 ]); } return 0 ; } int main (int argc, char **argv) { int r; if (argc == 2 ) { r = create(argv[1 ], FTYPE_DIR); if (r == -E_FILE_EXISTS) { printf ("mkdir: cannot create directory '%s': File exists\n" , argv[1 ]); } else if (r < 0 ) { printf ("mkdir: cannot create directory '%s': No such file or directory\n" , argv[1 ]); } } else { char name[MAXPATHLEN] = {}; int pos = 0 ; for (char *c = argv[2 ]; *c != '\0' ; c++) { if (*c == '/' ) { name[pos] = '\0' ; r = create(name, FTYPE_DIR); name[pos] = '/' ; pos++; } else { name[pos] = *c; pos++; } } if (name[pos] != '/' ) { name[pos] = '\0' ; r = create(name, FTYPE_DIR); } } return 0 ; } int main (int argc, char **argv) { int r; if (argc == 2 ) { r = nd_remove(argv[1 ]); if (r == -E_RM_DIR) { printf ("rm: cannot remove '%s': Is a directory\n" , argv[1 ]); } else if (r < 0 ) { printf ("rm: cannot remove '%s': No such file or directory\n" , argv[1 ]); } } else { if (strcmp (argv[1 ], "-r" ) == 0 ) { r = remove(argv[2 ]); if (r < 0 ) { printf ("rm: cannot remove '%s': No such file or directory\n" , argv[2 ]); } } else { r = remove(argv[2 ]); } } return 0 ; }
实现反引号
你需要使用反引号实现指令替换。只需要考虑 echo
进行的输出,你需要将反引号内指令执行的所有标准输出替换为 echo
的参数。
题目只需考虑echo
指令的反引号输出,其等同于直接执行反引号内指令。实现思路是在指令解析环节,如果遇到反引号,则将当前解析的argv
数组退格,重新解析cmd
。
在_gettoken()
函数中增加反引号的解析,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (*s == '`' ){ *p1 = s; *s++ = 0 ; *p2 = s; char *str = s; for (; *str && *str != '`' ; str++); if (*str == '`' ) { *str = 0 ; } return '`' ; }
在parsercmd()
函数中增加反引号的解析处理,具体代码如下:
1 2 3 4 5 6 7 8 case '`' : if ((gettoken(0 , &t)) != 'w' ) { debugf("syntax error: ` not followed by word\n" ); exit (1 ); } argv[argc - 1 ] = t; break ;
实现注释功能
你需要使用 #
实现注释功能,例如 ls | cat # this is a comment meow
,ls | cat
会被正确执行,而后面的注释则会被抛弃。
对于一行多条指令,注释符优先级最高,#
之后的指令均不需要解析或执行。实现思路是在指令解析环节,如果遇到注释符号,使用fork
函数创建子进程,并使用子进程返回之前解析出的命令,主进程等待子进程解析完毕后直接退出。
在parsercmd()
函数中增加注释符的解析处理,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 case '#' : r = fork(); if (r == 0 ) { return argc; } else { wait(r); exit (0 ); } break ;
实现历史指令
你需要实现 shell 中保存历史指令的功能,可以通过 Up
和 Down
选择所保存的指令并执行。你需要将历史指令保存到根目录的 .mosh_history
文件中(一条指令一行),为了评测的方便,我们设定 $HISTFILESIZE=20
(bash 中默认为 500),即在 .mosh_history
中至多保存最近的 20 条指令。你还需要支持通过 history
命令输出 .mosh_history
文件中的内容。
注:在 bash 中,history
为 shell built-in command,我们规定需要将 history
实现为 built-in command。
实现历史指令可以在用户态建立数组,存储当前指令、当前指令数目、当前光标位置,在访问时进行使用。具体思路是创建相关全局变量,并在命令解析环节按照输入的方向键调整光标以及控制台输出,实现历史指令的输出与执行。
在user/sh.c
中定义相关变量,具体代码如下:
1 2 3 4 5 int nCmds = 0 ;int lenCmds[500 ];int index;char curCmd[1024 ];
在user/sh.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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 void saveCmd (char *cmd) { int r, fd; if ((r = open("/.mosh_history" , O_WRONLY | O_APPEND)) < 0 ) { if ((r = create("/.mosh_history" , FTYPE_REG)) < 0 ) { user_panic("failed to create .mosh_history\n" ); } if ((r = open("/.mosh_history" , O_WRONLY | O_APPEND)) < 0 ) { user_panic("failed to open .mosh_history\n" ); } } fd = r; if ((r = write(fd, cmd, strlen (cmd))) != strlen (cmd)) { user_panic("fail to write cmd in .mosh_history\n" ); } if ((r = write(fd, "\n" , 1 )) != 1 ) { user_panic("fail to write \n in .mosh_history\n" ); } lenCmds[nCmds] = strlen (cmd) + 1 ; nCmds++; close(fd); } void getCmd (int serial, char *cmd) { int r, fd; char buf[MAXFILESIZE]; if ((r = open("/.mosh_history" , O_RDONLY)) < 0 ) { user_panic("failed to open .mosh_history\n" ); } fd = r; for (int i = 0 ; i < serial; i++) { if ((r = read(fd, buf, lenCmds[i])) != lenCmds[i]) { user_panic("failed to read line%d of .mosh_history\n" , i + 1 ); } } if ((r = read(fd, cmd, lenCmds[serial])) != lenCmds[serial]) { user_panic("failed to read serial line of .mosh_history\n" ); } cmd[lenCmds[serial] - 1 ] = 0 ; close(fd); }
在解析指令环节增加savecmd()
的函数,应注意全局变量的使用要求处于同一进程内,因此需要将其放置在主解析进程中,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 int main (int argc, char **argv) { ... for (;;) { ... readline(buf, sizeof buf); if (buf[0 ] != '\0' ) saveCmd(buf); ... } ... }
在解析一行指令环节添加对于上下键带来的光标以及控制台显示操作,具体代码如下:
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 void readline (char *buf, u_int n) { int r, lastLen; index = nCmds; for (int i = 0 ; i < n; i++) { ... if (buf[i] == '\b' || buf[i] == 0x7f ) { ... } else if (buf[i] == '\033' ) { switch (getDirect()) { case 'A' : printf ("\033[1B" ); if (index == nCmds) { strcpy (curCmd, buf); } if (index > 0 ) { index--; printf ("\033[2K" ); printf ("\033[%dD" , lastLen); getCmd(index, buf); printf ("%s" , buf); lastLen = strlen (buf); } break ; case 'B' : printf ("\033[1A" ); if (index < nCmds) { index++; if (index == nCmds) { strcpy (buf, curCmd); } else { getCmd(index, buf); } printf ("\033[2K" ); printf ("\033[%dD" , lastLen); printf ("%s" ,buf); lastLen = strlen (buf); } else { strcpy (buf, curCmd); printf ("\033[2K" ); printf ("\033[%dD" , lastLen); printf ("%s" ,buf); lastLen = strlen (buf); } break ; } } ... } ... } int getDirect () { char temp1, temp2; read(0 , &temp1, 1 ); read(0 , &temp2, 1 ); if (temp1 == '[' ) return temp2; return -1 ; }
在runcmd()
函数中添加内置指令history
,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (strcmp (argv[0 ], "history" ) == 0 ){ int r, fd; char buf; if ((r = open("/.mosh_history" , O_RDONLY)) < 0 ) { user_panic("fail to open file : .mosh_history\n" ); } fd = r; while ((r = read(fd, &buf, 1 )) == 1 ) { printf ("%c" , buf); } return ; }
实现一行多指令
你需要实现使用 ;
将多条指令隔开从而从左至右依顺序执行每条指令的功能。
对于一行多指令,在解析过程中,应该fork
出多个子进程,并行解析处理指令,并返回给runcmd
进程,进行执行操作。实现思路是在指令解析环节,如果遇到;
,使用fork
函数创建子进程,并使用子进程返回之前解析出的命令,主进程等待子进程解析完毕后继续执行解析操作,直至结束。
在parsercmd()
函数中增加多指令标识符;
的解析处理,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 case ';' : r = fork(); if (r == 0 ) { return argc; } else { wait(r); if (!iscons(1 )) { dup(0 , 1 ); } if (!iscons(0 )) { dup(1 , 0 ); } return parsecmd(argv, rightpipe, 2 , 0 ); } break ;
实现追加重定向
你需要实现 shell 中 >>
追加重定向的功能,即在文件末尾进行追加输入。
追加重定向的实现与重定向基本一致,只需要更改文件的打开权限,定义追加权限即可。实现思路是新定义追加权限,在重定向打开文件时给予追加权限,以使其可以在原文件内容上进行追加输入。
在serve_open()
函数中,增加追加权限打开操作,具体代码如下:
1 2 3 4 5 if ((rq->req_omode & O_APPEND) == O_APPEND) { ff->f_fd.fd_offset = ff->f_file.f_size; }
在_gettoken()
函数中增加追加重定向的解析,具体代码如下:
1 2 3 4 5 6 7 8 9 if (*s == '>' && *(s + 1 ) == '>' ){ *p1 = s; *s++ = 0 ; *s++ = 0 ; *p2 = s; return 'd' ; }
在parsercmd()
函数中增加追加重定向的解析处理,具体代码如下:
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 'd' : if ((gettoken(0 , &t)) != 'w' ) { debugf("syntax error: >> not followed by word\n" ); exit (1 ); } if ((r = open(t, O_RDWR | O_APPEND)) < 0 ) { if ((r = create(t, FTYPE_REG)) < 0 ) { debugf("failed to create %s in >>\n" , t); exit (1 ); } if ((r = open(t, O_RDWR | O_APPEND)) < 0 ) { debugf("failed to open %s in >>\n" , t); exit (1 ); } } fd = r; dup(fd, 1 ); if ((r = close(fd)) < 0 ) { return r; } break ;
实现引号支持
你需要实现引号支持,比如 echo "ls >"
,shell 在解析时需要将双引号内的内容看作是单个字符串。
在解析环节中,数组argv
中存储的是各个w
,如在echo aaa
中就会有两个w: "echo" "aaa"
。将双引号及其内部内容解析为一个w
,即可将其看作单个字符串。实现思路是在解析过程中,遇到双引号时,开始存储,直到遇到下一个双引号,并将之中的内容当作一个整体存储到argv
数组中,供运行阶段使用。
在_gettoken()
函数中增加双引号的解析,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (*s == '\"' ){ s++; *p1 = s; while (*s && *s != '\"' ) { s++; } *s = 0 ; s++; *p2 = s; return 'w' ; }
实现前后台任务管理
你需要支持 mosh 运行后台进程,当命令的末尾添加上 &
符号时,该命令应该在后台执行。
实现 jobs
指令列出当前 shell 中所有后台任务的状态。你需要为任务创建 ID(每次启动 mosh 时,任务从 1 开始编号,每个新增任务编号应加 1),并且通过 jobs
指令输出包括:任务 ID(job_id
)、任务的运行状态(status
:可能的取值为 Running
,Done
)、任务的进程 ID(env_id
)与运行任务时输入的指令(cmd
)。请以 printf("[%d] %-10s 0x%08x %s", job_id, status, env_id, cmd)
的格式进行输出。
实现 fg
将后台任务带回前台继续运行,用户通过 fg <job_id>
的方式将对应任务带回前台。
实现 kill
指令,用户通过 kill <job_id>
来实现结束后台任务。
在 fg
或 kill
指令中,若 job_id
对应的后台任务不存在则输出 printf("fg/kill: job (%d) do not exist\n", job_id)
,若 job_id
对应的 ID 为 envid
的进程状态不为 Running
则输出 printf("fg/kill: (0x%08x) not running\n", envid)
。
前后台任务管理主要是在内核态中进行,实现jobs
指令时获取全部任务信息,实现fg
指令时将任务调至前台等待其完成,实现kill
指令时将任务进程结束。实现思路是在内核态中建立结构体数组,并创建系统调用函数,实现任务的管理与展示。
在include/env.h
中定义任务结构体,具体代码如下:
1 2 3 4 5 6 7 struct Job { int job_id; int env_id; char status[10 ]; char cmd[1024 ]; };
在kern/syscall_all.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 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 69 70 71 int sys_job_list (struct Job list [], int *num) { *num = nJobs; for (int i = 0 ; i < nJobs; i++) { list [i] = jobs[i]; } return 0 ; } int sys_job_begin (u_int env_id, char cmd[]) { jobs[nJobs].job_id = nJobs + 1 ; jobs[nJobs].env_id = env_id; strcpy (jobs[nJobs].status, "Running" ); strcpy (jobs[nJobs].cmd, cmd); nJobs++; return 0 ; } int sys_job_end (u_int env_id) { for (int i = 0 ; i < nJobs; i++) { if (jobs[i].env_id == env_id) { strcpy (jobs[i].status, "Done" ); break ; } } return 0 ; } int sys_job_turn (int job_id, int *env_id) { if (job_id < 0 || job_id > nJobs) { return -E_NO_JOB; } int curenv_id = jobs[job_id - 1 ].env_id; *env_id = curenv_id; if (strcmp (jobs[job_id - 1 ].status, "Running" ) != 0 ) { return -E_NO_RUNJOB; } struct Env *e ; try(envid2env(curenv_id, &e, 0 )); return 0 ; } int sys_job_kill (int job_id, int *env_id) { if (job_id < 0 || job_id > nJobs) { return -E_NO_JOB; } int curenv_id = jobs[job_id - 1 ].env_id; *env_id = curenv_id; if (strcmp (jobs[job_id - 1 ].status, "Running" ) != 0 ) { return -E_NO_RUNJOB; } strcpy (jobs[job_id - 1 ].status, "Done" ); struct Env *e ; try(envid2env(curenv_id, &e, 0 )); printk("[%08x] destroying %08x\n" , curenv->env_id, e->env_id); env_destroy(e); return 0 ; } int sys_cgetc (void ) { int ch; ch = scancharc(); return ch; }
在用户态中添加相应的调用系统调用函数的信息发送函数,具体代码如下:
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 int syscall_job_list (struct Job list [], int *num) { return msyscall(SYS_job_list, list , num); } int syscall_job_begin (u_int env_id, char cmd[]) { return msyscall(SYS_job_begin, env_id, cmd); } int syscall_job_end (u_int env_id) { return msyscall(SYS_job_end, env_id); } int syscall_job_turn (int job_id, int *env_id) { return msyscall(SYS_job_turn, job_id, env_id); } int syscall_job_kill (int job_id, int *env_id) { return msyscall(SYS_job_kill, job_id, env_id); } int syscall_cgetc () { int ch; while ((ch = msyscall(SYS_cgetc)) == 0 ) { syscall_yield(); } return ch; }
在parsercmd()
函数中增加后台任务的解析处理,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 case '&' : r = fork(); if (r == 0 ) { if ((last == 0 && flag != 0 ) || (last == 1 && flag == 0 )) { return 0 ; } return argc; } else { if (!iscons(1 )) { dup(0 , 1 ); } if (!iscons(0 )) { dup(1 , 0 ); } return parsecmd(argv, rightpipe, 2 , 0 ); } break ;
在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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 if ((strcmp (argv[0 ], "jobs" ) == 0 ) || (strcmp (argv[0 ], "fg" ) == 0 ) || (strcmp (argv[0 ], "kill" ) == 0 )) { int r, env_id, num; if (strcmp (argv[0 ], "jobs" ) == 0 ) { struct Job list [1024]; syscall_job_list(list , &num); for (int i = 0 ; i < num; i++) { printf ("[%d] %-10s 0x%08x %s\n" , list [i].job_id, list [i].status, list [i].env_id, list [i].cmd); } } else if (strcmp (argv[0 ], "fg" ) == 0 ) { int job_id = turn_to_num(argv[1 ]); r = syscall_job_turn(job_id, &env_id); wait(env_id); if (r == -E_NO_JOB) { printf ("fg: job (%d) do not exist\n" , job_id); } else if (r == -E_NO_RUNJOB) { printf ("fg: (0x%08x) not running\n" , env_id); } } else if (strcmp (argv[0 ], "kill" ) == 0 ) { int job_id = turn_to_num(argv[1 ]); r = syscall_job_kill(job_id, &env_id); if (r == -E_NO_JOB) { printf ("kill: job (%d) do not exist\n" , job_id); } else if (r == -E_NO_RUNJOB) { printf ("kill: (0x%08x) not running\n" , env_id); } } return ; }
为每条指令添加起始的任务初始化以及任务结束,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void runcmd (char *s) { ... if (child >= 0 ) { syscall_job_begin(child, cmd); wait(child); } else { debugf("spawn %s: %d\n" , argv[0 ], child); } ... } void libmain (int argc, char **argv) { ... syscall_job_end(env->env_id); ... }