Shell挑战性任务
任务要求
本次挑战性任务是在 lab6 实现的 MOS Shell 基础上继续实现新的功能。
实现功能
具体内容参考任务说明。(各测试点间无依赖关系)
关于shell
在 MOS 中,进程是一个很重要的概念,我们需要清楚 MOS 在运行的过程中存在哪些进程、这些进程之间的关系是什么,这有助于我们完成 shell 的相关任务。
比如,当我们启动 shell 时,它的 main 函数构成了一个新的进程。这个主进程主要负责完成命令的读取(readline
)等工作(这个进程从 mosh 被打开到结束也都是不会被 free 的),不断有新的进程被 fork 出来通过调用runcmd
去执行命令。在runcmd
中,则首先调用parsecmd
解析命令的参数。
parsecmd
通过返回argc
表明完成对命令和参数的解析,或者递归调用parsecmd
进行更复杂的解析工作。在这个过程中,主要通过gettoken
和_gettoken
对字符串命令进行拆解。
解析完成后,又会调用spawn
函数加载对应的可执行文件、fork 新的进程并设置跳转入口,以执行对应的命令。spawn
会返回这个进程的 id ,使父进程得以等待(或根据要实现的功能进程不同的处理)子进程完成可执行文件的运行(所有会结束的进程最后会调用exit()
),然后父进程也就将结束它的使命。
以上是 mosh 运行的大致逻辑,对这些有了基本的了解后,让我们着手开始增强 mosh 的功能吧。
具体实现
不带.b
后缀指令
只需在spawn
加载可执行文件时进行额外判断即可,若可执行文件不存在,则添加.b
后缀再次尝试
1 2 3 4 5 6 7 8 9 10
| int fd; char cmd[1024] = {0}; if ((fd = open(prog, O_RDONLY)) < 0) { strcpy(cmd, prog); strcat(cmd, ".b\0"); if ((fd = open(cmd, O_RDONLY)) < 0) { return fd; } }
|
指令条件执行
显然,这首先需要我们修改 MOS 中对用户进程exit
的实现,使其能够返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13
| void exit(int r) { #if !defined(LAB) || LAB >= 5 close_all(); #endif env = &envs[ENVX(syscall_getenvid())]; if (envs[ENVX(env->env_parent_id)].env_ipc_recving != 0) { ipc_send(env->env_parent_id, r, 0, 0); } syscall_env_destroy(0); user_panic("unreachable code"); }
|
这里我采用了 ipc
来实现返回值的传递,当父进程需要返回值时,让子进程调用ipc_send
以条件||
为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| case -2: if ((*rightpipe = fork()) == 0) { return argc; } else { u_int who; r = ipc_recv(&who, 0, 0); if (r == 0) { do { c = gettoken(0, &t); } while (c != -1 && c != '#' && c); if (c != -1) { return 0; } } return parsecmd(argv, rightpipe, background); } break;
|
另外由于可执行文件执行之后的返回值不能直接传给上述父进程,所以可以修改wait
函数,让其将值先返回给子进程,子进程再将此值返回
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
| int wait(u_int envid) { const volatile struct Env *e; int r; u_int who; e = &envs[ENVX(envid)]; while (e->env_id == envid && e->env_status != ENV_FREE) { r = ipc_recv(&who, 0, 0); syscall_yield(); } return r; }
void runcmd(char *s) { int child = spawn(argv[0], argv); if (child >= 0) { r = wait(child); } exit(r); }
|
更多指令
需要实现touch
、mkdir
、rm
,并需要考虑给出的情形
首先我们需要实现 MOS 的创建文件的用户接口(文件系统的接口已给出),新增以下内容
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
| struct Fsreq_create { char req_path[MAXPATHLEN]; u_int f_type; };
int fsipc_create(const char *, int); int create(const char *path, u_int f_type);
int file_create(char *path, struct File **file);
int create(const char *path, u_int f_type) { return fsipc_create(path, f_type); }
int fsipc_create(const char *path, int f_type) { int len = strlen(path); if (len == 0 || len > MAXPATHLEN) { return -E_BAD_PATH; }
struct Fsreq_create *req = (struct Fsreq_create *) fsipcbuf; req->f_type = f_type; strcpy((char *) req->req_path, path);
return fsipc(FSREQ_CREATE, req, 0, 0); }
void *serve_table[MAX_FSREQNO] = { ..., [FSREQ_CREATE] = serve_create, };
void serve_create(u_int envid, struct Fsreq_create *rq) { struct File *f; int r;
if ((r = file_create(rq->req_path, &f)) < 0) { ipc_send(envid, r, 0, 0); return; }
f->f_type = rq->f_type; ipc_send(envid, 0, 0, 0); }
|
然后,对于普通的touch
和mkdir
命令,我们只需要在对应*.c
文件中进行以下操作即可
1
| int r = create(argv[1], FTYPE_REG);
|
对于mkdir
,我们需要额外实现-p
参数:当使用 -p
选项时忽略错误,若目录已存在则直接退出,若创建目录的父目录不存在则递归创建目录。
首先我们可以借助 ARG 宏来解析参数
1 2 3 4 5 6 7 8 9 10
| ARGBEGIN { case 'p': pmod = 1; break; default: usage(); break; } ARGEND
|
对于-p
参数,我的处理办法为,对路径进行解析,忽视错误对路径中每一个子目录进行创建
1 2 3 4 5 6 7 8 9 10 11 12 13
| char *path = argv[0]; if (*path == '/') { path++; } int len = strlen(path); *(path + len) = '/'; for (int i = 0; i <= len; ++i) { if (*(path + i) == '/') { *(path + i) = 0; create(path, FTYPE_DIR); *(path + i) = '/'; } }
|
对于rm
指令,用户接口remove
已给出,直接使用即可
不过我们需要处理不同的情况
rm <file>
:若文件存在则删除 <file>
,否则输出错误信息。
rm <dir>
:输出错误信息。
rm -r <dir>|<file>
:若文件或文件夹存在则删除,否则输出错误信息。
rm -rf <dir>|<file>
:如果对应文件或文件夹存在则删除,否则直接退出。
因此我们需要先获取路径对应的文件描述符,然后针对不同情况进行不同的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int fd = open(argv[0], O_RDONLY); if (rmod && fmod) { remove(argv[0]); } else { if (fd < 0) { printf("rm: cannot remove \'%s\': No such file or directory\n", argv[0]); } else { struct Filefd *filefd = (struct Filefd *) INDEX2FD(fd); if (!rmod && filefd->f_file.f_type == FTYPE_DIR) { printf("rm: cannot remove \'%s\': Is a directory\n", argv[0]); } else { remove(argv[0]); } } }
|
反引号
本次任务只需考虑echo
进行的输出且数据较弱,故忽略echo
指令和反引号,直接执行反引号中的指令貌似也能过。不过这里还是对反引号原有的要求进行了实现。
首先我们在SYMBOLS
里面添加反引号以完成识别
1
| #define SYMBOLS "<|>&;()`#"
|
这里我采用了如下的方式区分前后的反引号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int backquote; int _gettoken(char *s, char **p1, char **p2) { if (strchr(SYMBOLS, *s)) { int t = *s; *p1 = s; *s++ = 0; *p2 = s; if (t == '`') { backquote ^= 1; } return t; } }
|
然后使用管道将反引号中指令的所有输出传给父进程,对其解析以作为使用反引号的指令的参数
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
| case '`': if (backquote) { if ((r = pipe(p)) < 0) { debugf("failed to allocate a pipe: %d\n", r); exit(0); } if ((*rightpipe = fork()) == 0) { dup(p[1], 1); close(p[1]); close(p[0]); return parsecmd(argv, rightpipe, background); } else { dup(p[0], 0); close(p[0]); close(p[1]); char *tmp = (char *) bf; for (int i = 0, count = 0; i < MAXLEN; i++) { count += read(0, bf + i, 1); if (count == i) break; syscall_yield(); } while (strchr("\t\r\n", *tmp)) { *tmp++ = 0; } while (*tmp) { char *s1 = tmp; while (!strchr("\t\r\n", *tmp) && *tmp) { tmp++; } *tmp++ = 0; argv[argc++] = s1; while (strchr("\t\r\n", *tmp) && *tmp) { tmp++; } } do { c = gettoken(0, &t); } while (c != '`'); } } else { return argc; } break;
|
这里最开始我使用原始的wait
但是没有成功,因此使用了类似于轮询的方式完成了对数据的读取
不过后来我需要在wait.c
中添加新的功能,所以上面比较丑陋的轮询方式可以简化为下面的函数调用
1 2 3 4 5 6 7 8
| void check2(u_int envid) { const volatile struct Env *e; e = &envs[ENVX(envid)]; while (e->env_id == envid && e->env_status != ENV_FREE) { syscall_yield(); } }
|
注释功能
这个比较简单,完成对#
的识别后进行如下处理即可
1 2 3
| case 0: case '#': return argc;
|
一行多指令
常规的进程 fork
需要注意的点是可能之前会有重定向,将输出指向了其他地方,这里需要将其重新指回控制台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| case ';': if ((*rightpipe = fork()) == 0) { return argc; } else { wait(*rightpipe); if (redirect) { close(0); close(1); dup(opencons(), 1); dup(1, 0); redirect = 0; } return parsecmd(argv, rightpipe, background); } break;
|
追加重定向
首先我们需要实现文件操作的O_APPEND
模式,即追加写功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #define O_APPEND 0x1000
void serve_open(u_int envid, struct Fsreq_open *rq) { o->o_mode = rq->req_omode; ff->f_fd.fd_omode = o->o_mode; if (ff->f_fd.fd_omode & O_APPEND) { ff->f_fd.fd_offset = f->f_size; } }
|
然后在parsecmd
中进行相应的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| case '>':; int cc = gettoken(0, &t); int mode = O_WRONLY | O_CREAT; if (cc == '>') { mode |= O_APPEND; if ((cc = gettoken(0, &t)) != 'w') { debugf("syntax error: > not followed by word\n"); exit(0); } } else if (cc == 'w') { mode |= O_TRUNC; } else { debugf("syntax error: > not followed by word\n"); exit(0); } if ((r = open(t, mode)) < 0) { debugf("failed to open \'%s\': %d\n", t, r); exit(0); } fd = r; dup(fd, 1); close(fd); break;
|
引号支持
由于不用考虑引号和反引号的嵌套处理,所以也就相对简单,只需在_gettoken
时将引号内所用内容标记为一个word
(w
)
1 2 3 4 5 6 7 8 9 10 11
| int _gettoken(char *s, char **p1, char **p2) { if (*s == '\"') { *p1 = ++s; while (*s != '\"' && *s++); *s++ = 0; *p2 = s; return 'w'; } }
|
历史指令
重量级
首先本着要做就要做好的原则,我先实现了对键入命令任意位置的修改的支持(使用←,→,Backspace,Del),这样不仅看起来更加顺眼,还可以实现对历史指令的修改
查阅资料可知,我们需要判断的 ascii 为
键 |
编码 |
← |
\033[D |
→ |
\033[C |
↑ |
\033[A |
↓ |
\033[B |
Backspace |
\b or 0x7f |
Del |
~ |
通过以下宏我们可以实现光标的移动,从而能够通过输出新的内容覆盖指定位置的原有内容
1 2 3 4
| #define MoveLeft(x) debugf("\033[%dD", x) #define MoveRight(x) debugf("\033[%dC", x) #define MoveUp(x) debugf("\033[%dA", x) #define Movedown(x) debugf("\033[%dB", x)
|
然后修改readline
的逻辑,维护当前指令的长度len
和光标位置i
(表示在第i个字符前),以及历史指令的读取
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
| void readline(char *buf, u_int n) { int r, len = 0, i = 0; char tmp; while (i < n) { if ((r = read(0, &tmp, 1)) != 1) { if (r < 0) { debugf("read error: %d\n", r); } exit(0); } if (删除字符) { } else if (方向键) { } else if (tmp == '\r' || tmp == '\n') { buf[len] = 0; return; } else { if (i == len) { buf[i++] = tmp; } else { for (int j = len; j > i; j--) { buf[j] = buf[j - 1]; } buf[i] = tmp; buf[len + 1] = 0; MoveLeft(++i); debugf("%s", buf); MoveLeft(len - i + 1); } len += 1; } } }
|
进一步,对于退格键Backspace和删除键Del的处理,主要思路是用空格覆盖想要删除的字符(以实现删除效果),对于光标位置的计算没什么好说的,细心即可
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
| if (tmp == '\b' || tmp == 0x7f) { if (i > 0) { if (i == len) { buf[--i] = 0; MoveLeft(1); debugf(" "); MoveLeft(1); } else { for (int j = i - 1; j < len - 1; ++j) { buf[j] = buf[j + 1]; } buf[len - 1] = 0; MoveLeft(i--); debugf("%s ", buf); MoveLeft(len - i); } len -= 1; } } else if (tmp == '~') { if (i < len) { for (int j = i; j < len; ++j) { buf[j] = buf[j + 1]; } buf[--len] = 0; if (i != 0) { MoveLeft(i); } debugf("%s ", buf); MoveLeft(len - i + 1); } }
|
对于方向键的处理,左移右移比较直接,只需要注意不要移出范围
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
| } else if (tmp == '\033') { read(0, &tmp, 1); if (tmp == '[') { read(0, &tmp, 1); switch (tmp) { case 'A': break; case 'B': break; case 'C': if (i < len) { i += 1; } else { MoveLeft(1); } break; case 'D': if (i > 0) { i -= 1; } else { MoveRight(1); } break; default: break; } } }
|
对于上下键的处理,我们先不考虑历史指令如何保存和读取,而是关注如何实现上下键的逻辑功能
这里我采用了一个全局变量hislen
来记录历史指令的数量、一个局部变量hisline
来记录当前是哪条指令([0,hislen - 1]
表示历史指令),histmp
用来保存当前输入的指令(因为可能会上键选取历史指令、最后又下键回到当前指令)
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
| void deleteLine(int pos, int len) { if (pos != 0) { MoveLeft(pos); } for (int k = 0; k < len; ++k) { debugf(" "); } if (len != 0) { MoveLeft(len); } } void readline(char *buf, u_int n) { int hisline = hislen; while (i < n) { } else if (tmp == '\033') { read(0, &tmp, 1); if (tmp == '[') { read(0, &tmp, 1); switch (tmp) { case 'A': Movedown(1); if (hisline == hislen) { strcpy(histmp, buf); *(histmp + len) = 0; } if (hisline > 0) { hisline--; readHistory(hisline, buf); deleteLine(i, len); debugf("%s", buf); len = strlen(buf); i = len; } break; case 'B': if (hisline < hislen - 1) { hisline++; readHistory(hisline, buf); } else if (hisline + 1 == hislen) { hisline++; strcpy(buf, histmp); } deleteLine(i, len); debugf("%s", buf); len = strlen(buf); i = len; break; } } } }
|
现在我们可以开始尝试实现历史指令的功能了
首先是初始化创建/.mosh_history
文件
1 2 3 4 5 6 7
| if ((r = open("/.mosh_history", O_RDONLY)) < 0) { if (create("/.mosh_history", FTYPE_REG) < 0) { user_panic("create failed"); } } else { close(r); }
|
对于如何从文件中读取历史指令,我采用了一个数组hisoffset
来记录每条指令的偏移,这样我们就可以通过seek
和read
来读取指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int hislen, hisoffset[HISTFILESIZE + 5];
void readHistory(int line, char *buf) { int r, fd; if ((fd = open("/.mosh_history", O_RDONLY)) < 0) { user_panic("open failed"); } if (line >= hislen) { *buf = 0; return; } int offset = (line > 0 ? hisoffset[line - 1] : 0); int len = (line > 0 ? hisoffset[line] - hisoffset[line - 1] : hisoffset[line]); if ((r = seek(fd, offset)) < 0) { user_panic("seek failed"); } if ((r = read(fd, buf, len)) != len) { user_panic("read failed"); } close(fd); buf[len - 1] = 0; }
|
读取指令需要注意的是最大指令条数为HISTFILESIZE=20
,当超过时需要将最早的指令删除。这里我采用的是将后19条指令读出,清空文件,再和新指令一起写入的简单粗暴的方式。
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
| void saveHistory(char *buf) { int r, fd; if (hislen < HISTFILESIZE) { if ((fd = open("/.mosh_history", O_WRONLY | O_APPEND)) < 0) { user_panic("open failed"); } int len = strlen(buf); *(buf + len) = '\n'; len += 1; *(buf + len) = 0; if ((r = write(fd, buf, len)) != len) { user_panic("write error"); } hisoffset[hislen++] = len + (hislen > 0 ? hisoffset[hislen - 1] : 0); close(fd); } else { if ((fd = open("/.mosh_history", O_RDONLY)) < 0) { user_panic("open failed"); } if ((r = seek(fd, hisoffset[0])) < 0) { user_panic("seek failed"); } int len = hisoffset[HISTFILESIZE - 1] - hisoffset[0]; if ((r = read(fd, buftmp, len)) != len) { user_panic("read error"); } if ((fd = open("/.mosh_history", O_TRUNC | O_WRONLY)) < 0) { user_panic("trunc failed"); } if ((r = write(fd, buftmp, len)) != len) { debugf("%s\n", buftmp); debugf("%d %d\n", len, r); user_panic("rewrite error"); } if ((fd = open("/.mosh_history", O_WRONLY | O_APPEND)) < 0) { user_panic("rewrite not correctly"); } len = strlen(buf); *(buf + len) = '\n'; len += 1; *(buf + len) = 0; if ((r = write(fd, buf, len)) != len) { user_panic("write error"); } int ttmp = hisoffset[0]; for (int i = 0; i < HISTFILESIZE; i++) { hisoffset[i] = hisoffset[i + 1] - ttmp; } hisoffset[hislen - 1] = hisoffset[hislen - 2] + len; close(fd); } }
|
对于history
指令,直接将其变为cat /.mosh_history
即可
1 2 3 4 5 6 7 8 9
| int runcmd(char *s) { if (!strcmp(argv[0], "history") || !strcmp(argv[0], "history.b")) { argv[0] = "cat"; argv[1] = ".mosh_history"; argc = 2; } }
|
前后台任务管理
- 当命令的末尾添加上 & 符号时,该命令应该在后台执行
- 实现 jobs 指令列出当前 shell 中所有后台任务的状态。需要为任务创建 ID(每次启动 mosh 时,任务从 1 开始编号,每个新增任务编号应加 1),并且通过 jobs 指令输出包括:任务 ID(job_id)、任务的运行状态(status:可能的取值为 Running,Done)、任务的进程 ID(env_id)与运行任务时输入的指令(cmd)。
- 实现 fg 将后台任务带回前台继续运行,用户通过 fg <job_id> 的方式将对应任务带回前台。
- 实现 kill 指令,用户通过 kill <job_id> 来实现结束后台任务。
- 以上指令均需要设计为内置指令(与
history
一样)
坑点很多,相比于历史指令,这部分更是在 bug 里找 bug
总之这里收回前文对进程之间关系的强调伏笔,需要注意的是一定要在mosh
的主进程中读写 jobs 的相关信息(子进程被 free 信息就没了),然后对于 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 33
| char cmd[1024]; int jobscount; struct Job { int status; int env_id; char cmd[1024]; } jobs[20];
int savetask(char *s) { strcpy(cmd, s); int len = strlen(s); while (s[len - 1] != '&' && len > 1) { len--; } if (s[len - 1] == '&') { strcpy(jobs[++jobscount].cmd, cmd); jobs[jobscount].status = 1; return 1; } return 0; }
int main(int argc, char **argv) { for (;;) { readline(buf, sizeof buf); int rr = savetask(buf); } return 0; }
|
接着需要使用 ipc 去拿到对应的进程 id
先将runcmd
改为有返回值,其中与 jobs 相关的操作返回非 0 值,否则返回 0,然后通过类似之前处理条件操作的方法将值传回 mosh 主进程
1 2 3 4 5 6
| int ret = 0; if (r == 0) { exit(runcmd(buf)); } else { ret = wait(r); }
|
在parsecmd
中解析命令时需要检测是否有&
符号,如果有则将参数*background
置为 1,并在runcmd
中返回执行该命令的进程 id ,然后记录
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
| int parsecmd(char **argv, int *rightpipe, int *background) { case '&': *background = 1; break; }
int runcmd(char *s) { int child = spawn(argv[0], argv); close_all(); if (child >= 0) { if (background) { r = 0; } else { r = wait(child); } } else { debugf("spawn %s: %d\n", argv[0], child); } if (background) { return child; } }
int main(int argc, char **argv) { if (ret) { if (rr) { jobs[jobscount].env_id = ret; } } }
|
对于fg
指令,为了避免使用 ipc 时出现的各种玄学错误,最后选择了轮询等待对应的子进程执行完毕,然后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 27 28 29 30 31 32 33
| int atoi(char *s) { int ret = 0; while (*s) { ret = ret * 10 + (*s++ - '0'); } return ret; } int runcmd(char *s) { if (!strcmp(argv[0], "fg")) { int jobid = atoi(argv[1]); if (jobid <= jobscount) { debugf("fg: job (%d) do not exist\n", jobid); return 0; } if (jobs[jobid].status == 0) { debugf("fg: (0x%08x) not running\n", jobs[jobid].env_id); return 0; } check2(jobs[jobid].env_id); return 0; } }
void check2(u_int envid) { const volatile struct Env *e; e = &envs[ENVX(envid)]; while (e->env_id == envid && e->env_status != ENV_FREE) { syscall_yield(); } }
|
对于kill
命令,直接返回任务 id ,然后再主进程中将这个进程设置为ENV_NOT_RUNNABLE
即可
1 2 3 4 5 6 7 8
| else if (ret <= jobscount) { if (jobs[ret].status) { syscall_set_env_status(jobs[ret].env_id, ENV_NOT_RUNNABLE); jobs[ret].status = 0; } else { debugf("fg: (0x%08x) not running\n", jobs[ret].env_id); } }
|
对于jobs
指令,使用返回值-1
来表示,然后在主进程输出对应的信息
1 2 3 4 5
| else if (ret == -1) { for (int i = 1; i <= jobscount; i++) { debugf("[%d] %-10s 0x%08x %s\n", i, jobs[i].status ? "Running" : "Done", jobs[i].env_id, jobs[i].cmd); } }
|
另外值得注意的是,任务挂起以后会自己执行完毕,所以方便起见我们需要每次查询对应进程是否结束(状态是否为ENV_FREE
),并更新对应状态信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| for (int i = 1; i <= jobscount; i++) { if (jobs[i].status) { if (check(jobs[i].env_id)) { jobs[i].status = 0; } } }
int check(u_int envid) { const volatile struct Env *e; e = &envs[ENVX(envid)]; if (e->env_status == ENV_FREE) { return 1; } return 0; }
|