前言 计算机系统这学期一共有四个实验,其中的bomblab和buflab都特别有趣,让我学到了很多汇编相关的知识。至于第一次的datalab就,很怪。这次的shelllab也是让人摸不着头脑(结果就是得寻求高人的帮助),但最后还是得做出来(不然实验验收就寄了)。这里就先传一手shelllab的报告,至于bomblab和buflab的就再等什么时候有空再弄了。
一.实验目的 shell Lab的主要目的是为了让我们熟悉进程控制和信号。
二.实验内容 主要是对tsh.c中没有填写的函数进行填写,使得该shell能处理前后台运行程序、能够处理ctrl+z、ctrl+c等信号。需要实现的函数主要有以下七个:
eval: 主要功能是解析cmdline,并且运行.
builtin cmd: 辨识和解析出bulidin命令: quit, fg, bg, and jobs.
do bgfg: 实现bg和fg命令.
waitfg: 实现等待前台程序运行结束.
sigchld handler: 响应SIGCHLD.
sigint handler: 响应 SIGINT (ctrl-c) 信号.
sigtstp handler: 响应 SIGTSTP (ctrl-z) 信号.
三.实验过程 trace01 -> 正确终止EOF 直接运行就行了。
trace 02,03 ->实现内置的quit (1)首先观察trace02.txt文件,发现只有quit,WAIT两条命令。执行后可以发现无法正常终止,因为tsh的quit内置命令还未编写,所以不能正常退出。因此需要我们实现终止命令quit()。
(2)实现quit:
目的:补齐文件tsh.c中的函数eval()函数和函数builtin_cmd()与quit相关的部分。
实现思路:首先从命令中提取参数,然后判断是否为内置命令,如果为内置命令,则直接在当前进程执行即可;如果不是内置命令,则需要新建一个子进程,并利用 execve 来通过参数给出的路径寻找出可执行文件并在子进程中执行,如果找不到该可执行文件,则输出命令未找到,并结束子进程。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void eval (char *cmdline) { char *argv[MAXARGS]; char buf[MAXLINE]; pid_t pid; strcpy (buf,cmdline); parseline(buf,argv); if (argv[0 ] == NULL ) return ; if (!builtin_cmd(argv)){ if ((pid = fork()) == 0 ){ if (execve(arg[v0],argv,environ)<0 ){ printf ("%s: 命令未找到.\n" ,arg[0 ]); exit (0 ); } } return ; } }
(3)然后是判断是否为内置命令的函数builtin_cmd():
1 2 3 4 5 6 int builtin_cmd (char **argv) { if (!strcmp (argv[0 ],"quit" )) exit (0 ); return 0 ; }
此外,因为tset03功能是运行一个前台job,并且也是以quit终止,因此也同时完成了。
trace04 –>实现eval()的后台作业(BK job)管理功能 (1)思路:
·在原有的eval函数基础之上添加将作业添加至后台作业管理的函数使用(addjobs())。
·加以信号的阻塞和取消阻塞。
(2)具体实现:
1.首先使用一个标记符号 –> bg
2.因为要分析传入指令是否要在后台执行进程,因此要补充分析命令的函数builtin_cmd()
1 2 if (!strcmp (argv[0 ],"&" )) return 1 ;
3.接着在eval中进行判断是否为后台进程
1 2 3 4 5 6 if (!bg){ waitfg(pid); }else { printf ("[%d] (%d) %s" ,pid2jid(pid),pid,cmdline); }
4.再将waitfg()函数补充完整,让父进程正确地等待。
1 2 3 4 5 6 7 8 9 10 void waitfg (pid_t pid) { while (pid==fgpid(jobs)){ sleep(0 ); } return ; }
5.接下来实现信号的控制,这里使用sigprocmask()函数显式地阻塞和取消阻塞
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 (!builtin_cmd(argv)){ sigfillset(&mask_all); sigemptyset(&mask_one); sigaddset(&mask_one,SIGCHLD); sigprocmask(SIG_BLOCK,&mask_one,&prev_one); if ((pid = fork()) == 0 ){ sigprocmask(SIG_SETMASK,&prev_one,NULL ); if (setpgid(0 ,0 ) < 0 ){ printf ("setpgid error" ); exit (0 ); } 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==1 ? BG : FG,cmdline); sigprocmask(SIG_SETMASK,&prev_one,NULL );
6.接下来实现对应的sigcld_handler()以释放僵尸的子进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void sigchld_handler (int sig) { int olderrno = errno; sigset_t mask_all,prev_all; pid_t pid; sigfillset(&mask_all); while ((pid = waitpid(-1 ,NULL ,WNOHANG)) > 0 ){ sigprocmask(SIG_BLOCK,&mask_all,&prev_all); deletejob(jobs,pid); /释放僵尸进程/ sigprocmask(SIG_SETMASK,&prev_all,NULL ); } errno = olderrno; return ; }
7.综上,我们的addjobs就成功实现了!
trace05 –>处理jobs内置命令 (1)思路:直接调用自带的listjobs()方法,就是在原有builtin_cmd函数中添加一个判断函数,如果参数是jobs,则执行listjobs函数的功能(即将所有的作业打印出来)。
(2)实现:
1 2 3 4 if (!strcmp (argv[0 ],"jobs" )){ listjobs(jobs); return 1 ; }
trace06、trace07 ->处理SIGINT信号 (1)目的:
要实现的功能是:
trace06->将SIGINT信号转发到前台作业;
trace07->仅仅将SIGINT信号转发到前台作业;
因此这里放在一起实现。
(2)实现:
根据文档中的解决方法,我们来一步步实现。
1.首先更改一下eval函数,在其中调用setpgid(0,0):
1 2 3 4 5 6 7 8 9 10 11 if (setpgid(0 ,0 ) < 0 ){ printf ("setpgid error" ); exit (0 ); } if (execve(argv[0 ],argv,environ) < 0 ){ printf ("%s: Command not found.\n" ,argv[0 ]); exit (0 ); } }
2.更改信号处理函数sigint_handler(),实现转发到前台作业的操作(包含前台作业的进程组)
1 2 3 4 5 6 7 8 9 10 11 void sigint_handler (int sig) { pid_t pid = fgpid(jobs); if (pid > 0 ){ kill(-pid,sig); } return ; }
3.还要修改sigchld_handler()函数:为了区分进程终止的原因(符合测试文件)(后边也会用到)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (WIFEXITED(status)){ sigprocmask(SIG_BLOCK,&mask_all,&prev_all); deletejob(jobs,pid); sigprocmask(SIG_SETMASK,&prev_all,NULL ); } if (WIFSIGNALED(status)){ int jid = pid2jid(pid); printf ("Job [%d] (%d) terminated by signal %d\n" ,jid,pid,WTERMSIG(status)); deletejob(jobs,pid); }
6、Trace08 -> 仅仅将SIGSTP(ctrl+z)转发到前台作业(与上一题实现大同小异) (1)因此我们就直接实现其信号处理函数sigtstp_handler():
1 2 3 4 5 6 7 8 9 10 11 12 void sigtstp_handler (int sig) { pid_t pid = fgpid(jobs); if (pid > 0 ){ kill(-pid,sig); } return ; }
(2)依旧来修改一下sigchld_handler()函数。区分终止/停止。
思路:因此在上一题的基础上加上对于SIGTSTP(ctrl+z)的判断和信息显示。如下:
1 2 3 4 5 6 7 if (WIFSTOPPED(status)){ struct job_t * job = getjobpid(jobs,pid); int jid = pid2jid(pid); printf ("Job [%d] (%d) stopped by signal %d\n" ,jid,pid,WSTOPSIG(status)); job->state = ST; }
并改变while的判断条件:
1 while ((pid = waitpid(-1 ,&status,WNOHANG | WUNTRACED)) > 0 )
WNOHANG:挂起调用进程,直到有子进程终止。
WUNTRACED:挂起调用进程,直到等待集合中的一个进程变成已终止或者被停止。
WNOHANG | WUNTRACED:等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID。
两个合在一起就是接收终止和停止(ctrl+z和ctrl+c)。
trace 09 —> 实现进程内置命令bg bg < job >:命令会向一个已经停止的job发送SIGCNOT信号来重启这个job,并作为后台作业运行,参数可以是PID或JID。
(1)首先是完成识别命令:
要将bg命令添加到识别命令的函数builtin_cmd()中:
1 2 3 4 if (!strcmp (argv[0 ],"bg" )){ do_bgfg(argv); return 1 ; }
(2)接下来实现其处理函数:
修改do_bgfg()函数:
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 do_bgfg (char **argv) { pid_t pid; int jid; struct job_t * job ; if (argv[1 ][0 ] == '%' ){ jid = atoi(argv[1 ]+1 ); job = getjobjid(jobs,jid); pid = job->pid; }else { pid = atoi(argv[1 ]); job = getjobpid(jobs,pid); jid = job->jid; } if (pid > 0 ){ if (!strcmp (argv[0 ],"bg" )){ printf ("[%d] (%d) %s" ,jid,pid,job->cmdline); job->state = BG; kill(-pid,SIGCONT); } } return ; }
trace 10 —> 实现进程内置命令fg(与上一题差不多) fg < job >:将一个已停止或正在运行的后台作业更改为正在前台运行的作业。
(1)老方法,先往builtin_cmd()函数添加内容:
1 2 3 4 if (!strcmp (argv[0 ],"fg" )){ do_bgfg(argv); return 1 ; }
(2)然后往do_bgfg()函数中加入相关处理:
1 2 3 4 5 6 7 8 else if (!strcmp (argv[0 ],"fg" )){ if (job->state = ST){ kill(-pid,SIGCONT); } job->state = FG; waitfg(pid); }
trace11,12 trace 11 —> 将SIGINT转发给前台进程组中的每个进程
trace 12 —> 将SIGSTP转发给前台进程组中的每个进程
这两个实验在之前的trace06-trace07的分析中已经实现了,因此我们直接执行即可:
(1)sigint_handler()函数:
1 2 3 4 5 6 7 8 void sigint_handler (int sig) { pid_t pid = fgpid(jobs); if (pid > 0 ){ kill(-pid,sig); } return ; }
(2)sigtstp_handler()函数:
1 2 3 4 5 6 7 8 9 void sigtstp_handler (int sig) { pid_t pid = fgpid(jobs); if (pid > 0 ){ kill(-pid,sig); } return ; }
trace13 –>重新启动进程组中每个已经停止的进程 (1)分析:
· 因为此时需要唤醒所有停止的进程,因此要将唤醒函数kill(pid,SIGCONT)的第一个参数改为-pid,因为当其第一个参数<0时,kill就会将SIGCONT信号传递给整个进程组。
· 因为在FG中,有一步是需要等待当前的前台进程完成之后,才会唤醒进程组中的进程,所以为了保证唤醒所有进程,就要去掉FG中,job->state == ST才传递SIGCONT信号的判断,因为当前运行进程可能没有停止(ST),但是进程组中是有停止的,进程组中停止的这些也需要被唤醒。
(2)综上,我们得到以下实现do_bgfg()更改:
1 2 3 4 5 6 7 8 9 10 11 12 13 if (pid > 0 ){ if (!strcmp (argv[0 ],"bg" )){ printf ("[%d] (%d) %s" ,jid,pid,job->cmdline); job->state = BG; kill(-pid,SIGCONT); }else if (!strcmp (argv[0 ],"fg" )){ job->state = FG; kill(-pid,SIGCONT); waitfg(pid); } }
trace14 简单的错误处理(就是处理输入未实现的命令、fg、bg参数不正确等错误情况) (1)又直接运行make rtest14可知有五种处理方式,因此我们在do_bgfg()中进行对应的处理即可。
(2)处理:第一个错误:Command not found,未实现的命令。
我们再次回顾一下shell的执行流程:程序会首先执行 eval(),在 eval中进行判断(使用buildin_cmp()函数),如果发现命令不是内置命令,则会调用 fork()函数来新建一个子进程,在子进程中调用 execve()函数通过 argv[0]来寻找路径,并在子进程中运行路径中的可执行文件,如果找不到可执行文件,则说明命令为无效命令。因此我们在此处加入输出语句即可,如下:
1 2 3 4 5 if (execve(argv[0 ],argv,environ) < 0 ){ printf ("%s: Command not found.\n" ,argv[0 ]); exit (0 ); }
第二个错误是:fg command requires PID or %jobid argument,fg命令时没有传入pid或jid。因此在do_bgfg()中实现:
1 2 3 4 5 if (argv[1 ] == NULL ){ printf ("%s command requires PID or %%jobid argument\n" ,argv[0 ]); return ; }
第三个错误是:fg: argument must be a PID or %jobid,传入了pid或jid,但是不符合规范(pid或jid必须为数字)。处理:
1 2 3 4 5 6 7 if (argv[1 ][0 ] == '%' ){ if (argv[1 ][1 ] < '0' || argv[1 ][1 ] >'9' ){ printf ("fg: argument must be a PID or %%jobid\n" ); return ; } }
第四个错误是:No such process,通过传入的pid找不到对应的作业(job=null)处理:
1 2 3 4 5 6 job = getjobjid(jobs,jid); if (job == NULL ){ printf ("%%%d: No such job\n" ,jid); return ; }
第五个错误:No such job,通过传入的jid找不到对应的job(job=null)
1 2 3 4 5 6 job = getjobpid(jobs,pid); if (job == NULL ){ printf ("(%d): No such process\n" ,pid); return ; }
此外,我们发现还有一行(如下),这里我们和trace15一起解决。所以接下来看一下trace15.
1 2 tsh>fg %1 job [1 ] (3104 ) stopped by signal 20
trace15–>所有命令一起运行 (1)经make rtest15可知是缺失两条消息的处理。那么首先查看一下文件trace15.txt,看看是因为什么信号出现这种情况:经查看发现一是INT信号将job10终止,二是TSTP信号将job1中断。因此我们就在终止信号处理函数sihchld_handler()中进行判断处理,并输出上述错误信息:
(2)处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 if (WIFSIGNALED(status)){ int jid = pid2jid(pid); printf ("Job [%d] (%d) terminated by signal %d\n" ,jid,pid,WTERMSIG(status)); deletejob(jobs,pid); } if (WIFSTOPPED(status)){ struct job_t * job = getjobpid(jobs,pid); int jid = pid2jid(pid); printf ("Job [%d] (%d) stopped by signal %d\n" ,jid,pid,WSTOPSIG(status)); job->state = ST; }
trace16 –>测试shell是否能够处理来自其他进程而不是终端的SIGTSTP和SIGINT信号 emmm,好像没有要做的事了,直接执行就行了。
完整代码 最后给出7个函数的完整代码
eval 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 void eval (char *cmdline) { char *argv[MAXARGS]; char buf[MAXLINE]; int bg; pid_t pid; strcpy (buf,cmdline); bg = parseline(buf,argv); if (argv[0 ] == NULL ) return ; sigset_t mask_all,mask_one,prev_one; if (!builtin_cmd(argv)){ sigfillset(&mask_all); sigemptyset(&mask_one); sigaddset(&mask_one,SIGCHLD); sigprocmask(SIG_BLOCK,&mask_one,&prev_one); if ((pid = fork()) == 0 ){ sigprocmask(SIG_SETMASK,&prev_one,NULL ); if (setpgid(0 ,0 ) < 0 ){ printf ("setpgid error" ); exit (0 ); } 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==1 ? BG : FG,cmdline); sigprocmask(SIG_SETMASK,&prev_one,NULL ); if (!bg){ waitfg(pid); }else { printf ("[%d] (%d) %s" ,pid2jid(pid),pid,cmdline); } } return ; }
builtin_cmd 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int builtin_cmd (char **argv) { if (!strcmp (argv[0 ],"quit" )) exit (0 ); if (!strcmp (argv[0 ],"jobs" )){ listjobs(jobs); return 1 ; } if (!strcmp (argv[0 ],"&" )) return 1 ; if (!strcmp (argv[0 ],"bg" )){ do_bgfg(argv); return 1 ; } if (!strcmp (argv[0 ],"fg" )){ do_bgfg(argv); return 1 ; } return 0 ; }
do_bgfg 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 void do_bgfg (char **argv) { pid_t pid; int jid; struct job_t * job ; if (argv[1 ] == NULL ){ printf ("%s command requires PID or %%jobid argument\n" ,argv[0 ]); return ; } if (argv[1 ][0 ] == '%' ){ if (argv[1 ][1 ] < '0' || argv[1 ][1 ] >'9' ){ printf ("fg: argument must be a PID or %%jobid\n" ); return ; } jid = atoi(argv[1 ]+1 ); job = getjobjid(jobs,jid); if (job == NULL ){ printf ("%%%d: No such job\n" ,jid); return ; } pid = job->pid; }else { if (argv[1 ][0 ] < '0' || argv[1 ][0 ] >'9' ){ printf ("bg: argument must be a PID or %%jobid\n" ); return ; } pid = atoi(argv[1 ]); job = getjobpid(jobs,pid); if (job == NULL ){ printf ("(%d): No such process\n" ,pid); return ; } jid = job->jid; } if (pid > 0 ){ if (!strcmp (argv[0 ],"bg" )){ printf ("[%d] (%d) %s" ,jid,pid,job->cmdline); job->state = BG; kill(-pid,SIGCONT); }else if (!strcmp (argv[0 ],"fg" )){ job->state = FG; kill(-pid,SIGCONT); waitfg(pid); } } return ; }
waitfg 1 2 3 4 5 6 7 8 9 10 void waitfg (pid_t pid) { while (pid==fgpid(jobs)){ sleep(0 ); } return ; }
sigchld_handler 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 void sigchld_handler (int sig) { int olderrno = errno; sigset_t mask_all,prev_all; pid_t pid; int status; sigfillset(&mask_all); while ((pid = waitpid(-1 ,&status,WNOHANG | WUNTRACED)) > 0 ){ if (WIFEXITED(status)){ sigprocmask(SIG_BLOCK,&mask_all,&prev_all); deletejob(jobs,pid); sigprocmask(SIG_SETMASK,&prev_all,NULL ); } if (WIFSIGNALED(status)){ int jid = pid2jid(pid); printf ("Job [%d] (%d) terminated by signal %d\n" ,jid,pid,WTERMSIG(status)); deletejob(jobs,pid); } if (WIFSTOPPED(status)){ struct job_t * job = getjobpid(jobs,pid); int jid = pid2jid(pid); printf ("Job [%d] (%d) stopped by signal %d\n" ,jid,pid,WSTOPSIG(status)); job->state = ST; } } errno = olderrno; return ; }
sigint_handler 1 2 3 4 5 6 7 8 void sigint_handler (int sig) { pid_t pid = fgpid(jobs); if (pid > 0 ){ kill(-pid,sig); } return ; }
sigtstp_handler
1 2 3 4 5 6 7 8 9 void sigtstp_handler (int sig) { pid_t pid = fgpid(jobs); if (pid > 0 ){ kill(-pid,sig); } return ; }