由于我的第五个实验的partB部分一直出问题。而且修了好久没解决先不管了

这个实验建议一定要认真读完csapp全书的第八章。不然可能会毫无思路。千万不要上来直接做。

0. 环境配置和实验下载

利用docker配置Linux环境。无论你是mac还是windows都可以轻松搞定 https://www.cnblogs.com/JayL-zxl/p/14286789.html

1. 实验目的

你的任务为补全tsh.c里面的一些空缺的函数。

eval: 解析命令行 [70 lines]

builtin cmd: 识别命令是否为内置命令: quit, fg, bg, and jobs. [25 lines]

do bgfg:实现bgfg命令. [50 lines]

waitfg: 等待前台程序完成 [20 lines]

sigchld handler: Catches SIGCHILD signals. 80 lines]

sigint handler: Catches SIGINT (ctrl-c) signals. [15 lines]

sigtstp handler: Catches SIGTSTP (ctrl-z) signals. [15 lines]

每一次你修改tsh.c函数之后都要重新编译它,然后运行你的shell

  1. make
  2. ./tsh

writeup里面给了非常多的提示。这里简单给大家列举一下

  1. 要详细阅读书本的第八章内容
  2. 如果用户输入ctrl-c (ctrl-z),那么SIGINT (SIGTSTP)信号应该被送给每一个在前台进程组中的进程,如果没有进程,那么这两个信号应该不起作用。
  3. 如果一个命令以“&”结尾,那么tsh应该将它们放在后台运行,否则就放在前台运行(并等待它的结束)
  4. tsh应该回收(reap)所有僵尸进程,如果一个工作是因为收到了一个它没有捕获的(没有按照信号处理函数)而终止的,那么tsh应该输出这个工作的PID和这个信号的相关描述。
  5. tsh应该支持如下的内置命令
  1. The quit command terminates the shell.
  2. The jobs command lists all background jobs.
  3. The bg <job> command restarts <job> by sending it a SIGCONT signal, and then runs it in
  4. the background. The <job> argument can be either a PID or a JID.
  5. The fg <job> command restarts <job> by sending it a SIGCONT signal, and then runs it in
  6. the foreground. The <job> argument can be either a PID or a JID.

书上有展示过一个非常简单的Shell程序这里先复习一下。







当然上面的程序并没有回收后台运行的程序。而且它非常不完整,但足够帮助我们理解整个shell的基本逻辑了

1.1 信号简介





当一个进程收到信号之后就会调用信号处理程序。这个也是我们任务之一。

  1. 信号--->进程---->信号处理程序---执行------结果

当然关于信号有很多可以说的。比如信号不会排队啊。信号处理程序的阻塞,以及并发情况如何处理啊。这些请大家仔细阅读课本第八章。

2. 实验开始

好了下面开始实验了。首先根据writeup我们知道我们要依次根据test文件进行测试。就是说我们的make test0x的执行结果要和make rtest0x的参考结果一样就可以了。



这里我们发现对于test02的测试就出现了问题。这里去看一下trace02看看到底发生了什么。



这里有一条quit指令。不通过的原因就是我们的tsh程序还没有实现对于这个命令的处理。所以它不会退出而会停在这里。

下面正式开始我们的实验啦



由于我们没有办法预估信号到达的顺序。因此对于并发编程的处理就变得非常的重要。

考虑下面这种情况



因此我们要考虑这种情况。在父进程addjob之前先把SIGCHLD信号阻塞掉。

1. eval函数的实现

  1. void eval(char *cmdline)
  2. {
  3. char *argv[MAXLINE]; /*Argument list execve()*/
  4. char buf[MAXLINE]; /*Hold modified commend line*/
  5. int bg; /*Should the job run in bg or fg?*/
  6. pid_t pid;
  7. int state;
  8. sigset_t mask_all, mask_one, prev_one;
  9. strcpy(buf,cmdline);
  10. bg=parseline(cmdline,argv);
  11. if(argv[0]==0){
  12. return ;//ignore empty line
  13. }
  14. if(!builtin_cmd(argv)){
  15. sigfillset(&mask_all);
  16. sigemptyset(&mask_one);
  17. sigaddset(&mask_one, SIGCHLD);
  18. sigprocmask(SIG_BLOCK, &mask_one, &prev_one); //Block SIGCHLD
  19. if((pid==fork())==0){
  20. sigprocmask(SIG_SETMASK,&prev_one,NULL);//UnBlock SIGCHLD
  21. if (setpgid(0, 0) < 0)
  22. {
  23. perror("SETPGID ERROR");
  24. exit(0);
  25. }
  26. if (execve(argv[0], argv, environ) < 0) //if execve error return 0
  27. {
  28. printf("%s: Command not found\n", argv[0]);
  29. exit(0);
  30. }
  31. }
  32. else{
  33. state = bg ? BG : FG;
  34. sigprocmask(SIG_BLOCK, &mask_all, NULL); //parent process
  35. addjob(jobs, pid, state, cmdline);
  36. sigprocmask(SIG_SETMASK, &prev_one, NULL); //unblock SIGCHLD
  37. }
  38. bg?printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline):waitfg(pid);
  39. }
  40. return;
  41. }

基本参考了书上p525shell代码和p543的代码。考虑了上述提到的并发访问问题。

2. builtin_cmd的实现

如果用户输入的是built-in command则立即执行否则返回0

一共需要支持四个内置命令

分别处理即可

  1. int builtin_cmd(char **argv)
  2. {
  3. if(!strcmp(argv[0],"quit")){
  4. exit(0);
  5. }
  6. if(!strcmp(argv[0],"&"))
  7. return 1;
  8. if(!strcmp(argv[0],"bg")||!strcmp(argv[0],"fg"){
  9. do_bgfg(argv);
  10. return 1;
  11. }
  12. if(!strcmp(argv[0],"jobs")){
  13. listjobs(jobs);
  14. return 1;
  15. }
  16. return 0; /* not a builtin command */
  17. }

3. do_bgfg的实现

Execute the builtin bg and fg commands

每个job都可以由进程ID(PID)或job ID(JID)标识,该ID是一个正整数tsh分配。 JID应该在命令行上以前缀“%”表示。 例如,“%5”

表示JID 5,“ 5”表示PID5。(我们已为您提供了所需的所有例程处理工作清单。)

  1. tsh> fg %1
  2. Job [1] (9723) stopped by signal 20
  1. kill(pid,signal)的规则
  2. if (pid < 0) 则向|pid|的组中全部发送信号
  3. if(pid > 0) 则就向单个进程发送
  4. if(pid=0) 信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
  1. void do_bgfg(char **argv)
  2. {
  3. struct job_t *job;
  4. int id;
  5. if(argv[1]==NULL){
  6. printf("%s command requires PID or %%jobid argument\n", argv[0]);
  7. return ;
  8. }
  9. if (sscanf(argv[1], "%%%d", &id) > 0)
  10. {
  11. job = getjobjid(jobs, id);
  12. if (job == NULL)
  13. {
  14. printf("%%%d: No such job\n", id);
  15. return ;
  16. }
  17. }
  18. else if (sscanf(argv[1], "%d", &id) > 0)
  19. {
  20. job = getjobpid(jobs, id);
  21. if (job == NULL)
  22. {
  23. printf("(%d): No such process\n", id);
  24. return ;
  25. }
  26. }
  27. else
  28. {
  29. printf("%s: argument must be a PID or %%jobid\n", argv[0]);
  30. return;
  31. }
  32. if(!strcmp(argv[0], "bg"))
  33. {
  34. kill(-(job->pid), SIGCONT);
  35. job->state = BG;
  36. printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline);
  37. }
  38. else
  39. {
  40. kill(-(job->pid), SIGCONT);
  41. job->state = FG;
  42. waitfg(job->pid);
  43. }
  44. return;
  45. }

3. waitfg 函数实现

  1. Block until process pid is no longer the foreground process
  1. void waitfg(pid_t pid)
  2. {
  3. sigset_t mask;
  4. sigemptyset(&mask);
  5. while (fgpid(jobs) > 0)
  6. sigsuspend(&mask);
  7. return;
  8. }

这里注意一下

4. sigchld_handler的实现

  1. The kernel sends a SIGCHLD to the shell whenever
  2. * a child job terminates (becomes a zombie),
  3. * stops because it received a SIGSTOP or SIGTSTP signal.
  4. The handler reaps all available zombie children, but doesn't wait for any other currently running children to terminate.

关于waitpid参数的全介绍

  1. void sigchld_handler(int sig)
  2. {
  3. int olderrno = errno;
  4. pid_t pid;
  5. int status;
  6. sigset_t mask_all, prev;
  7. sigfillset(&mask_all);
  8. while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
  9. {
  10. if (WIFEXITED(status)) //正常
  11. {
  12. sigprocmask(SIG_BLOCK, &mask_all, &prev);
  13. deletejob(jobs, pid);
  14. sigprocmask(SIG_SETMASK, &prev, NULL);
  15. }
  16. else if (WIFSIGNALED(status))
  17. {
  18. struct job_t* job = getjobpid(jobs, pid);
  19. sigprocmask(SIG_BLOCK, &mask_all, &prev);
  20. printf("Job [%d] (%d) terminated by signal %d\n", job->jid, job->pid, WTERMSIG(status));
  21. deletejob(jobs, pid);
  22. sigprocmask(SIG_SETMASK, &prev, NULL);
  23. }
  24. else /
  25. {
  26. struct job_t* job = getjobpid(jobs, pid);
  27. sigprocmask(SIG_BLOCK, &mask_all, &prev);
  28. printf("Job [%d] (%d) stopped by signal %d\n", job->jid, job->pid, WSTOPSIG(status));
  29. job->state= ST;
  30. sigprocmask(SIG_SETMASK, &prev, NULL);
  31. }
  32. }
  33. errno = olderrno;
  34. return;
  35. }

5. sigint_handler的实现

The kernel sends a SIGINT to the shell whenver the user types ctrl-c at the keyboard. Catch it and send it along to the foreground job.

  1. void sigint_handler(int sig)
  2. {
  3. int pid=fgpid(jobs);
  4. int jid=pid2jid(pid);
  5. sigset_t mask_all, prev;
  6. sigfillset(&mask_all);
  7. if(pid!=0){
  8. sigprocmask(SIG_BLOCK, &mask_all, &prev);
  9. printf("Job [%d] terminated by SIGINT.\n",jid);
  10. deletejob(jobs,pid);
  11. sigprocmask(SIG_SETMASK, &prev, NULL);
  12. kill(-pid,sig);
  13. }
  14. return;
  15. }

6. sigstp_handler的实现

The kernel sends a SIGSTP to the shell whenver the user types ctrl-z at the keyboard. Catch it and suspend the

foreground job by sending it a SIGTSTP.

这个的整体和实现和上面的几乎一摸一样。

  1. void sigtstp_handler(int sig)
  2. {
  3. int pid=fgpid(jobs);
  4. int jid=pid2jid(pid);
  5. sigset_t mask_all, prev;
  6. sigfillset(&mask_all);
  7. if(pid!=0){
  8. sigprocmask(SIG_BLOCK, &mask_all, &prev);
  9. printf("Job [%d] stopped by SIGSTP.\n",jid);
  10. (*getjobpid(jobs,pid)).state = ST;;
  11. sigprocmask(SIG_SETMASK, &prev, NULL);
  12. kill(-pid,sig);
  13. }
  14. return;
  15. }

[已完成+附代码]CS:APP:Lab6-ShellLab的更多相关文章

  1. 分布式消息总线,基于.NET Socket Tcp的发布-订阅框架之离线支持,附代码下载

    一.分布式消息总线以及基于Socket的实现 在前面的分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载一文之中给大家分享和介绍了一个极其简单也非常容易上的基于.N ...

  2. 零行代码为App添加异常加载占位图

    前文提要 近期准备重构项目,需要重写一些通用模块,正巧需要设置App异常加载占位图的问题,心血来潮设想是否可以零行代码解决此问题,特在此分享实现思路. 思路分享 对于App占位图,通常需要考虑的控件有 ...

  3. 零行代码为 App 添加异常加载占位图

    前文提要 近期准备重构项目,需要重写一些通用模块,正巧需要设置App异常加载占位图的问题,心血来潮设想是否可以零行代码解决此问题,特在此分享实现思路. 思路分享 对于App占位图,通常需要考虑的控件有 ...

  4. 数据挖掘领域十大经典算法之—C4.5算法(超详细附代码)

    https://blog.csdn.net/fuqiuai/article/details/79456971 相关文章: 数据挖掘领域十大经典算法之—K-Means算法(超详细附代码)        ...

  5. iOS开发 swift 3dTouch实现 附代码

    iOS开发 swift 3dTouch实现 附代码 一.What? 从iphone6s开始,苹果手机加入了3d touch技术,最简单的理解就是可以读取用户的点击屏幕力度大小,根据力度大小给予不同的反 ...

  6. 深入理解计算机系统 (CS:APP) Lab2 - Bomb Lab 解析

    原文地址:https://billc.io/2019/04/csapp-bomblab/ 写在前面 CS:APP是这学期的一门硬核课程,应该是目前接触到最底层的课程了.学校的教学也是尝试着尽量和CMU ...

  7. 图文并茂-超详解 CS:APP: Lab3-Attack(附带栈帧分析)

    CS:APP:Lab3-ATTACK 0. 环境要求 关于环境已经在lab1里配置过了.lab1的连接如下 实验的下载地址如下 说明文档如下 http://csapp.cs.cmu.edu/3e/at ...

  8. error MSB6006: “CL.exe”已退出,代码为X —— 的解决办法

    错误 : error MSB6006: “CL.exe”已退出,代码为X . 解决方法: 1.有少可能是执行目录引起的. 参考 http://bbs.csdn.net/topics/370064083 ...

  9. 解决VS下“LC.exe已退出,代码为-1”问题

    今天使用VS2015开发一个Winform程序,手一抖拖错了一个第三方控件,然后将其去掉并删除相关的引用,结果导致了LC.exe错误:"Lc.exe已退出,代码为-1 ". 经过上 ...

随机推荐

  1. 系统吞吐量与QPS/TPS

    QPS/TPS QPS:Queries Per Second意思是"每秒查询率",是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准. ...

  2. ctfshow—web—web5

    打开靶机,代码审计 附上代码 <?php error_reporting(0); ?> <html lang="zh-CN"> <head> & ...

  3. BDC应用

    第一步:SHDB或者是SM35进入BDC录制事务.开始录制. 第二部:保存录制的记录. 第三步:在你自己的程序中定义一个内表如:ITAB TYPE TABLE OF BDCDATA. 再定义一个工作空 ...

  4. Mybatis执行流程学习之手写mybatis雏形

    Mybatis是目前开发中最常用的一款基于ORM思想的半自动持久层框架,平时我们都仅仅停留在使用阶段,对mybatis是怎样运行的并不清楚,今天抽空找到一些资料自学了一波,自己写了一个mybatis的 ...

  5. 1.2V转5V稳压芯片,低功耗电路

    PW5100具有将低输入电压0.7V-5V之间的范围,升压型,升压到5V的稳定电压输出. 可以使其镍氢电池1.2V稳定输出5V的1.2V转5V芯片. PW5100具有极低的输入静态功耗,1.2V时,应 ...

  6. 使用 gitlab-runner 持续集成

    gitlab-runner 是 Gitlab 推出的与 Gitlab CI 配合使用的持续集成工具.当开发人员在 Gitlab 上更新代码之后,Gitlab CI 服务能够检测到代码更新,此时可以触发 ...

  7. Django orm中related_name/related_query_name区别

    related_name/related_query_name区别 class Department(models.Model): title = models.CharField(verbose_n ...

  8. 4、剑指offer——从尾到头打印链表java实现

    **题目描述** **输入一个链表,按链表从尾到头的顺序返回一个ArrayList.** 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M 思路:   1.如果链 ...

  9. map 传递给函数的代价

    https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/08.1.md map 传递给函数的代价很小:在 32 位机器上占 4 ...

  10. (Oracle)已有数据表建立表分区—在线重定义

    今天在做数据抽取的时候,发现有一张业务表数据量达到了5000W,所以就想将此表改为分区表.分区表的有点如下: 1.改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索速度.2.增强可用性: ...