CSAPP lab7 tiny shell笔记
lab7 tiny shell笔记
主要任务是在tsh.h
中实现一个简单功能的shell(支持job)。有16个测试,是make test01
至make test16
,依次测试trace01.txt
至trace16.txt
中的内容。
测试的流程
输入make test01
,会调用sdriver.pl对tsh进行测试。

sdriver.pl的主要功能是模拟用户输入,比如可以等待1秒钟之后输入ctrl+z(发送SIGSTP)。
sdriver读取txt的每一行,识别自己的特殊命令(如TSTP,INT)等,可以给child shell发信号。自己识别不了的才通过Writer发送给child shell的stdin,child shell的打印(stdout)会发送给sdriver的Reader,然后Reader打印



trace01
#
# trace01.txt - Properly terminate on EOF.
#
CLOSE
WAIT
// sdriver.pl
# Close pipe (sends EOF notification to child)
elsif ($line =~ /CLOSE/) {
if ($verbose) {
print "$0: Closing output end of pipe to child $pid\n";
}
close Writer;
}
sdriver打印前三行注释,执行CLOSE发送eof信号给child shell,正确的功能是shell关闭,wait被SIGCHILD打断。在初始代码中已实现该功能:
// main()
if (feof(stdin)) { /* End of file (ctrl-d) */
fflush(stdout);
exit(0);
}
trace02
是要实现quit内置指令。参考CSAPP 3e的P525~526,先把eval()和buildin_command()框架写上去:
void eval(char *cmdline)
{
char *argv[MAXARGS]; // Argument list execve()
int bg; // Should the job run in bg or fg?
pid_t pid; // Process id
bg = parseline(cmdline, argv);
if (argv[0] == NULL)
return; // Ignore empty lines
if (!builtin_cmd(argv)) {
if ((pid = Fork()) == 0) { // Child runs user job
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}
// Parent waits for foreground job to terminate
if (!bg) {
int status;
if (waitpid(-1, &status, 0) < 0)
unix_error("waitfg: waitpid error");
} else {
printf("%d %s", pid, cmdline);
}
}
return;
}
int builtin_cmd(char **argv)
{
if (!strcmp(argv[0], "quit"))
exit(0);
if (!strcmp(argv[0], "&"))
return 1;
return 0; /* not a builtin command */
}
trace03
要实现非内置命令执行文件。trace02已经完成。
第4行的意思是执行
/bin/echo "tsh>" "quit"
简单打印tsh> quit
而不是执行
/bin/echo tsh> quit
后者会将tsh字符串输出到quit文件中
trace04
echo -e表示打印转义字符,\046表示&
目的是要执行后台job,tsh不用等待。trace02也已经完成,但是输出需要修改。
make test04输出:
#
# trace04.txt - Run a background job.
#
tsh> ./myspin 1 &
[1] (26252) ./myspin 1 &
则将eval()中的bg输出改为:
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
trace05
实现jobs
指令
在buildin_command()
添加:
if (!strcmp(argv[0], "jobs")) {
listjobs(jobs);
return 1;
}
在eval()
中添加add_job()
,需要对信号进行阻塞。这里的Sigfillset()
等函数是从csapp官网的csapp.c获取到的。
void eval(char *cmdline)
{
char *argv[MAXARGS]; // Argument list execve()
int bg; // Should the job run in bg or fg?
pid_t pid; // Process id
sigset_t mask_all, mask_one, prev; // signal sets
bg = parseline(cmdline, argv);
if (argv[0] == NULL)
return; // Ignore empty lines
Sigfillset(&mask_all);
Sigemptyset(&mask_one);
Sigaddset(&mask_one, SIGCHLD);
if (!builtin_cmd(argv)) {
Sigprocmask(SIG_BLOCK, &mask_one, &prev);
if ((pid = Fork()) == 0) { // Child runs user job
Sigprocmask(SIG_SETMASK, &prev, NULL);
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}
Sigprocmask(SIG_BLOCK, &mask_all, NULL);
addjob(jobs, pid, bg ? BG : FG, cmdline);
Sigprocmask(SIG_SETMASK, &prev, NULL);
// Parent waits for foreground job to terminate
if (!bg) {
int status;
if (waitpid(-1, &status, 0) < 0)
unix_error("waitfg: waitpid error");
} else {
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
}
return;
}
trace06,07
实现sigint_handler和sigchld_handler
shell发送sigint给前台job(这里先不考虑进程组),job结束后到sigchld_handler中回收,并打印出相关信息和删除job
void sigint_handler(int sig)
{
int olderrno = errno;
pid_t fg_pid = fgpid(jobs);
if (fg_pid)
Kill(fg_pid, sig);
errno = olderrno;
return;
}
void sigchld_handler(int sig)
{
int olderrno = errno;
pid_t pid;
sigset_t mask_all, prev;
int status;
Sigfillset(&mask_all);
// WNOHANG | WUNTRACED return immediately
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
Sigprocmask(SIG_BLOCK, &mask_all, &prev);
struct job_t* job = getjobpid(jobs, pid);
if (WIFEXITED(status)) {
// normally returned or exited, delete job
deletejob(jobs, pid);
} else if (WIFSIGNALED(status)) {
// terminated by signal, delete job and print message
printf("Job [%d] (%d) terminated by signal %d\n", job->jid, pid, WTERMSIG(status));
deletejob(jobs, pid);
}
Sigprocmask(SIG_SETMASK, &prev, NULL);
}
errno = olderrno;
return;
}
同时,eval()中的waitpid
改为waitfg()
if (!bg) {
waitfg(pid);
}
void waitfg(pid_t pid)
{
while (pid == fgpid(jobs))
Sleep(1); // sleep one second
return;
}
采用简单的轮询策略:当回收后,deletejob成功,则waitfg不继续等待。
trace08
实现sigtstp_handler
void sigtstp_handler(int sig)
{
int olderrno = errno;
pid_t fg_pid = fgpid(jobs);
if (fg_pid)
Kill(fg_pid, sig);
errno = olderrno;
return;
}
然后到sigchld_handler中添加:
else if (WIFSTOPPED(status)) {
// stopped by signal, change job state
printf("Job [%d] (%d) stopped by signal %d\n", job->jid, pid, WSTOPSIG(status));
job->state = ST;
}
trace09,10
实现bg
,fg
指令:将停止的job变为运行的后台/前台job
在buildin_command()
添加:
if (!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")) {
do_bgfg(argv);
return 1;
}
void do_bgfg(char **argv)
{
char* cmd = argv[0];
char* id = argv[1];
struct job_t* job;
if (id[0] == '%') {
int jid = atoi(id + 1);
job = getjobjid(jobs, jid);
} else {
int pid = atoi(id);
job = getjobpid(jobs, pid);
}
Kill(job->pid, SIGCONT);
if (!strcmp(cmd, "bg")) {
job->state = BG;
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
} else if (!strcmp(cmd, "fg")) {
job->state = FG;
waitfg(job->pid);
}
return;
}
trace 11,12,13
检查是否信号发送给前台的进程组而不是单个进程。
首先修改sigint_handler
,sigtstp_handler
:
if (fg_pid)
Kill(-fg_pid, sig); // send signal to process group
但是进程id并不等于进程组id,所以需要在execve之前显式设置:
Setpgid(0, 0);
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found\n", argv[0]);
exit(0);
}
还要修改do_bgfg
:
Kill(-(job->pid), SIGCONT);
trace 14
bg,fg错误信息打印。
void do_bgfg(char **argv)
{
char* cmd = argv[0];
char* id = argv[1];
struct job_t* job;
if (!id) {
printf("%s command requires PID or %%jobid argument\n", cmd);
return;
}
if (id[0] == '%') {
int jid = atoi(id + 1);
if (!jid) {
printf("%s: argument must be a PID or %%jobid\n", cmd);
return;
}
job = getjobjid(jobs, jid);
if (!job) {
printf("%%%d: No such job\n", jid);
return;
}
} else {
int pid = atoi(id);
if (!pid) {
printf("%s: argument must be a PID or %%jobid\n", cmd);
return;
}
job = getjobpid(jobs, pid);
if (!job) {
printf("(%d): No such process\n", pid);
return;
}
}
Kill(-(job->pid), SIGCONT);
if (!strcmp(cmd, "bg")) {
job->state = BG;
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
} else if (!strcmp(cmd, "fg")) {
job->state = FG;
waitfg(job->pid);
}
return;
}
trace 15,16
完成1~14后,15,16测试通过
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!