0%

CS15-213labs-shell-lab(下)

功能

  • 执行二进制文件
  • 作业控制(前后台切换, 前后台运行, 显示当前所有作业)
  • 支持简单的信号(SIGINT, SIGTSTP, SIGCHLD, SIGQUIT)
  • IO重定向(stdin, stdout)

完整代码: code

流程

流程图

实际的代码需要处理许多问题:

  • 如何维护当前所有作业状态

  • 父进程如何等待前台进程执行完

  • IO 如何在执行一条命令重定向

  • 如何接收键盘,子进程信号

如何维护当前所有作业状态

1
2
3
4
5
6
7
8
struct job_t {              /* The job struct */
pid_t pid; /* job PID */
int jid; /* job ID [1, 2, ...] */
int state; /* UNDEF, BG, FG, or ST */
char cmdline[MAXLINE]; /* command line */
};

struct job_t jobs[MAXJOBS]; /* The job list */

我定义了一个结构体 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){
/*child process */
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);
/* error */
printf("%s: Command not found\n", argv[0]);
fflush(stdout);
exit(0);
}
else {
/* father process */
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