lab5 shellLabNote

lab5 shellLabNote

CSAPP 读书笔记系列chap8&10

这一次的实验是完善一个简单的tiny shell;
需要完善的函数有:

  • eval: Main routine that parses and interprets the command line. [70 lines]
  • builtin cmd: Recognizes and interprets the built-in commands: quit, fg, bg, and jobs. [25 lines]
  • do_bgfg: Implements the bg and fg built-in commands. [50 lines]
  • waitfg: Waits for a foreground job to complete. [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]

其实大多数函数书上已经有例子,照猫画虎的添加,问题也不大.

实验涉及的函数,chap8.md 也说了.

需要注意的是,因为涉及到并发的原因,gdb调试反而不如printf打印日志好用,这也算是以后的工作知道日志的重要吧.

代码的github地址

觉得应该顺着tshref.out文件的trace 文件来一点点做,从小功能开始慢慢迭代上去.

步骤

step1

第一步需要完成的函数是eval,定义一些参数变量,这是个big jobs.

void eval(char *cmdline) {
  char buf[MAXLINE]; // 缓冲
  sigset_t mask;     //信号的掩码
  pid_t pid;         // 进程ID

  struct comline_tokens {
    int bgFlag; // 是否为后台运行
    int argc;
    char *argv[MAXLINE]; /*execve()需要执行的多个argv参数,指令名和parms*/
  } tokens;

  strcpy(buf, cmdline);
  tokens.bgFlag = parseline(buf, tokens.argv);
  if (NULL == tokens.argv[0])
    return; //输入空行

  if (!builtin_cmd(tokens.argv)) {
    // 不为内置函数,执行execve

    // 防止race
    sigfillset(&mask);
    // sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, NULL); //阻塞CHLD信号

    if ((pid = fork()) == 0) {
      //子进程
      // 取消阻塞CHLD信号
      sigprocmask(SIG_UNBLOCK, &mask, NULL);
      setpgid(0, 0);
      // 执行相应的指令
      if (execve(tokens.argv[0], tokens.argv, environ) < 0) {
        // file not found
        fprintf(stderr, "%s command not found\n", tokens.argv[0]);
        return;
      }
    }
    //父进程
    // 加入job后取消阻塞CHLD,见8.5.6
    addjob(jobs, pid, tokens.bgFlag ? BG : FG, cmdline);
    sigprocmask(SIG_UNBLOCK, &mask, NULL);
    if (tokens.bgFlag == 1) {
      // 后台运行
      printf("[%d],(%d) %s", pid2jid(pid), pid, cmdline);
    } else {
      waitfg(pid);
    }
  }
  return;
}
step2

写了几行就发现需要完善builtin_cmd函数,

/*
 * builtin_cmd - If the user has typed a built-in command then execute
 *    it immediately.
 */
int builtin_cmd(char **argv) {
  if (!strcmp(argv[0], "quit"))
    // quit退出shell
    exit(0);
  if (!strcmp(argv[0], "&"))
    // &指令无效
    return 1;
  if (!strcmp(argv[0], "jobs")) {
    listjobs(jobs);
    return 1;
  }
  if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) {
    do_bgfg(argv);
    return 1;
  }
  return 0; /* not a builtin command */
}

然后发现基本的功能已经实现了,比如跑一个/bin/ls

执行ls

但bg 和 fg的功能没实现,这个不着急,等等step4

step3 信号的处理

信号的处理,这里相对比较难.我这里没有做好

waitfg

先说等等前台job结束的函数waitfg,这个按照书上的chap8.5.7,写一个while()循环.浪费就浪费

/*
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid) {
  // 等前台job结束,8.5.7
  sigset_t zero;
  sigfillset(&zero);
  while (pid == fgpid(jobs))
    ;
  // 暂时开放所有信号
  // sigsuspend(&zero);
  return;
}

结果就被打脸了,CPU开始都是100%的运行...

CPU100%.png
当然也可以使用sigsuspend函数,这里我没有使用

sigint_handler

SIGINT 信号处理器,处理ctrl-c

void sigint_handler(int sig) {
  // 发送信号SIGINT到所有前台程序
  pid_t pid = fgpid(jobs);
  if (pid != 0) {
    // 使用SIG_CHID 杀掉子进程
    kill(-pid, SIGINT);
  }

  return;
}
sigtstp_handler

SIGTSTP,处理ctrl-z的函数

void sigtstp_handler(int sig) {
  pid_t pid = fgpid(jobs);
  // int jid = pid2jid(pid);
  if (pid != 0) {
    printf("SIGTSTP got there\n");
    (*getjobpid(jobs, pid)).state = ST;
    kill(-pid, SIGTSTP);
  }
  return;
}
sigchld_handler

SIG_CHID信号处理函数,
这个没做好,在父进程和子进程中,进程的调度不符合test14的要求,实在想不明白(留个坑)

void sigchld_handler(int sig) {
  // SIG_CHID信号处理函数
  pid_t pid;
  int status; // 检测waitpid回收子进程的退出状态,判断是由于什么原因停止或暂停的。

  while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
    if (WIFEXITED(status))
      // 子进程exit或return正常退出
      deletejob(jobs, pid);
    if (WIFSIGNALED(status)) {
      // 因为一个未被捕获的信号终止
      // 格式: Job [1] (26263) terminated by signal 2
      printf("Job[%d] (%d) terminated by signal %d\n", pid2jid(pid), pid,
             WSTOPSIG(status));
      deletejob(jobs, pid);
    }
  }

  return;
}

test16 也没通过,具体的测试结果可以看这篇http://blog.csdn.net/The_V_/article/details/46842753

分析可以看这篇http://wdxtub.com/2016/04/16/thick-csapp-lab-5/

step4 do_bgfg

也就是处理fg 和 bg 的命令

  • bg:将一个在后台暂停的命令,变成继续执行
  • fg:将后台中的命令调至前台继续运行

都是通过kill发送SIGCONT来使进程继续

void do_bgfg(char **argv) {
  pid_t pid;
  struct job_t *job;
  char *id = argv[1];
  if (!id) {
    printf("%s commands requires PID or %% jobid argument\n", argv[0]);
    return;
  }
  if ('%' == id[0]) {
    // 使用jobs 中的序号
    int jid = atoi(&id[1]);
    job = getjobjid(jobs, jid);
    if (job == NULL) {
      // jobs 列表中没有
      printf("%%%d No such job \n", jid);
      return;
    }
  } else if (isdigit(id[0])) {
    pid = atoi(id);
    job = getjobpid(jobs, pid);
    if (job == NULL) {
      // jobs 列表中没有
      printf("(%d) No such process \n", pid);
      return;
    }
  } else {
    printf("%s commands requires PID or %% jobid argument\n", argv[0]);
    return;
  }
  kill(-job->pid, SIGCONT);     // 发送继续的信号
  if (!strcmp(argv[0], "bg")) { /*set job state ,do it in bg or fg*/
    job->state = BG;
    printf("[%d] (%d) %s\n", job->jid, job->pid, job->cmdline);
  } else {
    // 前台运行
    job->state = FG;
    printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
    waitfg(job->pid);
  }

  return;
}

实验就到这里做完了.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值