功能
- 执行二进制文件
- 作业控制(前后台切换, 前后台运行, 显示当前所有作业)
- 支持简单的信号(SIGINT, SIGTSTP, SIGCHLD, SIGQUIT)
- IO重定向(stdin, stdout)
完整代码: code
流程
实际的代码需要处理许多问题:
如何维护当前所有作业状态
父进程如何等待前台进程执行完
IO 如何在执行一条命令重定向
如何接收键盘,子进程信号
如何维护当前所有作业状态
1 2 3 4 5 6 7 8
| struct job_t { pid_t pid; int jid; int state; char cmdline[MAXLINE]; };
struct job_t jobs[MAXJOBS];
|
我定义了一个结构体 job_t ,保存
- 当前作业 pid
- 作业的 id
- 当前作业状态
- 执行此进程的命令
读入命令时,程序会先分析前台还是后台(简单的判断最后一个字符是否为 &)
然后执行
1
| addjob(jobs, pid, runbg ? BG : FG, cmdline);
|
当程序退出,被阻塞或者切换前后时更改对应状态,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void sigtstp_handler(int sig) {
#if DEBUG printf("sig: %d\n", sig); fflush(stdout); printf("sigstophandle\n"); fflush(stdout); #endif
sigprocmask(SIG_BLOCK, &mask, NULL); pid_t pid = fgpid(jobs); struct job_t* job = getjobpid(jobs, pid); if (job) { job->state = ST; fflush(stdout); kill(-pid, SIGTSTP); printf("Job [%d] (%d) stoped by signal %d\n", pid2jid(pid), pid, SIGTSTP); fflush(stdout); } sigprocmask(SIG_SETMASK, &prev, NULL); fflush(stdout); return; }
|
置对应的 job->state为 ST,当父进程收到 SIGTSTP 信号时
父进程如何等待前台进程执行完
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 ((pid = fork()) < 0){ unix_error("fork error"); } else if (pid == 0){ setpgid(0, 0); sigprocmask(SIG_SETMASK, &prev, NULL); Signal(SIGINT, SIG_DFL); Signal(SIGTSTP, SIG_DFL); Signal(SIGCHLD, SIG_DFL); Signal(SIGQUIT, SIG_DFL);
#if DEBUG printf("\n"); fflush(stdout); printf("child process\npid: (%d)", getpid()); fflush(stdout); printf("cmdline: %s", cmdline); fflush(stdout); #endif
execv(argv[0], argv); printf("%s: Command not found\n", argv[0]); fflush(stdout); exit(0); } else { prevpid = pid;
|
每次 fork 时,父进程记录当前子进程 pid
1 2 3
| while(prevpid != 0) { sigsuspend(&emptyset); }
|
当 prevpid 不为 0, 一直阻塞
当父进程 SIGCHLD 处理函数发现是子进程发来的信号,置为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 34 35 36 37 38 39 40 41 42 43
| while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){ if(pid == prevpid) { prevpid = 0; } if (WIFSTOPPED(status)) {
struct job_t* job = getjobpid(jobs, pid); if (job->state == ST); else { job->state = ST; printf("Job [%d] (%d) stoped by signal %d\n", pid2jid(pid), pid, SIGTSTP); fflush(stdout); } } else if(WIFSIGNALED(status)) { if (WTERMSIG(status) == SIGINT) { if (getjobpid(jobs, pid) != NULL) { printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, SIGINT); fflush(stdout); } } deletejob(jobs, pid); } else if(WIFEXITED(status)){
#if DEBUG printf("退出值为 %d\n", WEXITSTATUS(status)); fflush(stdout); #endif
deletejob(jobs, pid); } else if (WIFCONTINUED(status)) { printf("continued\n"); fflush(stdout); }
#if DEBUG printf("childhandle pid: %d\n", pid); fflush(stdout); #endif
}
|
IO 如何在执行一条命令重定向
得到命令字符串时,传给 ioredirection 函数处理
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
| void ioredirection(char** argv) { int endcmd = 0x7FFFFFFF; for (int i = 0; argv[i] != NULL; i++) { if (strcmp(argv[i], "<") == 0) { isredirect = true; if(freopen(argv[i+1], "r", stdin) == NULL) { return unix_error("io redirection error.\n"); } if(endcmd > i) { endcmd = i; } } else if (strcmp(argv[i], ">") == 0) { isredirect = true; if (freopen(argv[i+1],"a",stdout) == NULL) { return unix_error("io redirection error.\n"); } if (endcmd > i) { endcmd = i; } } } if(endcmd != 0x7FFFFFFF) { argv[endcmd] = NULL; } }
|
若存在 > ,打开下一个字符串指定的文件重定向到 stdout
若存在 < ,打开下一个字符串指定的文件重定向到 stdin
每次读取命令的时候判断,如果 标准输入和标准输出被重定向了,恢复
1 2 3 4 5 6 7 8 9
| if (isredirect) { if (freopen("/dev/tty", "r", stdin) == NULL) { unix_error("io redirection error."); } if (freopen("/dev/tty", "w", stdout) == NULL) { unix_error("io redirection error."); } isredirect = false; }
|
如何接收键盘,子进程信号
当键盘键入 ctrl+c, 父进程接到 SIGINT 信号,执行信号处理函数
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
| void sigint_handler(int sig) {
#if DEBUG printf("sigint handle call\n"); fflush(stdout); #endif
sigprocmask(SIG_BLOCK, &mask, NULL); pid_t pid = fgpid(jobs); struct job_t* job = getjobpid(jobs, pid); if (job != NULL) { printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, SIGINT); fflush(stdout); deletejob(jobs, pid); } sigprocmask(SIG_SETMASK, &prev, NULL);
if (pid) { kill(-pid, SIGINT); } else { exit(0); } return; }
|
删除对应的 job_t, 向前台进程发送 SIGINT,之后子进程终止, 父进程调用处理函数回收
效果
输入命令:
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
| ardxwe@ardxwe ~/github/CS15-213labs/shlab-handout$ cat trace15.txt # # trace15.txt - Putting it all together #
/bin/echo tsh> ./bogus ./bogus
/bin/echo tsh> ./myspin 10 ./myspin 10
SLEEP 2 INT
/bin/echo -e tsh> ./myspin 3 \046 ./myspin 3 &
/bin/echo -e tsh> ./myspin 4 \046 ./myspin 4 &
/bin/echo tsh> jobs jobs
/bin/echo tsh> fg %1 fg %1
SLEEP 2 TSTP
/bin/echo tsh> jobs jobs
/bin/echo tsh> bg %3 bg %3
/bin/echo tsh> bg %1 bg %1
/bin/echo tsh> jobs jobs
/bin/echo tsh> fg %1 fg %1
/bin/echo tsh> quit quit
|
执行结果:
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
| ardxwe@ardxwe ~/github/CS15-213labs/shlab-handout$ make test15 ./sdriver.pl -t trace15.txt -s ./tsh -a "-p" # # trace15.txt - Putting it all together # tsh> ./bogus ./bogus: Command not found tsh> ./myspin 10 Job [1] (18375) terminated by signal 2 tsh> ./myspin 3 & [1] (18388) ./myspin 3 & tsh> ./myspin 4 & [2] (18390) ./myspin 4 & tsh> jobs [1] (18388) Running ./myspin 3 & [2] (18390) Running ./myspin 4 & tsh> fg %1 Job [1] (18388) stoped by signal 20 tsh> jobs [1] (18388) Stopped ./myspin 3 & [2] (18390) Running ./myspin 4 & tsh> bg %3 No such job tsh> bg %1 [1] (18388) ./myspin 3 & tsh> jobs [1] (18388) Running ./myspin 3 & [2] (18390) Running ./myspin 4 & tsh> fg %1 tsh> quit
|